Android中的RxJava

Android RxJava: 这是一份全面的 操作符 使用汇总 ,安卓开发基础面试题 近两年“程序员职业发展”话题,受到越来越多关注,做技术还是转管理?如何保持竞争力?是否要转换新的技术栈? 每个工作三五年的程序员,都正面临着类似的焦虑。 工作三五年,不能像刚入职场凭着一股子冲劲和能吃苦耐劳的精神赢得升职加薪,而转型管理,是很多技术人员的梦想。 那么,技术骨干朝着管理人才前进的过程中,应该如何培养其管理能力呢? compile ‘io.reactivex.rxjava2:rxjava:2.0.7’ // 注:RxJava2 与 RxJava1 不能共存,即依赖不能同时存在 } 3.1 创建. 阅读详情

原文链接

在第1,2,3篇中,我大概介绍了RxJava是怎么使用的。下面我会介绍如何在Android中使用RxJava.

RxAndroid

RxAndroid是RxJava的一个针对Android平台的扩展。它包含了一些能够简化Android开发的工具。

首先,AndroidSchedulers提供了针对Android的线程系统的调度器。需要在UI线程中运行某些代码?很简单,只需要使用AndroidSchedulers.mainThread():

retrofitService.getImage(url)
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(bitmap -> myImageView.setImageBitmap(bitmap));

如果你已经创建了自己的Handler,你可以使用HandlerThreadScheduler1将一个调度器链接到你的handler上。

接着要介绍的就是AndroidObservable,它提供了跟多的功能来配合Android的生命周期。bindActivity()和bindFragment()方法默认使用AndroidSchedulers.mainThread()来执行观察者代码,这两个方法会在Activity或者Fragment结束的时候通知被观察者停止发出新的消息。

