├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── cn │ │ └── easydone │ │ └── androidfluxpractice │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── cn │ │ │ └── easydone │ │ │ └── androidfluxpractice │ │ │ ├── RxBus.java │ │ │ ├── action │ │ │ ├── Action.java │ │ │ ├── ActionCreator.java │ │ │ ├── UserAction.java │ │ │ └── UserActionCreator.java │ │ │ ├── bean │ │ │ ├── GitHubUser.java │ │ │ └── User.java │ │ │ ├── dispatcher │ │ │ └── Dispatcher.java │ │ │ ├── request │ │ │ ├── GitHubApi.java │ │ │ └── GitHubApiUtils.java │ │ │ ├── store │ │ │ ├── Store.java │ │ │ └── UserStore.java │ │ │ └── ui │ │ │ ├── MainActivity.java │ │ │ └── UserAdapter.java │ └── res │ │ ├── layout │ │ ├── activity_main.xml │ │ └── item_github_user.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── cn │ └── easydone │ └── androidfluxpractice │ └── ExampleUnitTest.java ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── 当EventBus遇上RxJava.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /.idea 4 | /build 5 | gradle.properties 6 | local.properties 7 | /app/build 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AndroidFluxPractice 2 | 3 | Android Flux Practice . 4 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.3" 6 | 7 | defaultConfig { 8 | applicationId "cn.easydone.androidfluxpractice" 9 | minSdkVersion 14 10 | targetSdkVersion 23 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | debug { 16 | minifyEnabled true 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | release { 20 | minifyEnabled true 21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | productFlavors {} 25 | } 26 | 27 | dependencies { 28 | compile fileTree(dir: 'libs', include: ['*.jar']) 29 | testCompile 'junit:junit:4.12' 30 | compile 'com.android.support:design:23.3.0' 31 | compile 'com.github.bumptech.glide:glide:3.6.1' 32 | compile 'io.reactivex:rxandroid:1.1.0' 33 | compile 'io.reactivex:rxjava:1.1.2' 34 | compile 'com.squareup.retrofit2:retrofit:2.0.1' 35 | compile 'com.squareup.retrofit2:converter-gson:2.0.1' 36 | compile 'com.squareup.retrofit2:adapter-rxjava:2.0.1' 37 | compile 'com.squareup.okhttp3:logging-interceptor:3.0.0-RC1' 38 | } 39 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/liang/Documents/android-sdk-macosx/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | 20 | #指定代码的压缩级别 21 | -optimizationpasses 5 22 | #包名不混合大小写 23 | -dontusemixedcaseclassnames 24 | #不去忽略非公共的库类 25 | -dontskipnonpubliclibraryclasses 26 | #优化 不优化输入的类文件 27 | -dontoptimize 28 | #预校验 29 | -dontpreverify 30 | #混淆时是否记录日志 31 | -verbose 32 | # 混淆时所采用的算法 33 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* 34 | #保护注解 35 | -keepattributes *Annotation* 36 | #保持哪些类不被混淆 37 | -keep public class * extends android.app.Application 38 | -keep public class * extends android.app.AppCompatActivity 39 | -keep public class * extends android.app.Activity 40 | -keep public class * extends android.app.Fragment 41 | 42 | -keep class cn.easydone.androidfluxpractice.bean.** { *; } 43 | -keep class cn.easydone.androidfluxpractice.ui.** { *; } 44 | 45 | -keepclassmembers class * { 46 | public (org.json.JSONObject); 47 | } 48 | 49 | #忽略警告 50 | -ignorewarning 51 | 52 | #如果引用了v4或者v7包 53 | -dontwarn android.support.** 54 | 55 | -keep public class * extends android.view.View { 56 | public (android.content.Context); 57 | public (android.content.Context, android.util.AttributeSet); 58 | public (android.content.Context, android.util.AttributeSet, int); 59 | public void set*(...); 60 | } 61 | 62 | #保持 native 方法不被混淆 63 | -keepclasseswithmembernames class * { 64 | native ; 65 | } 66 | 67 | #保持自定义控件类不被混淆 68 | -keepclasseswithmembers class * { 69 | public (android.content.Context, android.util.AttributeSet); 70 | } 71 | 72 | #保持自定义控件类不被混淆 73 | -keepclasseswithmembers class * { 74 | public (android.content.Context, android.util.AttributeSet, int); 75 | } 76 | #保持自定义控件类不被混淆 77 | -keepclassmembers class * extends android.app.Activity { 78 | public void *(android.view.View); 79 | } 80 | 81 | #保持 Parcelable 不被混淆 82 | -keep class * implements android.os.Parcelable { 83 | public static final android.os.Parcelable$Creator *; 84 | } 85 | 86 | #保持 Serializable 不被混淆 87 | -keepnames class * implements java.io.Serializable 88 | 89 | #保持 Serializable 不被混淆并且enum 类也不被混淆 90 | -keepclassmembers class * implements java.io.Serializable { 91 | static final long serialVersionUID; 92 | private static final java.io.ObjectStreamField[] serialPersistentFields; 93 | !static !transient ; 94 | !private ; 95 | !private ; 96 | private void writeObject(java.io.ObjectOutputStream); 97 | private void readObject(java.io.ObjectInputStream); 98 | java.lang.Object writeReplace(); 99 | java.lang.Object readResolve(); 100 | } 101 | 102 | #保持枚举 enum 类不被混淆 如果混淆报错,建议直接使用上面的 -keepclassmembers class * implements java.io.Serializable即可 103 | -keepclassmembers enum * { 104 | public static **[] values(); 105 | public static ** valueOf(java.lang.String); 106 | } 107 | 108 | #不混淆资源类 109 | -keepclassmembers class **.R$* { 110 | public static ; 111 | } 112 | 113 | 114 | 115 | #nuwa 116 | -keep class cn.jiajixin.nuwa.** { *; } 117 | 118 | #design 119 | -keep class android.support.design.widget.** { *; } 120 | -keep interface android.support.design.widget.** { *; } 121 | -dontwarn android.support.design.** 122 | 123 | #glide 124 | -keep public class * implements com.bumptech.glide.module.GlideModule 125 | -keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** { 126 | **[] $VALUES; 127 | public *; 128 | } 129 | 130 | 131 | -keep class fr.castorflex.android.circularprogressbar.** { *; } 132 | -dontwarn fr.castorflex.android.circularprogressbar.** 133 | 134 | #okio 135 | -dontwarn okio.** 136 | -keep class okio.** { *; } 137 | -keep interface okio.** { *; } 138 | 139 | #okhttp3 140 | -dontwarn okhttp3.** 141 | -keep class okhttp3.** { *; } 142 | -keep interface okhttp3.** { *; } 143 | 144 | #androidannotations 145 | -keep class androidannotations.** { *; } 146 | -dontwarn org.androidannotations.api.rest** 147 | 148 | #retrofit2 149 | -dontwarn retrofit2.** 150 | -keep class retrofit2.** { *; } 151 | -keepattributes Signature 152 | -keepattributes Exceptions 153 | 154 | #rxjava 155 | -keep class rx.android.** { *; } 156 | -dontwarn rx.android.** 157 | -dontwarn sun.misc.** 158 | -keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* { 159 | long producerIndex; 160 | long consumerIndex; 161 | } 162 | -keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef { 163 | rx.internal.util.atomic.LinkedQueueNode producerNode; 164 | } 165 | -keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef { 166 | rx.internal.util.atomic.LinkedQueueNode consumerNode; 167 | } 168 | 169 | 170 | ## gson 171 | -keepattributes Signature 172 | -keep class sun.misc.Unsafe { *; } 173 | -keep class com.google.gson.examples.android.model.** { *; } 174 | 175 | -------------------------------------------------------------------------------- /app/src/androidTest/java/cn/easydone/androidfluxpractice/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package cn.easydone.androidfluxpractice; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/java/cn/easydone/androidfluxpractice/RxBus.java: -------------------------------------------------------------------------------- 1 | package cn.easydone.androidfluxpractice; 2 | 3 | 4 | import android.support.annotation.NonNull; 5 | 6 | import rx.Observable; 7 | import rx.Scheduler; 8 | import rx.Subscription; 9 | import rx.functions.Action1; 10 | import rx.subjects.PublishSubject; 11 | import rx.subjects.SerializedSubject; 12 | 13 | /** 14 | * Created by Android Studio 15 | * User: liangzhitao 16 | * Date: 16/3/24 17 | * Time: 上午10:36 18 | */ 19 | 20 | public class RxBus { 21 | 22 | private static volatile RxBus instance; 23 | private final SerializedSubject subject; 24 | 25 | private RxBus() { 26 | subject = new SerializedSubject<>(PublishSubject.create()); 27 | } 28 | 29 | public static RxBus getInstance() { 30 | if (instance == null) { 31 | synchronized (RxBus.class) { 32 | if (instance == null) { 33 | instance = new RxBus(); 34 | } 35 | } 36 | } 37 | return instance; 38 | } 39 | 40 | public void post(Object object) { 41 | subject.onNext(object); 42 | } 43 | 44 | @NonNull 45 | public Observable toObservable(final Class type) { 46 | return subject.ofType(type); 47 | } 48 | 49 | public boolean hasObservers() { 50 | return subject.hasObservers(); 51 | } 52 | 53 | public Subscription toSubscription(Class type, Action1 action1, Scheduler scheduler) { 54 | return RxBus.getInstance() 55 | .toObservable(type) 56 | .observeOn(scheduler) 57 | .subscribe(action1); 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /app/src/main/java/cn/easydone/androidfluxpractice/action/Action.java: -------------------------------------------------------------------------------- 1 | package cn.easydone.androidfluxpractice.action; 2 | 3 | /** 4 | * Created by Android Studio 5 | * User: liangzhitao 6 | * Date: 2016-02-24 7 | * Time: 12:12 8 | */ 9 | public class Action { 10 | private String type; 11 | private Object data; 12 | 13 | public Action(String type, Object data) { 14 | this.type = type; 15 | this.data = data; 16 | } 17 | 18 | public String getType() { 19 | return type; 20 | } 21 | 22 | public void setType(String type) { 23 | this.type = type; 24 | } 25 | 26 | public Object getData() { 27 | return data; 28 | } 29 | 30 | public void setData(Object data) { 31 | this.data = data; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/cn/easydone/androidfluxpractice/action/ActionCreator.java: -------------------------------------------------------------------------------- 1 | package cn.easydone.androidfluxpractice.action; 2 | 3 | import cn.easydone.androidfluxpractice.dispatcher.Dispatcher; 4 | 5 | /** 6 | * Created by Android Studio 7 | * User: liangzhitao 8 | * Date: 2016-02-24 9 | * Time: 12:14 10 | */ 11 | public class ActionCreator { 12 | protected Dispatcher dispatcher; 13 | 14 | protected ActionCreator(Dispatcher dispatcher) { 15 | this.dispatcher = dispatcher; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/cn/easydone/androidfluxpractice/action/UserAction.java: -------------------------------------------------------------------------------- 1 | package cn.easydone.androidfluxpractice.action; 2 | 3 | /** 4 | * Created by Android Studio 5 | * User: liangzhitao 6 | * Date: 2016-02-24 7 | * Time: 12:34 8 | */ 9 | public class UserAction extends Action { 10 | 11 | public static final String LOADING_START = "loadingStart"; 12 | public static final String INIT_RECYCLER_VIEW = "initRecyclerView"; 13 | public static final String FETCH_DATA_ERROR = "fetchDataError"; 14 | 15 | public UserAction(String type, Object data) { 16 | super(type, data); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/cn/easydone/androidfluxpractice/action/UserActionCreator.java: -------------------------------------------------------------------------------- 1 | package cn.easydone.androidfluxpractice.action; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | import cn.easydone.androidfluxpractice.bean.GitHubUser; 9 | import cn.easydone.androidfluxpractice.bean.User; 10 | import cn.easydone.androidfluxpractice.dispatcher.Dispatcher; 11 | import cn.easydone.androidfluxpractice.request.GitHubApiUtils; 12 | import rx.Observable; 13 | import rx.android.schedulers.AndroidSchedulers; 14 | import rx.functions.Action0; 15 | import rx.functions.Action1; 16 | import rx.functions.Func1; 17 | import rx.schedulers.Schedulers; 18 | 19 | /** 20 | * Created by Android Studio 21 | * User: liangzhitao 22 | * Date: 2016-02-24 23 | * Time: 12:45 24 | */ 25 | public class UserActionCreator extends ActionCreator { 26 | 27 | private static UserActionCreator userActionCreator; 28 | 29 | public static UserActionCreator getInstance(Dispatcher dispatcher) { 30 | if (userActionCreator == null) { 31 | synchronized (UserActionCreator.class) { 32 | if (userActionCreator == null) { 33 | userActionCreator = new UserActionCreator(dispatcher); 34 | } 35 | } 36 | } 37 | return userActionCreator; 38 | } 39 | 40 | protected UserActionCreator(Dispatcher dispatcher) { 41 | super(dispatcher); 42 | } 43 | 44 | 45 | public void fetchData(List users) { 46 | Observable.merge(getObservables(users)) 47 | .doOnSubscribe(new Action0() { 48 | @Override 49 | public void call() { 50 | dispatcher.dispatch(new UserAction(UserAction.LOADING_START, null)); 51 | } 52 | }) 53 | .buffer(users.size()) 54 | .map(new Func1, List>() { 55 | @Override 56 | public List call(List gitHubUsers) { 57 | return getUserList(gitHubUsers); 58 | } 59 | }) 60 | .subscribeOn(Schedulers.newThread()) 61 | .observeOn(AndroidSchedulers.mainThread()) 62 | .subscribe(new Action1>() { 63 | @Override 64 | public void call(List users) { 65 | dispatcher.dispatch(new UserAction(UserAction.INIT_RECYCLER_VIEW, users)); 66 | } 67 | }, new Action1() { 68 | @Override 69 | public void call(Throwable throwable) { 70 | dispatcher.dispatch(new UserAction(UserAction.FETCH_DATA_ERROR, throwable)); 71 | } 72 | }); 73 | } 74 | 75 | @NonNull 76 | private List getUserList(List gitHubUsers) { 77 | List userList = new ArrayList<>(); 78 | for (GitHubUser gitHubUser : gitHubUsers) { 79 | userList.add(getUser(gitHubUser)); 80 | } 81 | return userList; 82 | } 83 | 84 | @NonNull 85 | private List> getObservables(List users) { 86 | List> observableList = new ArrayList<>(); 87 | for (String user : users) { 88 | Observable observable = GitHubApiUtils.getInstance().getGitHubApi().user(user); 89 | observableList.add(observable); 90 | } 91 | return observableList; 92 | } 93 | 94 | @NonNull 95 | private User getUser(GitHubUser gitHubUser) { 96 | User user = new User(); 97 | user.setUrl(gitHubUser.url); 98 | user.setAvatarUrl(gitHubUser.avatarUrl); 99 | user.setName(gitHubUser.name); 100 | user.setBlog(gitHubUser.blog); 101 | user.setEmail(gitHubUser.email); 102 | user.setFollowers(gitHubUser.followers); 103 | user.setFollowing(gitHubUser.following); 104 | user.setPublicGists(gitHubUser.publicGists); 105 | user.setPublicRepos(gitHubUser.publicRepos); 106 | return user; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /app/src/main/java/cn/easydone/androidfluxpractice/bean/GitHubUser.java: -------------------------------------------------------------------------------- 1 | package cn.easydone.androidfluxpractice.bean; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * Created by Android Studio 7 | * User: Ailurus(ailurus@foxmail.com) 8 | * Date: 2016-01-07 9 | * Time: 08:58 10 | */ 11 | public class GitHubUser { 12 | public String name; 13 | public String email; 14 | @SerializedName("public_repos") 15 | public int publicRepos; 16 | @SerializedName("public_gists") 17 | public int publicGists; 18 | public String login; 19 | public int id; 20 | @SerializedName("avatar_url") 21 | public String avatarUrl; 22 | @SerializedName("gravatar_id") 23 | public String gravatarId; 24 | public String url; 25 | @SerializedName("html_url") 26 | public String htmlUrl; 27 | @SerializedName("followers_url") 28 | public String followersUrl; 29 | @SerializedName("following_url") 30 | public String followingUrl; 31 | @SerializedName("gists_url") 32 | public String gistsUrl; 33 | @SerializedName("starred_url") 34 | public String starredUrl; 35 | @SerializedName("subscriptions_url") 36 | public String subscriptionsUrl; 37 | @SerializedName("organizations_url") 38 | public String organizationsUrl; 39 | @SerializedName("repos_url") 40 | public String reposUrl; 41 | @SerializedName("events_url") 42 | public String eventsUrl; 43 | @SerializedName("received_events_url") 44 | public String receivedEventsUrl; 45 | public String type; 46 | @SerializedName("site_admin") 47 | public boolean siteAdmin; 48 | public String company; 49 | public String blog; 50 | public String location; 51 | public boolean hireable; 52 | public String bio; 53 | public int followers; 54 | public int following; 55 | @SerializedName("created_at") 56 | public String createdAt; 57 | @SerializedName("updated_at") 58 | public String updatedAt; 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/cn/easydone/androidfluxpractice/bean/User.java: -------------------------------------------------------------------------------- 1 | package cn.easydone.androidfluxpractice.bean; 2 | 3 | 4 | /** 5 | * Created by Android Studio 6 | * User: Ailurus(ailurus@foxmail.com) 7 | * Date: 2016-01-09 8 | * Time: 11:37 9 | */ 10 | public class User { 11 | 12 | private String url; 13 | private String name; 14 | private String avatarUrl; 15 | private String blog; 16 | private String email; 17 | private int followers; 18 | private int following; 19 | private int publicRepos; 20 | private int publicGists; 21 | 22 | public String getUrl() { 23 | return url; 24 | } 25 | 26 | public void setUrl(String url) { 27 | this.url = url; 28 | } 29 | 30 | public String getName() { 31 | return name; 32 | } 33 | 34 | public void setName(String name) { 35 | this.name = name; 36 | } 37 | 38 | public String getAvatarUrl() { 39 | return avatarUrl; 40 | } 41 | 42 | public void setAvatarUrl(String avatarUrl) { 43 | this.avatarUrl = avatarUrl; 44 | } 45 | 46 | public String getBlog() { 47 | return blog; 48 | } 49 | 50 | public void setBlog(String blog) { 51 | this.blog = blog; 52 | } 53 | 54 | public String getEmail() { 55 | return email; 56 | } 57 | 58 | public void setEmail(String email) { 59 | this.email = email; 60 | } 61 | 62 | public int getFollowers() { 63 | return followers; 64 | } 65 | 66 | public void setFollowers(int followers) { 67 | this.followers = followers; 68 | } 69 | 70 | public int getFollowing() { 71 | return following; 72 | } 73 | 74 | public void setFollowing(int following) { 75 | this.following = following; 76 | } 77 | 78 | public int getPublicRepos() { 79 | return publicRepos; 80 | } 81 | 82 | public void setPublicRepos(int publicRepos) { 83 | this.publicRepos = publicRepos; 84 | } 85 | 86 | public int getPublicGists() { 87 | return publicGists; 88 | } 89 | 90 | public void setPublicGists(int publicGists) { 91 | this.publicGists = publicGists; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /app/src/main/java/cn/easydone/androidfluxpractice/dispatcher/Dispatcher.java: -------------------------------------------------------------------------------- 1 | package cn.easydone.androidfluxpractice.dispatcher; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import cn.easydone.androidfluxpractice.action.Action; 7 | import cn.easydone.androidfluxpractice.store.Store; 8 | 9 | /** 10 | * Created by Android Studio 11 | * User: liangzhitao 12 | * Date: 2016-02-24 13 | * Time: 12:14 14 | */ 15 | public class Dispatcher { 16 | 17 | private static Dispatcher instance; 18 | private List stores; 19 | 20 | private Dispatcher() { 21 | stores = new ArrayList<>(); 22 | } 23 | 24 | public static Dispatcher getInstance() { 25 | if (instance == null) { 26 | synchronized (Dispatcher.class) { 27 | instance = new Dispatcher(); 28 | } 29 | } 30 | return instance; 31 | } 32 | 33 | public void register(Store store) { 34 | stores.add(store); 35 | } 36 | 37 | public void unRegister(Store store) { 38 | stores.remove(store); 39 | } 40 | 41 | public void dispatch(Action action) { 42 | post(action); 43 | } 44 | 45 | private void post(final Action action) { 46 | for (Store store : stores) { 47 | store.onAction(action); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/cn/easydone/androidfluxpractice/request/GitHubApi.java: -------------------------------------------------------------------------------- 1 | package cn.easydone.androidfluxpractice.request; 2 | 3 | import cn.easydone.androidfluxpractice.bean.GitHubUser; 4 | import retrofit2.http.GET; 5 | import retrofit2.http.Path; 6 | import rx.Observable; 7 | 8 | /** 9 | * Created by Android Studio 10 | * User: Ailurus(ailurus@foxmail.com) 11 | * Date: 2016-01-07 12 | * Time: 08:32 13 | */ 14 | public interface GitHubApi { 15 | 16 | @GET("users/{user}") 17 | Observable user( 18 | @Path("user") String user); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/cn/easydone/androidfluxpractice/request/GitHubApiUtils.java: -------------------------------------------------------------------------------- 1 | package cn.easydone.androidfluxpractice.request; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | 6 | import java.io.IOException; 7 | 8 | import okhttp3.Interceptor; 9 | import okhttp3.OkHttpClient; 10 | import okhttp3.Request; 11 | import okhttp3.Response; 12 | import okhttp3.logging.HttpLoggingInterceptor; 13 | import retrofit2.Retrofit; 14 | import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; 15 | import retrofit2.converter.gson.GsonConverterFactory; 16 | 17 | /** 18 | * Created by Android Studio 19 | * User: Ailurus(ailurus@foxmail.com) 20 | * Date: 2016-01-07 21 | * Time: 08:59 22 | */ 23 | public class GitHubApiUtils { 24 | 25 | private static GitHubApiUtils mInstance; 26 | private GitHubApi gitHubApi; 27 | 28 | private GitHubApiUtils() { 29 | /* JSON 解析 */ 30 | Gson gson = new GsonBuilder() 31 | .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") 32 | .create(); 33 | 34 | Retrofit retrofit = new Retrofit.Builder() 35 | .baseUrl("https://api.github.com/") 36 | .addConverterFactory(GsonConverterFactory.create(gson)) 37 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 38 | .client(okHttpClient()) 39 | .build(); 40 | 41 | gitHubApi = retrofit.create(GitHubApi.class); 42 | } 43 | 44 | /* 单例 */ 45 | public static GitHubApiUtils getInstance() { 46 | if (mInstance == null) { 47 | synchronized (GitHubApiUtils.class) { 48 | if (mInstance == null) { 49 | mInstance = new GitHubApiUtils(); 50 | } 51 | } 52 | } 53 | return mInstance; 54 | } 55 | 56 | public GitHubApi getGitHubApi() { 57 | return gitHubApi; 58 | } 59 | 60 | private OkHttpClient okHttpClient() { 61 | 62 | HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); 63 | logging.setLevel(HttpLoggingInterceptor.Level.HEADERS).setLevel(HttpLoggingInterceptor.Level.BODY); 64 | 65 | OkHttpClient.Builder builder = new OkHttpClient.Builder().addInterceptor(logging).addInterceptor(headerInterceptor); 66 | 67 | return builder.build(); 68 | } 69 | 70 | /* header */ 71 | Interceptor headerInterceptor = new Interceptor() { 72 | @Override 73 | public Response intercept(Chain chain) throws IOException { 74 | Request original = chain.request(); 75 | 76 | Request request = original.newBuilder() 77 | .addHeader("User-Agent", "Test") 78 | .addHeader("Accept", "application/vnd.github.v3+json") 79 | // .addHeader("Authorization", "") 80 | .method(original.method(), original.body()) 81 | .build(); 82 | 83 | return chain.proceed(request); 84 | } 85 | }; 86 | } 87 | -------------------------------------------------------------------------------- /app/src/main/java/cn/easydone/androidfluxpractice/store/Store.java: -------------------------------------------------------------------------------- 1 | package cn.easydone.androidfluxpractice.store; 2 | 3 | import cn.easydone.androidfluxpractice.RxBus; 4 | 5 | /** 6 | * Created by Android Studio 7 | * User: liangzhitao 8 | * Date: 2016-02-24 9 | * Time: 12:26 10 | */ 11 | public abstract class Store { 12 | private RxBus rxBus; 13 | 14 | protected Store() { 15 | rxBus = RxBus.getInstance(); 16 | } 17 | 18 | //处理接收到不同的事件 19 | public abstract void onAction(T action); 20 | 21 | protected abstract StoreChangeEvent changeEvent(); 22 | 23 | //发送更新UI的事件给View 24 | protected void post() { 25 | StoreChangeEvent storeChangeEvent = changeEvent(); 26 | if (storeChangeEvent != null && rxBus.hasObservers()) { 27 | rxBus.post(storeChangeEvent); 28 | } 29 | } 30 | 31 | public class StoreChangeEvent {} 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/cn/easydone/androidfluxpractice/store/UserStore.java: -------------------------------------------------------------------------------- 1 | package cn.easydone.androidfluxpractice.store; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import cn.easydone.androidfluxpractice.action.UserAction; 7 | import cn.easydone.androidfluxpractice.bean.User; 8 | 9 | /** 10 | * Created by Android Studio 11 | * User: liangzhitao 12 | * Date: 2016-02-24 13 | * Time: 13:18 14 | */ 15 | public class UserStore extends Store { 16 | 17 | private static UserStore userStore; 18 | private List userList; 19 | private Throwable throwable; 20 | private StoreChangeEvent changeEvent; 21 | 22 | public static UserStore getInstance() { 23 | if (userStore == null) { 24 | synchronized (UserStore.class) { 25 | } 26 | userStore = new UserStore(); 27 | } 28 | return userStore; 29 | } 30 | 31 | protected UserStore() { 32 | userList = new ArrayList<>(); 33 | } 34 | 35 | public List getUserList() { 36 | return userList; 37 | } 38 | 39 | public Throwable getThrowable() { 40 | return throwable; 41 | } 42 | 43 | @Override 44 | public void onAction(final UserAction action) { 45 | switch (action.getType()) { 46 | case UserAction.LOADING_START: 47 | changeEvent = new LoadingStartChangeEvent(); 48 | post(); 49 | break; 50 | case UserAction.INIT_RECYCLER_VIEW: 51 | changeEvent = new InitRecyclerViewChangeEvent(); 52 | userList = (List) action.getData(); 53 | post(); 54 | break; 55 | case UserAction.FETCH_DATA_ERROR: 56 | changeEvent = new ErrorChangeEvent(); 57 | throwable = (Throwable) action.getData(); 58 | post(); 59 | break; 60 | } 61 | } 62 | 63 | @Override 64 | protected StoreChangeEvent changeEvent() { 65 | return changeEvent; 66 | } 67 | 68 | public class LoadingStartChangeEvent extends StoreChangeEvent {} 69 | 70 | public class InitRecyclerViewChangeEvent extends StoreChangeEvent {} 71 | 72 | public class ErrorChangeEvent extends StoreChangeEvent {} 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/cn/easydone/androidfluxpractice/ui/MainActivity.java: -------------------------------------------------------------------------------- 1 | package cn.easydone.androidfluxpractice.ui; 2 | 3 | import android.os.Bundle; 4 | import android.support.design.widget.FloatingActionButton; 5 | import android.support.design.widget.Snackbar; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.support.v7.widget.LinearLayoutManager; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.support.v7.widget.Toolbar; 10 | import android.view.View; 11 | import android.widget.ProgressBar; 12 | import android.widget.Toast; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | import cn.easydone.androidfluxpractice.R; 18 | import cn.easydone.androidfluxpractice.RxBus; 19 | import cn.easydone.androidfluxpractice.action.UserActionCreator; 20 | import cn.easydone.androidfluxpractice.bean.User; 21 | import cn.easydone.androidfluxpractice.dispatcher.Dispatcher; 22 | import cn.easydone.androidfluxpractice.store.UserStore; 23 | import rx.Subscription; 24 | import rx.android.schedulers.AndroidSchedulers; 25 | import rx.functions.Action1; 26 | import rx.subscriptions.CompositeSubscription; 27 | 28 | public class MainActivity extends AppCompatActivity { 29 | 30 | private ProgressBar progressBar; 31 | private RecyclerView recyclerView; 32 | 33 | private UserActionCreator userActionCreator; 34 | private UserStore userStore; 35 | private UserAdapter userAdapter; 36 | private Dispatcher dispatcher; 37 | private List userList; 38 | private CompositeSubscription subscription; 39 | private List users; 40 | 41 | @Override 42 | protected void onCreate(Bundle savedInstanceState) { 43 | super.onCreate(savedInstanceState); 44 | setContentView(R.layout.activity_main); 45 | initDependencies(); 46 | setupView(); 47 | } 48 | 49 | private void initDependencies() { 50 | userStore = UserStore.getInstance(); 51 | subscription = new CompositeSubscription(); 52 | dispatcher = Dispatcher.getInstance(); 53 | dispatcher.register(userStore); 54 | userActionCreator = UserActionCreator.getInstance(dispatcher); 55 | } 56 | 57 | private void setupView() { 58 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 59 | setSupportActionBar(toolbar); 60 | final FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); 61 | assert fab != null; 62 | fab.setOnClickListener(new View.OnClickListener() { 63 | @Override 64 | public void onClick(View v) { 65 | Snackbar.make(fab, "This is fab", Snackbar.LENGTH_SHORT).show(); 66 | userActionCreator.fetchData(users); 67 | } 68 | }); 69 | recyclerView = (RecyclerView) findViewById(R.id.recycler_view); 70 | progressBar = (ProgressBar) findViewById(R.id.progress_bar); 71 | LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this); 72 | linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); 73 | assert recyclerView != null; 74 | recyclerView.setLayoutManager(linearLayoutManager); 75 | 76 | userList = userStore.getUserList(); 77 | userAdapter = new UserAdapter(userList, MainActivity.this); 78 | recyclerView.setAdapter(userAdapter); 79 | 80 | users = new ArrayList<>(); 81 | users.add("liangzhitao"); 82 | users.add("AlanCheen"); 83 | users.add("yongjhih"); 84 | users.add("zzz40500"); 85 | users.add("greenrobot"); 86 | users.add("nimengbo"); 87 | 88 | userActionCreator.fetchData(users); 89 | 90 | onEvent(); 91 | } 92 | 93 | private void onEvent() { 94 | subscription.add(toSubscription(UserStore.LoadingStartChangeEvent.class, 95 | new Action1() { 96 | @Override 97 | public void call(UserStore.LoadingStartChangeEvent changeEvent) { 98 | if (changeEvent != null) { 99 | recyclerView.setVisibility(View.GONE); 100 | progressBar.setVisibility(View.VISIBLE); 101 | } 102 | } 103 | })); 104 | 105 | subscription.add(toSubscription(UserStore.InitRecyclerViewChangeEvent.class, 106 | new Action1() { 107 | @Override 108 | public void call(UserStore.InitRecyclerViewChangeEvent changeEvent) { 109 | recyclerView.setVisibility(View.VISIBLE); 110 | progressBar.setVisibility(View.GONE); 111 | if (changeEvent != null) { 112 | userList = userStore.getUserList(); 113 | userAdapter.refreshUi(userList); 114 | } 115 | } 116 | })); 117 | 118 | subscription.add(toSubscription(UserStore.ErrorChangeEvent.class, 119 | new Action1() { 120 | @Override 121 | public void call(UserStore.ErrorChangeEvent changeEvent) { 122 | progressBar.setVisibility(View.GONE); 123 | if (changeEvent != null) { 124 | Throwable throwable = userStore.getThrowable(); 125 | Toast.makeText(MainActivity.this, throwable.getLocalizedMessage(), Toast.LENGTH_SHORT).show(); 126 | } 127 | } 128 | })); 129 | } 130 | 131 | private Subscription toSubscription(Class type, Action1 action1) { 132 | return RxBus.getInstance() 133 | .toObservable(type) 134 | .observeOn(AndroidSchedulers.mainThread()) 135 | .subscribe(action1); 136 | } 137 | 138 | @Override 139 | protected void onDestroy() { 140 | super.onDestroy(); 141 | dispatcher.unRegister(userStore); 142 | subscription.unsubscribe(); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /app/src/main/java/cn/easydone/androidfluxpractice/ui/UserAdapter.java: -------------------------------------------------------------------------------- 1 | package cn.easydone.androidfluxpractice.ui; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.ImageView; 9 | import android.widget.TextView; 10 | 11 | import com.bumptech.glide.Glide; 12 | import com.bumptech.glide.load.engine.DiskCacheStrategy; 13 | 14 | import java.util.List; 15 | 16 | import cn.easydone.androidfluxpractice.R; 17 | import cn.easydone.androidfluxpractice.bean.User; 18 | 19 | /** 20 | * Created by Android Studio 21 | * User: Ailurus(ailurus@foxmail.com) 22 | * Date: 2016-01-09 23 | * Time: 10:14 24 | */ 25 | public class UserAdapter extends RecyclerView.Adapter { 26 | 27 | private List users; 28 | private Context context; 29 | 30 | public UserAdapter(List users, Context context) { 31 | this.users = users; 32 | this.context = context; 33 | } 34 | 35 | @Override 36 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 37 | View view = LayoutInflater.from(parent.getContext()) 38 | .inflate(R.layout.item_github_user, parent, false); 39 | return new ViewHolder(view); 40 | } 41 | 42 | @Override 43 | public void onBindViewHolder(ViewHolder holder, int position) { 44 | final User user = users.get(position); 45 | Glide.with(context) 46 | .load(user.getAvatarUrl()) 47 | .crossFade() 48 | .diskCacheStrategy(DiskCacheStrategy.ALL) 49 | .centerCrop() 50 | .into(holder.ivAvatar); 51 | holder.ivAvatar.setTag(R.id.iv_user_avatar, position); 52 | holder.tvBlog.setText(user.getBlog()); 53 | holder.tvEmail.setText(user.getEmail()); 54 | holder.tvUserName.setText(user.getName()); 55 | holder.tvFollowerNum.setText(context.getString(R.string.follower_num, user.getFollowers())); 56 | holder.tvFollowingNum.setText(context.getString(R.string.following_num, user.getFollowing())); 57 | holder.tvPubReposNum.setText(context.getString(R.string.pub_repos_num, user.getPublicRepos())); 58 | holder.tvPubGistsNum.setText(context.getString(R.string.pub_gists_num, user.getPublicGists())); 59 | } 60 | 61 | @Override 62 | public int getItemCount() { 63 | return users.size(); 64 | } 65 | 66 | public void refreshUi(List users) { 67 | this.users = users; 68 | notifyDataSetChanged(); 69 | } 70 | 71 | public static class ViewHolder extends RecyclerView.ViewHolder { 72 | 73 | private final ImageView ivAvatar; 74 | private final TextView tvUserName; 75 | private final TextView tvBlog; 76 | private final TextView tvEmail; 77 | private final TextView tvPubReposNum; 78 | private final TextView tvFollowerNum; 79 | private final TextView tvPubGistsNum; 80 | private final TextView tvFollowingNum; 81 | 82 | public ViewHolder(View itemView) { 83 | super(itemView); 84 | ivAvatar = (ImageView) itemView.findViewById(R.id.iv_user_avatar); 85 | tvUserName = (TextView) itemView.findViewById(R.id.tv_user_name); 86 | tvBlog = (TextView) itemView.findViewById(R.id.tv_blog); 87 | tvEmail = (TextView) itemView.findViewById(R.id.tv_email); 88 | tvPubReposNum = (TextView) itemView.findViewById(R.id.tv_pub_repos_num); 89 | tvFollowerNum = (TextView) itemView.findViewById(R.id.tv_follower_num); 90 | tvPubGistsNum = (TextView) itemView.findViewById(R.id.tv_pub_gists_num); 91 | tvFollowingNum = (TextView) itemView.findViewById(R.id.tv_following_num); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 19 | 20 | 21 | 22 | 29 | 30 | 37 | 38 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_github_user.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 19 | 20 | 26 | 27 | 33 | 34 | 39 | 40 | 44 | 45 | 49 | 50 | 54 | 55 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cxyxlxdm/AndroidFluxPractice/20648cca1973407ef84d52f4b79b1674613b3c4d/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cxyxlxdm/AndroidFluxPractice/20648cca1973407ef84d52f4b79b1674613b3c4d/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cxyxlxdm/AndroidFluxPractice/20648cca1973407ef84d52f4b79b1674613b3c4d/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cxyxlxdm/AndroidFluxPractice/20648cca1973407ef84d52f4b79b1674613b3c4d/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cxyxlxdm/AndroidFluxPractice/20648cca1973407ef84d52f4b79b1674613b3c4d/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | 8 | @color/gplus_color_1 9 | @color/gplus_color_2 10 | @color/gplus_color_3 11 | @color/gplus_color_4 12 | 13 | 14 | #3e802f 15 | #f4b400 16 | #427fed 17 | #b23424 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 16dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | AndroidFluxPractice 3 | Settings 4 | %d\nGists 5 | %d\nFollowing 6 | %d\nFollowers 7 | http://www.easydone.cn 8 | woshiliangzhitao@gmail.com 9 | %d\nRepositories 10 | Setting 11 | Setting item 1 12 | Setting item 2 13 | Nav item3 14 | Nav item2 15 | Nav item1 16 | Name 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 16 | 26 | 27 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/test/java/cn/easydone/androidfluxpractice/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package cn.easydone.androidfluxpractice; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertEquals; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | } 5 | dependencies { 6 | classpath 'com.android.tools.build:gradle:2.1.0-rc1' 7 | } 8 | } 9 | 10 | allprojects { 11 | repositories { 12 | jcenter() 13 | } 14 | } 15 | 16 | task clean(type: Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cxyxlxdm/AndroidFluxPractice/20648cca1973407ef84d52f4b79b1674613b3c4d/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Apr 05 16:08:09 CST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /当EventBus遇上RxJava.md: -------------------------------------------------------------------------------- 1 | ##引言 2 | 接触过 `EventBus` 和 `RxJava` 的都知道,可以用 `RxJava` 来实现 `EventBus`,网上随便一搜,就可以拿得到代码。但是究竟为什么可以这么做?却没有类似的文章作进一步的深度解析。(本文假定读者都已经了解 `EventBus` 和 `RxJava` 是什么,可以做什么。) 3 | 4 | ```Java 5 | public class RxBus { 6 | 7 | private static volatile RxBus instance; 8 | private final SerializedSubject subject; 9 | 10 | private RxBus() { 11 | subject = new SerializedSubject<>(PublishSubject.create()); 12 | } 13 | 14 | public static RxBus getInstance() { 15 | if (instance == null) { 16 | synchronized (RxBus.class) { 17 | if (instance == null) { 18 | instance = new RxBus(); 19 | } 20 | } 21 | } 22 | return instance; 23 | } 24 | 25 | public void post(Object object) { 26 | subject.onNext(object); 27 | } 28 | 29 | private Observable toObservable(final Class type) { 30 | return subject.ofType(type); 31 | } 32 | 33 | public boolean hasObservers() { 34 | return subject.hasObservers(); 35 | } 36 | } 37 | ``` 38 | 可以看到,代码非常简练,当 `RxJava` 遇上 `EventBus` ,居然会如此神奇~那么问题来了,为什么这段代码就可以实现 `EventBus` 的功能? 39 | 40 | 要搞明白这几个问题,我们得弄清楚这些东西。 41 | 42 | 1. `EventBus` 是如何实现的? 43 | 2. `RxJava` 满足了实现 `EventBus` 的哪些条件? 44 | 3. 如何用 `RxJava` 去封装一个 `EventBus` ? 45 | 46 | ## `EventBus`工作流程 47 | 简单讲,事件总线,顾名思义,分为两个概念,一个事件,即 Event ,一个总线,即 Bus ,这是在整个 APP 里一种规范地传递事件的方式。作为独立于项目里各个模块的 Application 级别的存在,可以很好地用来程序的解耦。使用大致有四个步骤:注册→发送→接收→取消注册。具体的源码分析,可以参看 codeKK 上 [Trinea](http://www.trinea.cn/) 的 [EventBus源码解析](http://a.codekk.com/detail/Android/Trinea/EventBus%20%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90) 和 [kymjs张涛](http://www.kymjs.com/) 的 [EventBus源码研读](http://kymjs.com/column/resourcecode.html)。 48 | 49 | 更重要的一点,`RxBus` 的重点应该在 `Bus` 上,而不是 `RxJava` 上。用 `RxJava` 去实现 `EventBus` 的思想。因此,应该把分析 `EventBus` 作为一个重点。 50 | 51 | 我们来看看要实现一个 `EventBus` 需要满足什么条件。 52 | 53 | 1. 获取一个 `EventBus` 实例,可以用单例,也可以用 `Builder`; 54 | 2. 注册 `EventBus` 和取消注册 `EventBus`; 55 | 3. 发送和接收事件的方法。 56 | 57 | ##`RxJava` && `EventBus` 58 | 要实现 `EventBus` 需要满足的条件,在 `RxJava` 里是如何体现的呢? 59 | 60 | 首先我们需要明确的是,`EventBus` 里都有哪些角色:`Event`、`Subscriber`、`Publisher`,也就是说需要`Event`、`Observer`、`Observable`,Event 自不必说,在 `RxJava` 里既能充当`Observer`,又能充当`Observable`的对象就是 `Subject`,而 `Subject` 是线程非安全的,我们要构造一个线程安全的 `Subject` ,需要用到它的子类 `SerializedSubject`,而实际使用的时候,我们的观察者只需要订阅发生之后的,来自 `Observable` 的数据,因此还需要给 `SerializedSubject` 传入 `PublishSubject` 作为参数。 61 | 62 | 1. 获取 `Bus` 实例,一个单例即可,当然,`EventBus` 还提供了使用 `Builder` 创建实例的方法,可根据具体情况自行实现; 63 | 64 | ```Java 65 | private static volatile RxBus instance; 66 | private RxBus() { 67 | subject = new SerializedSubject<>(PublishSubject.create()); 68 | } 69 | 70 | public static RxBus getInstance() { 71 | if (instance == null) { 72 | synchronized (RxBus.class) { 73 | if (instance == null) { 74 | instance = new RxBus(); 75 | } 76 | } 77 | } 78 | return instance; 79 | } 80 | ``` 81 | 2. 注册和取消注册 `Bus`; 82 | 83 | `EventBus` 的注册过程,就是对接收某个事件的所有方法进行 `subscribe()` ,在 `subscribe()` 方法里拿到这些的方法,把这些方法存进 `subscriberMethods`(一个 List 集合)中,然后把事件的类名作为 key ,把 `subscriberMethods` 作为 value ,存进 `methodCache`(一个 HashMap 缓存)里,这样就不用每次都去反射了。这里需要注意一点,`EventBus` 里用 `methodCache` cache 下来的不是 `Observer` ,也不是 `Observable` ,而是 `Observable.subscribe(Observer)`,即 `Subscription` ,那么如果用 `RxJava` ,该怎么去实现这么个功能呢?在 RxJava 里有这样一个类 `CompositeSubscription` ,对应的是一个存储着 `Subscription` 的 `HashSet`,因此我们只需要将接收事件的方法 add 进一个 `CompositeSubscription` ,在生命周期结束的时候,再把 `CompositeSubscription` 取消订阅即可。 84 | 85 | 明确了上面的流程,对 RxJava 的封装就好办了。我们只需要获取 `Subscription` 即可。注意,这里跟 `EventBus` 是有区别的,`EventBus` 的封装,是通过反射,获取所有接收事件的方法,然后注册,当然,现在的 `EventBus` 版本里这些反射几乎对性能没有任何影响了。现在我们用 `RxJava` 是不是也要用反射再去获取所有的 `Subscription` 呢?当然不是,EventBus 的机制其实类似于广播,在接收事件的地方是没有方法调用的,因此需要反射。但是 `RxBus` 则提供了调用接收事件的方法,因此只需要在 `Activity` 或 `Fragment` 里 new 出来 `CompositeSubscription` 对象,然后在需要接收事件的地方,用 `CompositeSubscription` 对象去 add 进对应的 `Subscription` 就可以了(这一点在下面发送接收事件一节还会提到)。 86 | 87 | 对应的取消注册的过程就简单多了,在生命周期结束的地方,对 `CompositeSubscription` 取消注册即可,以避免内存泄露,而 `CompositeSubscription ` 的取消注册方法是可以自动取消 `HashSet` 里的所有 `Subscription` 的,因此无须对每个 `Subscription` 单独处理。 88 | 89 | 3. 发送和接收事件。 90 | 91 | `EventBus` 发送事件,就是 post 方法,在 `EventBus` 里有一个内部类 `PostingThreadState`, 通过 `postingState.eventQueue` 可以获取一个 List 集合,只要 `eventQueue` 不为空,就不断地从 `eventQueue` 里取出事件(当然,伴随有是否为主线程,是否正在发送等状态的判断),然后调用 `postSingleEvent` 方法,最后调 `postToSubscription` 把事件发出去,post 一个,就从 `eventQueue` 里 remove 一个,最终又来到了我们从刚接触 Android 就让人很头痛的 `Handler` ,这是一个叫 `HandlerPoster` 的类。说一千,道一万,对应的 `RxJava` 处理事件就是调方法 `onNext`。这样代码就有了。 92 | 93 | ```Java 94 | public void post(Object object) { 95 | subject.onNext(object); 96 | } 97 | ``` 98 | 99 | `EventBus` 接收事件需要通过 `onEvent` 开头的方法来遍历获取,第一次遍历会缓存,仅查找 `onEvent` 开头的方法,同时忽略一些特定 SDK 的方法,可以提高一些效率。在使用 `RxJava` 接收事件的时候,根据传递的事件类型(eventType)可以获取对应类型的 `Observable` ,那么问题就来了,在这里我们是不是要提供一个返回对应的 `Subscription` 的方法呢?其实是可以的!但是需要指定 `Scheduler` ,因为我们知道,接收事件处理事件是有可能在不同的线程里的,如果在这里我们就提供一个返回 `Subscription` 的方法,那后续的事件处理是在哪个线程呢?因此在这里就指定了 UI 线程或者异步线程,后面的具体的事件处理就可能会有问题。当然,我们也只可以在需要接收事件的地方,调用 `toObservable` 方法,然后指定线程。这也是相对于 `Otto` 的一个优势。 100 | 101 | 在 `RxBus` 里: 102 | 103 | ```Java 104 | public Observable toObservable(final Class type) { 105 | return subject.ofType(type); 106 | } 107 | ``` 108 | ```Java 109 | public Subscription toSubscription(Class type, Action1 action1, Scheduler scheduler) { 110 | return RxBus.getInstance() 111 | .toObservable(type) 112 | .observeOn(scheduler) 113 | .subscribe(action1); 114 | } 115 | ``` 116 | 117 | 在 `Activity` 或 `Fragment` 里再去获取 `Subscription` 。 118 | 119 | ```Java 120 | private Subscription toSubscription(Class type, Action1 action1) { 121 | return RxBus.getInstance() 122 | .toObservable(type) 123 | .observeOn(AndroidSchedulers.mainThread()) 124 | .subscribe(action1); 125 | } 126 | ``` 127 | 128 | 最后,将所有的 `Subscription` add 进 `CompositeSubscription` 就好了。最后,一定不要忘记对 `CompositeSubscription` 取消注册。 129 | 130 | 到这里,有关 `EventBus` 的内容,基本是完了,不过还有一点,`EventBus` 里是有一个 `StickyEvent` 的,什么意思呢,就是说一般流程是,我们先去订阅事件,然后被观察者再去发布事件,观察者去接收事件,但是如果是先发布了事件,再去订阅事件呢?这时候先于订阅事件之前发布的事件就会被丢弃,这时候 `StickyEvent` 就登场了。即便是先于订阅事件之前发布了事件,它已然可以接收一个最近被发布的事件,可以理解为它缓存了一个最近发布的事件,而与订阅状态无关。当然,只是一个!明确了这些,要用 `RxJava` 实现就非常简单了,在 `RxJava` 里有一个 `BehaviorSubject` 完美实现了这个功能,具体实现跟 `PublishSubject` 一模一样。这时候就会有人问了,如果我们想要接收到所有的(而不是一个)在订阅事件之前发布的事件,该怎么办呢?很遗憾,`EventBus` 是无法办到的,但是 `RxJava` 可以!将 `BehaviorSubject` 换成 `RelaySubject` 即可。不过说句题外话,这样的功能好鸡肋啊,感觉适用场景少之又少。我想这也是 `EventBus` 没有去实现这样的功能的原因吧。 131 | 132 | ------ 133 | 具体使用可以参照[AndroidFluxPractice](https://github.com/liangzhitao/AndroidFluxPractice),Sample 里将 EventBus 替换为了 RxBus ,完美地实现了一模一样的效果。 134 | 135 | 参考:[http://www.jianshu.com/p/ca090f6e2fe2/](http://www.jianshu.com/p/ca090f6e2fe2/) 136 | 137 | 138 | 139 | 140 | 141 | --------------------------------------------------------------------------------