AndroidObservable.bindActivity(this, retrofitService.getImage(url))
    .subscribeOn(Schedulers.io())
    .subscribe(bitmap -> myImageView.setImageBitmap(bitmap);
我自己也很喜欢AndroidObservable.fromBroadcast()方法,它允许你创建一个类似BroadcastReceiver的Observable对象。下面的例子展示了如何在网络变化的时候被通知到:
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
AndroidObservable.fromBroadcast(context, filter)
    .subscribe(intent -> handleConnectivityChange(intent));

最后要介绍的是ViewObservable,使用它可以给View添加了一些绑定。如果你想在每次点击view的时候都收到一个事件,可以使用ViewObservable.clicks(),或者你想监听TextView的内容变化,可以使用ViewObservable.text()。
ViewObservable.clicks(mCardNameEditText, false)
    .subscribe(view -> handleClick(view));

Retrofit

大名鼎鼎的Retrofit库内置了对RxJava的支持。通常调用发可以通过使用一个Callback对象来获取异步的结果:

@GET("/user/{id}/photo")
void getUserPhoto(@Path("id") int id, Callback<Photo> cb);

使用RxJava,你可以直接返回一个Observable对象。
@GET("/user/{id}/photo")
Observable<Photo> getUserPhoto(@Path("id") int id);

现在你可以随意使用Observable对象了。你不仅可以获取数据,还可以进行变换。 

Retrofit对Observable的支持使得它可以很简单的将多个REST请求结合起来。比如我们有一个请求是获取照片的,还有一个请求是获取元数据的,我们就可以将这两个请求并发的发出,并且等待两个结果都返回之后再做处理:

Observable.zip(
    service.getUserPhoto(id),
    service.getPhotoMetadata(id),
    (photo, metadata) -> createPhotoWithData(photo, metadata))
    .subscribe(photoWithData -> showPhoto(photoWithData));

在第二篇里我展示过一个类似的例子(使用flatMap())。这里我只是想展示以下使用RxJava+Retrofit可以多么简单地组合多个REST请求。

遗留代码,运行极慢的代码

Retrofit可以返回Observable对象,但是如果你使用的别的库并不支持这样怎么办?或者说一个内部的内码,你想把他们转换成Observable的?有什么简单的办法没?

绝大多数时候Observable.just() 和 Observable.from() 能够帮助你从遗留代码中创建 Observable 对象:

private Object oldMethod() { ... }

public Observable<Object> newMethod() {
    return Observable.just(oldMethod());
}

上面的例子中如果oldMethod()足够快是没有什么问题的,但是如果很慢呢?调用oldMethod()将会阻塞住他所在的线程。 

为了解决这个问题,可以参考我一直使用的方法–使用defer()来包装缓慢的代码:

private Object slowBlockingMethod() { ... }

public Observable<Object> newMethod() {
    return Observable.defer(() -> Observable.just(slowBlockingMethod()));
}

现在,newMethod()的调用不会阻塞了,除非你订阅返回的observable对象。

生命周期

我把最难的不分留在了最后。如何处理Activity的生命周期?主要就是两个问题: 
1.在configuration改变(比如转屏)之后继续之前的Subscription。

比如你使用Retrofit发出了一个REST请求,接着想在listview中展示结果。如果在网络请求的时候用户旋转了屏幕怎么办?你当然想继续刚才的请求,但是怎么搞?

2.Observable持有Context导致的内存泄露

这个问题是因为创建subscription的时候,以某种方式持有了context的引用,尤其是当你和view交互的时候,这太容易发生!如果Observable没有及时结束,内存占用就会越来越大。 
不幸的是,没有银弹来解决这两个问题,但是这里有一些指导方案你可以参考。

第一个问题的解决方案就是使用RxJava内置的缓存机制,这样你就可以对同一个Observable对象执行unsubscribe/resubscribe,却不用重复运行得到Observable的代码。cache() (或者 replay())会继续执行网络请求(甚至你调用了unsubscribe也不会停止)。这就是说你可以在Activity重新创建的时候从cache()的返回值中创建一个新的Observable对象。

Observable<Photo> request = service.getUserPhoto(id).cache();
Subscription sub = request.subscribe(photo -> handleUserPhoto(photo));

// ...When the Activity is being recreated...
sub.unsubscribe();

// ...Once the Activity is recreated...
request.subscribe(photo -> handleUserPhoto(photo));

注意,两次sub是使用的同一个缓存的请求。当然在哪里去存储请求的结果还是要你自己来做,和所有其他的生命周期相关的解决方案一延虎,必须在生命周期外的某个地方存储。(retained fragment或者单例等等)。

第二个问题的解决方案就是在生命周期的某个时刻取消订阅。一个很常见的模式就是使用CompositeSubscription来持有所有的Subscriptions,然后在onDestroy()或者onDestroyView()里取消所有的订阅。

private CompositeSubscription mCompositeSubscription
    = new CompositeSubscription();

private void doSomething() {
    mCompositeSubscription.add(
        AndroidObservable.bindActivity(this, Observable.just("Hello, World!"))
        .subscribe(s -> System.out.println(s)));
}

@Override
protected void onDestroy() {
    super.onDestroy();

    mCompositeSubscription.unsubscribe();
}

你可以在Activity/Fragment的基类里创建一个CompositeSubscription对象,在子类中使用它。

注意! 一旦你调用了 CompositeSubscription.unsubscribe(),这个CompositeSubscription对象就不可用了, 如果你还想使用CompositeSubscription,就必须在创建一个新的对象了。

两个问题的解决方案都需要添加额外的代码,如果谁有更好的方案,欢迎告诉我。

总结

RxJava还是一个很新的项目,RxAndroid更是。RxAndroid目前还在活跃开发中,也没有多少好的例子。我打赌一年之后我的一些建议就会被看做过时了。

深入浅出RxJava四-在Android中使用响应式编程




RxAndroid是RxJava的扩展, 优雅地处理异步请求. RxAndroid配合Lambda表达式, 精简处理回调, 使程序更具有可读性. Rx作为Android最优秀的开源库之一, 极大地提高生产力, 我们需要掌握. 本文由浅入深, 介绍一些常见的使用方法, 并附有源码.

更多: http://www.wangchenlong.org/

本文代码的GitHub下载地址.

要点包含:
(1) 链式表达式的使用方式.
(2) Lambda的应用.
(3) Rx处理网络请求.
(4) 线程自动管理, 防止内存泄露.
(5) RxBinding绑定控件的异步事件.

Rx


基础

当然, 从一个崭新的HelloWorld项目开始.

添加Gradle配置.

1
2
3
compile 'com.jakewharton:butterknife:7.0.1'
compile 'io.reactivex:rxandroid:1.1.0' // RxAndroid
compile 'io.reactivex:rxjava:1.1.0' // 推荐同时加载RxJava

RxAndroid是本文的核心依赖, 同时添加RxJava. 还有ButterKnife注解库.

Lambda表达式, 是写出优雅代码的关键, 参考.

1
2
3
4
5
6
7
8
9
10
11
12
plugins {
    id "me.tatarka.retrolambda" version "3.2.4"
}

android {
	...

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

Gradle 2.1+以上, 配置非常简单, 添加一个plugin和一个Java1.8兼容即可.

从主MainActivity跳转至SimpleActivity.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
 * 主Activity, 用于跳转各个模块.
 *
 * @author wangchenlong
 */
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    // 跳转简单的页面
    public void gotoSimpleModule(View view) {
        startActivity(new Intent(this, SimpleActivity.class));
    }

    // 跳转复杂的页面
    public void gotoMoreModule(View view) {
        startActivity(new Intent(this, MoreActivity.class));
    }

    // 跳转Lambda的页面
    public void gotoLambdaModule(View view) {
        startActivity(new Intent(this, LambdaActivity.class));
    }

    // 跳转网络的页面
    public void gotoNetworkModule(View view) {
        startActivity(new Intent(this, NetworkActivity.class));
    }

    // 跳转线程安全的页面
    public void gotoSafeModule(View view) {
        startActivity(new Intent(this, SafeActivity.class));
    }
}

在SimpleActivity中, 创建一个观察者, 收到字符串的返回.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    // 观察事件发生
    Observable.OnSubscribe mObservableAction = new Observable.OnSubscribe<String>() {
        @Override public void call(Subscriber<? super String> subscriber) {
            subscriber.onNext(sayMyName()); // 发送事件
            subscriber.onCompleted(); // 完成事件
        }
    };

...

    // 创建字符串
    private String sayMyName() {
        return "Hello, I am your friend, Spike!";
    }

创建两个订阅者, 使用字符串输出信息.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 订阅者, 接收字符串, 修改控件
Subscriber<String> mTextSubscriber = new Subscriber<String>() {
    @Override public void onCompleted() {

    }

    @Override public void onError(Throwable e) {

    }

    @Override public void onNext(String s) {
        mTvText.setText(s); // 设置文字
    }
};

// 订阅者, 接收字符串, 提示信息
Subscriber<String> mToastSubscriber = new Subscriber<String>() {
    @Override public void onCompleted() {

    }

    @Override public void onError(Throwable e) {

    }

    @Override public void onNext(String s) {
        Toast.makeText(SimpleActivity.this, s, Toast.LENGTH_SHORT).show();
    }
};

在页面中, 观察者接收信息, 发送至主线程AndroidSchedulers.mainThread(), 再传递给订阅者, 由订阅者最终处理消息. 接收信息可以是同步, 也可以是异步.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_simple);
    ButterKnife.bind(this);

    // 注册观察活动
    @SuppressWarnings("unchecked")
    Observable<String> observable = Observable.create(mObservableAction);

    // 分发订阅信息
    observable.observeOn(AndroidSchedulers.mainThread());
    observable.subscribe(mTextSubscriber);
    observable.subscribe(mToastSubscriber);
}

最基础的RxAndroid使用.
基础


更多

我们已经熟悉了初步的使用方式, 在接着学习一些其他方法, 如

just: 获取输入数据, 直接分发, 更加简洁, 省略其他回调.
from: 获取输入数组, 转变单个元素分发.
map: 映射, 对输入数据进行转换, 如大写.
flatMap: 增大, 本意就是增肥, 把输入数组映射多个值, 依次分发.
reduce: 简化, 正好相反, 把多个数组的值, 组合成一个数据.

来看看这个示例, 设置两个不同类型数组, 作为输入源, 根据不同情况分发数据.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/**
 * 更多的RxAndroid的使用方法.
 * <p>
 * Created by wangchenlong on 15/12/30.
 */
public class MoreActivity extends Activity {

    @Bind(R.id.simple_tv_text) TextView mTvText;

    final String[] mManyWords = {"Hello", "I", "am", "your", "friend", "Spike"};
    final List<String> mManyWordList = Arrays.asList(mManyWords);

    // Action类似订阅者, 设置TextView
    private Action1<String> mTextViewAction = new Action1<String>() {
        @Override public void call(String s) {
            mTvText.setText(s);
        }
    };

    // Action设置Toast
    private Action1<String> mToastAction = new Action1<String>() {
        @Override public void call(String s) {
            Toast.makeText(MoreActivity.this, s, Toast.LENGTH_SHORT).show();
        }
    };

    // 设置映射函数
    private Func1<List<String>, Observable<String>> mOneLetterFunc = new Func1<List<String>, Observable<String>>() {
        @Override public Observable<String> call(List<String> strings) {
            return Observable.from(strings); // 映射字符串
        }
    };

    // 设置大写字母
    private Func1<String, String> mUpperLetterFunc = new Func1<String, String>() {
        @Override public String call(String s) {
            return s.toUpperCase(); // 大小字母
        }
    };

    // 连接字符串
    private Func2<String, String, String> mMergeStringFunc = new Func2<String, String, String>() {
        @Override public String call(String s, String s2) {
            return String.format("%s %s", s, s2); // 空格连接字符串
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_simple);
        ButterKnife.bind(this);

        // 添加字符串, 省略Action的其他方法, 只使用一个onNext.
        Observable<String> obShow = Observable.just(sayMyName());

        // 先映射, 再设置TextView
        obShow.observeOn(AndroidSchedulers.mainThread())
                .map(mUpperLetterFunc).subscribe(mTextViewAction);

        // 单独显示数组中的每个元素
        Observable<String> obMap = Observable.from(mManyWords);

        // 映射之后分发
        obMap.observeOn(AndroidSchedulers.mainThread())
                .map(mUpperLetterFunc).subscribe(mToastAction);

        // 优化过的代码, 直接获取数组, 再分发, 再合并, 再显示toast, Toast顺次执行.
        Observable.just(mManyWordList)
                .observeOn(AndroidSchedulers.mainThread())
                .flatMap(mOneLetterFunc)
                .reduce(mMergeStringFunc)
                .subscribe(mToastAction);
    }

    // 创建字符串
    private String sayMyName() {
        return "Hello, I am your friend, Spike!";
    }
}

这次简化调用代码, 因为有时候我们对异常并不是很关心,
只要能catch异常即可, 因此流仅仅关注真正需要的部分.

输入字符串, 变换大写, 输出至控件中显示.

1
2
3
4
5
6
// 添加字符串, 省略Action的其他方法, 只使用一个onNext.
Observable<String> obShow = Observable.just(sayMyName());

// 先映射, 再设置TextView
obShow.observeOn(AndroidSchedulers.mainThread())
        .map(mUpperLetterFunc).subscribe(mTextViewAction);

just可以非常简单的获取任何数据, 分发时, 选择使用的线程.
map是对输入数据加工, 转换类型, 输入Func1, 准换大写字母.
Func1代表使用一个参数的函数, 前面是参数, 后面是返回值.
Action1代表最终动作, 因而不需要返回值, 并且一个参数.

输入数组, 单独分发数组中每一个元素, 转换大写, 输入Toast连续显示.

1
2
3
4
5
6
// 单独显示数组中的每个元素
Observable<String> obMap = Observable.from(mManyWords);

// 映射之后分发
obMap.observeOn(AndroidSchedulers.mainThread())
        .map(mUpperLetterFunc).subscribe(mToastAction);

from是读取数组中的值, 每次单独分发, 并分发多次, 其余类似.

输入数组, 映射为单独分发, 并组合到一起, 集中显示.

1
2
3
4
5
6
// 优化过的代码, 直接获取数组, 再分发, 再合并, 再显示toast, Toast顺次执行.
Observable.just(mManyWordList)
        .observeOn(AndroidSchedulers.mainThread())
        .flatMap(mOneLetterFunc)
        .reduce(mMergeStringFunc)
        .subscribe(mToastAction);

这次是使用just分发数组, 则分发数据就是数组, 并不是数组中的元素.
flatMap把数组转换为单独分发, Func1内部使用from拆分数组.
reduce把单独分发数据集中到一起, 再统一分发, 使用Func2.
最终使用Action1显示获得数据. 本次代码也更加简洁.

由此我们可以观察到, Rx的写法可以是多种多样, 合理的写法会更加优雅.

效果
效果

网络请求

Retrofit是网络请求库, 刚推出2.0版本. Rx的一个核心应用就是处理异步网络请求, 结合Retrofit, 会更加方便和简洁. 参考.

引入库

1
2
3
4
5
6
7
compile 'com.android.support:recyclerview-v7:23.1.1' // RecyclerView

compile 'com.squareup.retrofit:retrofit:2.0.0-beta2' // Retrofit网络处理
compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2' // Retrofit的rx解析库
compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2' // Retrofit的gson库

compile 'com.squareup.picasso:picasso:2.5.2' // Picasso网络图片加载

recyclerview和picasso为了显示. retrofit系列是网络请求.

主页使用一个简单的列表视图, 展示Github的用户信息.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
 * Rx的网络请求方式
 * <p>
 * Created by wangchenlong on 15/12/31.
 */
public class NetworkActivity extends Activity {

    @Bind(R.id.network_rv_list) RecyclerView mRvList; // 列表

    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_network);
        ButterKnife.bind(this);

        // 设置Layout管理器
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        mRvList.setLayoutManager(layoutManager);

        // 设置适配器
        UserListAdapter adapter = new UserListAdapter(this::gotoDetailPage);
        NetworkWrapper.getUsersInto(adapter);
        mRvList.setAdapter(adapter);
    }

    // 点击的回调
    public interface UserClickCallback {
        void onItemClicked(String name);
    }

    // 跳转到库详情页面
    private void gotoDetailPage(String name) {
        startActivity(NetworkDetailActivity.from(NetworkActivity.this, name));
    }
}

在列表中提供点击用户信息跳转至用户详情.
NetworkWrapper.getUsersInto(adapter) 请求网络, 设置适配器信息.

关键部分, 适配器, 其中包含ViewHolder类和数据类.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/**
 * 显示列表
 * <p>
 * Created by wangchenlong on 15/12/31.
 */
public class UserListAdapter extends RecyclerView.Adapter<UserListAdapter.UserViewHolder> {

    private List<GitHubUser> mUsers; // 用户名集合

    private NetworkActivity.UserClickCallback mCallback; // 用户点击项的回调

    public UserListAdapter(NetworkActivity.UserClickCallback callback) {
        mUsers = new ArrayList<>();
        mCallback = callback;
    }

    public void addUser(GitHubUser user) {
        mUsers.add(user);
        notifyItemInserted(mUsers.size() - 1); // 最后一位
    }

    @Override public UserViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View item = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_network_user, parent, false);
        return new UserViewHolder(item, mCallback);
    }

    @Override public void onBindViewHolder(UserViewHolder holder, int position) {
        holder.bindTo(mUsers.get(position));
    }

    @Override public int getItemCount() {
        return mUsers.size();
    }

    // Adapter的ViewHolder
    public static class UserViewHolder extends RecyclerView.ViewHolder {

        @Bind(R.id.network_item_iv_user_picture) ImageView mIvUserPicture;
        @Bind(R.id.network_item_tv_user_name) TextView mTvUserName;
        @Bind(R.id.network_item_tv_user_login) TextView mTvUserLogin;
        @Bind(R.id.network_item_tv_user_page) TextView mTvUserPage;

        public UserViewHolder(View itemView, NetworkActivity.UserClickCallback callback) {
            super(itemView);
            ButterKnife.bind(this, itemView);
            // 绑定点击事件
            itemView.setOnClickListener(v ->
                    callback.onItemClicked(mTvUserLogin.getText().toString()));
        }

        // 绑定数据
        public void bindTo(GitHubUser user) {
            mTvUserName.setText(user.name);
            mTvUserLogin.setText(user.login);
            mTvUserPage.setText(user.repos_url);

            Picasso.with(mIvUserPicture.getContext())
                    .load(user.avatar_url)
                    .placeholder(R.drawable.ic_person_black_24dp)
                    .into(mIvUserPicture);
        }
    }

    // 用户类, 名称必须与Json解析相同
    public static class GitHubUser {
        public String login;
        public String avatar_url;
        public String name;
        public String repos_url;
    }
}

添加数据addUser, 其中notifyItemInserted通知更新.
可以自动生成Json解析类的网站.

首先创建`Retrofit``服务, 通过服务获取数据, 再依次分发给适配器.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
 * 用户获取类
 * <p>
 * Created by wangchenlong on 15/12/31.
 */
public class NetworkWrapper {
    private static final String[] mFamousUsers =
            {"SpikeKing", "JakeWharton", "rock3r", "Takhion", "dextorer", "Mariuxtheone"};

    // 获取用户信息
    public static void getUsersInto(final UserListAdapter adapter) {
        GitHubService gitHubService =
                ServiceFactory.createServiceFrom(GitHubService.class, GitHubService.ENDPOINT);

        Observable.from(mFamousUsers)
                .flatMap(gitHubService::getUserData)
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(adapter::addUser);
    }

    // 获取库信息
    public static void getReposInfo(final String username, final RepoListAdapter adapter) {
        GitHubService gitHubService =
                ServiceFactory.createServiceFrom(GitHubService.class, GitHubService.ENDPOINT);

        gitHubService.getRepoData(username)
                .flatMap(Observable::from)
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(adapter::addRepo);
    }
}

网络请求无法在主线程上执行, 需要启动异步线程, 如Schedulers.newThread().
使用工厂模式ServiceFactory创建服务, 也可以单独创建服务.

创建Retrofit服务的工厂类.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
 * 工厂模式
 * <p>
 * Created by wangchenlong on 15/12/31.
 */
public class ServiceFactory {
    public static <T> T createServiceFrom(final Class<T> serviceClass, String endpoint) {
        Retrofit adapter = new Retrofit.Builder()
                .baseUrl(endpoint)
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 添加Rx适配器
                .addConverterFactory(GsonConverterFactory.create()) // 添加Gson转换器
                .build();
        return adapter.create(serviceClass);
    }
}

这是Retrofit 2.0的写法, 注意需要添加Rx和Gson的解析.

设置网络请求的Url.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
 * GitHub的服务
 * <p>
 * Created by wangchenlong on 15/12/31.
 */
public interface GitHubService {
    String ENDPOINT = "https://api.github.com";

    // 获取个人信息
    @GET("/users/{user}")
    Observable<UserListAdapter.GitHubUser> getUserData(@Path("user") String user);

    // 获取库, 获取的是数组
    @GET("/users/{user}/repos")
    Observable<RepoListAdapter.GitHubRepo[]> getRepoData(@Path("user") String user);
}

显示用户
显示

详情页面与主页类似, 参考代码, 不做细说.


线程安全

Rx的好处之一就是可以防止内存泄露, 即根据页面生命周期, 处理异步线程的结束. 可以使用RxLifecycle库处理生命周期.

Activity类继承RxAppCompatActivity, 替换AppCompatActivity.

启动一个循环线程.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
 * Rx的线程安全
 * <p>
 * Created by wangchenlong on 15/12/31.
 */
public class SafeActivity extends RxAppCompatActivity {
    private static final String TAG = "DEBUG-WCL: " + SafeActivity.class.getSimpleName();

    @Bind(R.id.simple_tv_text) TextView mTvText;

    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_simple);
        ButterKnife.bind(this);

        Observable.interval(1, TimeUnit.SECONDS)
                .observeOn(AndroidSchedulers.mainThread())
                .compose(bindToLifecycle()) // 管理生命周期, 防止内存泄露
                .subscribe(this::showTime);
    }

    private void showTime(Long time) {
        mTvText.setText(String.valueOf("时间计数: " + time));
        Log.d(TAG, "时间计数器: " + time);
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.w(TAG, "页面关闭!");
    }
}

继承RxAppCompatActivity, 添加bindToLifecycle方法管理生命周期. 当页面onPause时, 会自动结束循环线程. 如果注释这句代码, 则会导致内存泄露.


RxBinding

RxBinding是Rx中处理控件异步调用的方式, 也是由Square公司开发, Jake负责编写. 通过绑定组件, 异步获取事件, 并进行处理. 编码风格非常优雅.

除了RxJava, 再添加RxBinding的依赖.

1
2
3
4
// RxBinding
compile 'com.jakewharton.rxbinding:rxbinding:0.3.0'
compile 'com.jakewharton.rxbinding:rxbinding-appcompat-v7:0.3.0'
compile 'com.jakewharton.rxbinding:rxbinding-design:0.3.0'

Toolbar和Fab, 两个较新的控件.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical"
    tools:context=".BindingActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/rxbinding_t_toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            android:popupTheme="@style/AppTheme.PopupOverlay"
            tools:targetApi="21"/>

    </android.support.design.widget.AppBarLayout>

    <include layout="@layout/content_rxbinding"/>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/rxbinding_fab_fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        android:src="@android:drawable/ic_dialog_email"/>

</android.support.design.widget.CoordinatorLayout>

两个EditText控件, 对比传统方法和RxBinding方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:app="http://schemas.android.com/apk/res-auto"
              xmlns:tools="http://schemas.android.com/tools"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical"
              android:padding="@dimen/activity_margin"
              app:layout_behavior="@string/appbar_scrolling_view_behavior"
              tools:context=".BindingActivity"
              tools:showIn="@layout/activity_binding">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/usual_approach"/>

    <EditText
        android:id="@+id/rxbinding_et_usual_approach"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:hint="@null"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/reactive_approach"/>

    <EditText
        android:id="@+id/rxbinding_et_reactive_approach"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:hint="@null"/>

    <TextView
        android:id="@+id/rxbinding_tv_show"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>

使用ButterKnife注入控件, 使用RxBinding方式绑定控件, 异步监听事件.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/**
 * Rx绑定
 * <p>
 * Created by wangchenlong on 16/1/25.
 */
public class BindingActivity extends AppCompatActivity {

    @Bind(R.id.rxbinding_t_toolbar) Toolbar mTToolbar;
    @Bind(R.id.rxbinding_et_usual_approach) EditText mEtUsualApproach;
    @Bind(R.id.rxbinding_et_reactive_approach) EditText mEtReactiveApproach;
    @Bind(R.id.rxbinding_tv_show) TextView mTvShow;
    @Bind(R.id.rxbinding_fab_fab) FloatingActionButton mFabFab;

    @Override protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_binding);
        ButterKnife.bind(this);

        initToolbar(); // 初始化Toolbar
        initFabButton(); // 初始化Fab按钮
        initEditText(); // 初始化编辑文本
    }

    // 初始化Toolbar
    private void initToolbar() {
        // 添加菜单按钮
        setSupportActionBar(mTToolbar);
        ActionBar actionBar = getSupportActionBar();
        // 添加浏览按钮
        if (actionBar != null) {
            actionBar.setDisplayHomeAsUpEnabled(true);
        }

        RxToolbar.itemClicks(mTToolbar).subscribe(this::onToolbarItemClicked);

        RxToolbar.navigationClicks(mTToolbar).subscribe(this::onToolbarNavigationClicked);
    }

    // 点击Toolbar的项
    private void onToolbarItemClicked(MenuItem menuItem) {
        String m = "点击\"" + menuItem.getTitle() + "\"";
        Toast.makeText(this, m, Toast.LENGTH_SHORT).show();
    }

    // 浏览点击
    private void onToolbarNavigationClicked(Void v) {
        Toast.makeText(this, "浏览点击", Toast.LENGTH_SHORT).show();
    }

    @Override public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_rxbinding, menu);
        return super.onCreateOptionsMenu(menu);
    }

    // 初始化Fab按钮
    private void initFabButton() {
        RxView.clicks(mFabFab).subscribe(this::onFabClicked);
    }

    // 点击Fab按钮
    private void onFabClicked(Void v) {
        Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content), "点击Snackbar", Snackbar.LENGTH_SHORT);
        snackbar.show();
        RxSnackbar.dismisses(snackbar).subscribe(this::onSnackbarDismissed);
    }

    // 销毁Snackbar, event参考{Snackbar}
    private void onSnackbarDismissed(int event) {
        String text = "Snackbar消失代码:" + event;
        Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
    }

    // 初始化编辑文本
    private void initEditText() {
        // 正常方式
        mEtUsualApproach.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override public void onTextChanged(CharSequence s, int start, int before, int count) {
                mTvShow.setText(s);
            }

            @Override public void afterTextChanged(Editable s) {

            }

        });

        // Rx方式
        RxTextView.textChanges(mEtReactiveApproach).subscribe(mTvShow::setText);
    }
}

Toolbar使用RxToolbar监听点击事件; Snackbar使用RxSnackbar监听;
EditText使用RxTextView监听; 其余使用RxView监听.

动画


OK, That’s all. Enjoy it!


原始地址:
http://www.wangchenlong.org/2016/03/20/1603/207-rxjava-first/
欢迎Follow我的GitHub, 关注我的简书, 微博, 优快云, 掘金, Slides.
我已委托“维权骑士”为我的文章进行维权行动. 未经授权, 禁止转载, 授权或合作请留言.







【Android】使用 `reactor.core.scheduler.Schedulers` 提供的线程池让开发者更加灵活地执行任务 在 Android 开发中,使用提供的线程池可以让开发者更加灵活地执行任务,并且可以根据具体情况选择最合适的线程。:该调度器会使用单个线程来执行任务,适用于需要按照先后顺序依次执行任务的场景。:该调度器会使用一个无限制大小的线程池来执行 I/O 操作,适用于执行 I/O 密集型任务的场景。:该调度器会使用一个无限制大小的线程池来执行计算密集型任务,适用于执行 CPU 密集型任务的场景。:该调度器会创建一个新的并行线程池来执行任务,线程数取决于 CPU 核心数和参数的设置。 阅读详情

相关推荐

手把手教你用DaVinci Configurator配置AUTOSAR SWC接口(含RTE避坑指南)

本文提供了一份基于DaVinci Configurator工具的AUTOSAR软件组件(SWC)接口配置实战指南。文章详细拆解了SR(发送者-接收者)和CS(客户端-服务器)接口的配置流程,并重点分享了运行时环境(RTE)生成阶段在多核部署、数据类型匹配等常见陷阱中的避坑方案与调试技巧,旨在帮助汽车电子工程师高效完成配置并规避错误。

uuu88的博客 548

RxJava 线程切换,AndroidSchedulers 源码分析(三)

RxJava线程类型有以下几种: Schedulers.io() 用于网络请求、访问数据库等耗时操作,线程数量无限制 Schedulers.newThread() 创建新的线程,需要慎用,用在长时间运行在后台不会频繁创建和销毁 Schedulers.computation() 大量数据和图片处理,线程有最大值的限制 Schedulers.immediate() 当前线程,可以理解为默认的是这...

Deaht_Huimie的博客 1416

电动汽车整车控制器 UDS诊断功能开发调试优化方法

摘要:通过某电动汽车VCU开发过程中,UDS诊断功能的调试,发现一些测试中存在的问题,比如覆盖度不够、效率低下等,提出了相应的优化方法,并经过测试验证其可行性,此方法亦可推广应用至BMS、HCM等其他控制器的UDS诊断开发调试过程中。希望能给相关技术人员带来参考和帮助。

qq_43011586的博客 2143

Android最简单的线程调度工具

1、背景介绍。 Android是多线程的系统,线程分为UI线程与工作线程。 UI线程:app的主要线程:主要负责UI的操作。工作线程:主要负责除了UI线程操作的一切线程。 问题所在: 但是很多开发者觉得切换线程麻烦并且容易内存泄漏,所以就不怎么去切换线程操作,能在主线称进行的操作就不在工作线程中执行。但是这样会导致 一、UI线程工作繁重。二、并且系统流畅性差。三、不定时的anr。 不使用工作线程来做非UI线程的工作的原因: 使用工作线程来做非UI线程的工作,需要开启线程,还要考虑到类被销毁后内存

u013265344的博客 785

用Navicat轻松管理魔兽世界私服数据库:修改IP、添加装备的实用技巧

本文详细介绍了如何使用Navicat管理魔兽世界私服数据库,包括修改服务器IP、添加自定义装备和调整角色属性等实用技巧。通过专业的SQL操作指南,帮助玩家轻松实现私服的高级定制,提升游戏体验。

fanta的博客 946

机器学习项目(四)疫情期间网民情绪识别 (一)

疫情期间网民情绪识别 赛题背景 2019新型冠状病毒(2019-nCoV)感染的肺炎疫情发生对人们生活生产的方方面面产生了重要影响,并引发国内舆论的广泛关注,众多网民参与疫情相关话题的讨论。为了帮助政府掌握真实社会舆论情况,科学高效地做好防控宣传和舆情引导工作,本赛题针对疫情相关话题开展网民情绪识别的任务。 数据介绍 训练集 10万条 无标记样本90万条 测试集1万条 数据集依据与“新冠肺炎”相关...

qq_33357094的博客 8972

我的android多线程编程之路(2)之RxJava Schedulers源码分析

写在伊始上一篇介绍了线程的一些基础知识和工作这么久以后对于多线程部分的使用经验之路,这篇主要对RxJava线程控制部分进行分析。RxJava(本文就RxJava2.0分析)说实话,近一年多一直在用rxjava进行项目架构的编写及封装及一些异步请求的处理等等。真的很好用,但本文只对其线程部分进行分析。如果你想学习rxjava的话,推荐您看一下如下几篇文档,也是一点一点学过来的,希望可以帮到您。http

say_from_wen的博客 1784

告别LM2596!用LM5175打造400W自动升降压数控电源(附完整电路图与代码)

本文详细介绍了如何利用LM5175芯片设计400W自动升降压数控电源,替代传统的LM2596方案。通过四开关Buck-Boost拓扑结构,LM5175实现了高达98%的转换效率和400W大功率输出,支持无缝升降压切换。文章涵盖芯片选型、电路设计、PCB布局及STM32数控实现,为工程师提供完整的实践指南。

weixin_27032989的博客 599

RXJava找不到AndroidSchedulers

原因: 还需要依赖一个 RxAndroid,RxAndroid里有AndroidSchedulers,RxJava里是没有的(类似于Java的SDK不会包含AndroidSDK里的TextView) 解决办法: 1.可以增加RxAndroid依赖 2.自己写一个AndroidSchedulers,以下代码: public class AndroidScheduler impleme...

T的专栏 4895

产品经理必看!用Cursor自动生成符合开发规范的技术文档(含UI设计规则模板)

本文详细介绍了产品经理如何利用Cursor工具自动生成符合开发规范的技术文档,并整合UI设计规则模板。通过配置标准化文档结构和设计关联规则,Cursor能有效解决术语不统一、结构混乱等问题,提升跨团队协作效率。文章包含实际案例、规则配置要点及企业级实施方案,帮助产品经理快速掌握AI辅助文档生成技巧。

emacs5lisp的博客 224

【AI】获取各类AI大模型api-key的快速教程(OpenAI、Gemini、Claude、DeepSeek)

【AI】获取各类AI大模型api-key的快速教程(OpenAI、Gemini、Claude、DeepSeek)

funfan的博客 3016

Android面试神器之Rxjava破冰

前言 刚参加工作的时候接触到了项目中的Rxjava,当时一点基础没有,学习了好长时间才渐渐学会使用,但也只是皮毛,停留在表面。后来换工作了发现Rxjava对找工作的帮助还是挺大的,因为是流行框架,都可以讲给面试官听,体现出自己追求技术的精神。但实际上,学会Rxjava对技术的提升还是很有帮助的,所以我会为大家讲解Rxjava的相关知识,以及Rxjava 1 到Rxjava 2的变化,帮助大...

风花散却隽世间,雪月寥落化雨田 1万+

【AI智能体】基于Coze 制作高质量PPT实战操作详解

基于Coze 制作高质量PPT实战操作详解

congge 6102

android rxjava2 简书,《安卓-深入浅出MVVM教程》应用篇-08 RxJava2

简介背景这几年 MVP 架构在安卓界非常流行,几乎已经成为主流框架,它让业务逻辑 和 UI操作相对独立,使得代码结构更清晰。MVVM 在前端火得一塌糊涂,而在安卓这边却基本没见到几个人在用,看到介绍 MVVM 也最多是讲 DataBinding 或 介绍思想的。偶尔看到几篇提到应用的,还是对谷歌官网的Architecture Components 文章的翻译。相信大家看别人博客或官方文档的时候,总...

weixin_27388739的博客 311

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

RxJava在Android中的使用详解

本文详细讲解Rxjava在Android中的原理,技术及应用。比较简单(本文转载于RxJava在Android中的使用)。 前言 我从去年开始使用 RxJava ,到现在一年多了。今年加入了 Flipboard 后,看到 Flipboard 的 Android 项目也在使用 RxJava ,并且使用的场景越来越多 。而最近这几个月,我也发现国内越来越多的人开始提及 RxJava 。有人说『RxJava 真是太好用了』,有人说『RxJava 真是太难用了』,另外更多的人表示:我真的百度了也谷歌了,但我还是

AndyYuan317的博客 3222

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

在安卓上使用RxJava

上面的示例中我们可以看到,方法前的注释是由一个HTTP方法(我们这里用的是GET,当然你也可以按需要用Retrofit实现POST、 PUT、DELETE和HEAD方法)和一个相对路径(基本路径是由RestAdapter.Builder提供的)。响应式扩展库是个很好的想法,我们把它用于安卓程序的开发,已经用了好几个礼拜了(在不久的将来,我们产品的异步任务处理将完全基于它完 成)。怎么样,很简单吧,你只需要很少的代码就实现了整个调用过程,这就是Retrofit的威力,要了解更多,请点击。

G11176593的博客 720

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

2024年安卓最新给 Android 开发者的 RxJava 详解(1),flutter页面跳转后保持导航栏

分享一份工作1到5年以上的Android程序员架构进阶学习路线体系,希望能对那些还在从事Android开发却还不知道如何去提升自己的,还处于迷茫的朋友!阿里P7级Android架构师技术脑图;查漏补缺,体系化深入学习提升**全套体系化高级架构视频;**七大主流技术模块,视频+源码+笔记有任何问题,欢迎广大网友一起来交流网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。需要这份系统化学习资料的朋友,可以戳这里获取。

2401_84563438的博客 1129

安卓进阶——RxJava

RxJava是由Netflix开发的响应式编程框架,基于观察者模式简化异步编程。核心组件包括Observable(数据发射源)、Observer(数据处理者)、Subject(双向桥梁)和Subscription(订阅管理)。框架提供创建、转换、过滤、组合等丰富操作符,支持线程调度控制(如subscribeOn/observeOn)。典型应用场景包括网络请求链式调用、UI事件防抖、生命周期管理等。需注意内存泄漏防护(CompositeDisposable)、合理选择操作符及错误处理机制。其流式...

Meteors.的博客 1200

java的io安卓能用吗_了解RxJava以及如何在Android应用中使用它

如果你在阅读这篇文章,相信你一定很想了解RxJava以及如何在Android应用中使用它。可能你已经见过RxJava的代码了,但仍然有些疑惑,愿你能在这篇文章里找到答案。当我第一次使用RxJava的时候我只是在照搬代码,这些代码能跑起来,但是我对RxJava的基础部分仍然存在误解,而且我找不到好的源码来学习。所以为了理解RxJava,我不得不一点一点学习,踩了不少坑。为了不让你把我踩过的坑再踩一遍...

weixin_31164189的博客 194

RxJava在Android中的应用

将 RxJava 与 MVP 或 MVVM 架构结合使用,是现代 Android(以及 Java Swing)开发中的最佳实践之一。这种结合能充分发挥各自的优势:架构模式负责清晰的职责分离和可测试性,而 RxJava 则优雅地处理异步、事件流和复杂的线程切换。MVP 的核心思想是将 Activity/Fragment (View) 从繁重的业务逻辑中解放出来,使其只负责 UI 的展示和用户交互的传递。) 设置全局错误处理器。merge 合并多个 Observable 的数据,按时间顺序发射,可能交错。

csdn_silent的博客 1154

java方向和安卓方向不同_Android开发的又一个方向之 RxJava 详解

Android开发的又一个方向之 RxJava 详解图片来自极客学院RxJava是当前Android开发者中越来越普及,唯一的问题是在学习的初期比较困难。目前大多数人都习惯用命令行式编程,但是只要你理解它以后,就会觉得她太棒了~话不多说,让我们开始正文,先放上Github上的链接:在正文开始前,我们来说一下RxJava学习中会遇到的一些问题RxJava采用的是响应式编程,它和传统的命令行编程有区别...

weixin_29361879的博客 342

安卓rxjava使用,Android性能优化最佳实践,知乎上转疯了!

一、架构师专题 想要掌握复杂的技术,必须要理解其原理和架构。本模块结合实际一线互联网大型项目理解架构思维,抽丝剥茧,层层深入,帮助大家成为Android架构师,在思想上对架构认识有一次升华,并知其所以然,把别人的架构思想学以致用。 Android Jetpack组件的作用是什么? Navigation:一个用于管理Fragment切换的工具类,可视化、可绑定控件、支持动画等是其优点。 Data Binding:不用说,都知道,加速MVVM的创建。 Lifecycle:他是我们能够处理Act

fanzhang_vip0723的博客 247

安卓RxJava2实现Android帧动画

public class MainActivity extends AppCompatActivity { private List<Integer> pics = new ArrayList<>(); private ImageView iv_pic; private int index = 0; @Override protected void onCreate(Bundle savedInstanceState) { .

zqd1984309864的博客 266

【安卓笔记】RxJava的Hook机制,整体拦截器

整个项目都是用了RxJava,需要对 整个/部分 项目的RxJava进行监听(拦截)可以看到,总共有两个操作符:.create 和 .map。可以根据每个人的业务不同,进行修改。Java 版本:Java11。电脑:Windows10。就会使用到Hook技术。说明hook已经完成。编程语言: Java。确实打印了两次日志。

liosen的博客 370

RxJava Android 示例教程

RxJava Android 示例教程 项目介绍 RxJava 是一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库。RxJava-Android-Example 是一个展示如何在 Android 应用中使用 RxJava 的开源项目。该项目通过实际案例展示了 RxJava 在 Android 开发中的多种应用场景,包括异步编程、数据流处理、错误处理等。 项目快速启动 克隆...

gitblog_00994的博客 1202
上一篇: 给 Android 开发者的 RxJava 详解
下一篇: RxJava使用场景小结
rabbit_in_android
博客等级 码龄11年 45粉丝 39原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值