Android多线程技术选型最全指南
作者:qing的世界
链接:https://juejin.im/post/5d1eb4acf265da1bb003de71
new Thread(new Runnable() {public void run() {}}).start();
以上的做法是非常不可取的,缺点非常的多,想必大部分朋友面试的时候都会遇到这种问题,分析一下为啥不可以。浪费线程资源是第一,最重要的是我们无法控制该线程的执行,因此可能会造成不必要的内存泄漏。在Activity或者Fragment这种有生命周期的控件里面直接执行这段代码,相信大部分人都知道会可能有内存泄漏。但是就算在其他的设计模式,比如MVP,同样也可能会遇到这个问题。
//runnable->presenter->viewpublic class Presenter {//持有view引用private IView view;public Presenter(IView v){this.view = v;}public void doSomething(String[] args){new Thread(new Runnable() {@Overridepublic void run() {/**** 持有presenter引用**///do something}}).start();}public static interface IView{}}
比如图中的一段代码(我标记了引用方向),通常MVP里面的View都是一个接口,但是接口的实现可能是Activity。那么在代码中就可能存在内存泄漏了。Thread的runnable是匿名内部类,持有presenter的引用,presenter持有view的引用。这里的引用链就会造成内存泄漏了。关键是,就算你持有线程的句柄,也无法把这个引用关系给解除。
虽然HandlerThread是安卓framework的亲儿子,但是在实际的开发过程中却很少能有他的适用之处。HandlerThread继承于Thread类,所以每次开启一个HandlerThread就和开启一个普通Thread一样,很浪费资源。我们可以通过使用HandlerThread的例子来分析他最大的作用是什么。
static HandlerThread thread = new HandlerThread("test");static {thread.start();}public void testHandlerThread(){Handler handler = new Handler(thread.getLooper());handler.post(new Runnable() {public void run() {//do something}});//如果不需要了就remove handler's messagehandler.removeCallbacksAndMessages(null);}public void test(){//如果我还想利用HandlerThread,但是已经丢失了handler的句柄,那么我们利用handler thread再构建一个handlerHandler handler = new Handler(thread.getLooper());handler.post(new Runnable() {public void run() {//do something}});}
HandlerThread最屌的地方就在于,只要你还有它的句柄,你可以随时拿到在该线程下创建的Looper对象,用于生成一个Handler。之后post的所有runnable都可以在该HandlerThread下运行。然而。。
HandlerThread有任何的优势。而且其实实现也很简单,我们可以随时手写一个简陋版的HandlerThread.public static class DemoThread extends Thread{private LinkedBlockingQueuequeue = new LinkedBlockingQueue<>(); public void run() {super.run();while(true){if(!queue.isEmpty()){Runnable runnable;synchronized (this){runnable = queue.poll();}if(runnable!= null) {runnable.run();}}}}public synchronized void post(Runnable runnable){queue.add(runnable);}public synchronized void clearAllMessage(){queue.clear();}public synchronized void clearOneMessage(Runnable runnable){for(Runnable runnable1 : queue){if(runnable == runnable1){queue.remove(runnable);}}}}public void testDemoThread(){DemoThread thread = new DemoThread();thread.start();//发一个消息Runnable r = new Runnable() {public void run() {}};thread.post(r);//不想执行了。。。。删掉thread.clearOneMessage(r);}
看分分钟完成HandlerThread能做到的一切。。。。是不是很简单。
3.直接使用AsyncTask.execute()
AsyncTask.execute(new Runnable() {@Overridepublic void run() {}});
个人认为AsyncTask的设计暴露了这个接口方法谷歌做的非常不恰当。它这样允许开发者直接使用AsyncTask本身的线程池,我们可以看看源代码做验证
@MainThreadpublic static void execute(Runnable runnable) {sDefaultExecutor.execute(runnable);}
AsyncTask asyncTask = new AsyncTask() {protected Object doInBackground(Object[] objects) {return null;}protected void onPostExecute(Object o) {//1.提供了后台线程切换回主线程的方法super.onPostExecute(o);}};//2.可以随时取消asyncTask.cancel(true);
But!如果直接使用execute方法的话,我们完全没有利用到AsyncTask本身设计的初衷下的优势,和直接自己创建一个线程池没有任何区别,还存在内存泄漏的风险。这样的用法,肯定不能称之为best practice.
4. 以为RxJava的unsubscribe能包治百病
这个误区标题起的有点模糊,这个没办法,因为例子有点点复杂。让我来慢慢解释。

我们以一个实际的app例子开始,让我们看看youtube的app退订频道功能:

用户点击退订按钮之后,app发出api call,告诉后台我们停止订阅该频道,同时把UI更新为progress bar,当api call结束,在api的回调里面我们更新UI控件显示已退订UI。我们写一个示例代码看看:完美!
但是万一用户在点击退订按钮,但是api call还没发出去之前就退出了app呢?
public class YoutubePlayerActivity extends Activity {private Subscription subscription;public void setUnSubscribeListner(){unsubscribeButton.setOnClickListener(new View.OnClickListener() {public void onClick(View v) {subscription = Observable.create(new Observable.OnSubscribe() { public void call(Subscriber super Void> subscriber) {try {//在这里我们做取消订阅的API, httpAPI api = new API();api.unSubscribe();}catch (Exception e){subscriber.onError(e);}subscriber.onNext(null);subscriber.onCompleted();}}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Action1() { public void call(Void aVoid) {//API call成功!,在这里更新订阅button的uiunsubscribeButton.toggleSubscriptionStatus();}});}});}protected void onDestroy() {super.onDestroy();//onDestroy 里面对RxJava stream进行unsubscribe,防止内存泄漏subscription.unsubscribe();}}
看似好像没啥问题,没有内存泄漏,可以后台线程和主线程直接灵活切换,更新UI不会crash。而且我们使用了Schedulers.io()调度器,看似也没有浪费线程资源。
BUT!!!!!!
我们先仔细想想一个问题。我们在点击button之后,我们的Observable
API api = new API();api.unSubscribe();
会立刻执行么?
答案是NO。因为我们的Observable是subscribeOn io线程池。如果该线程池现在非常拥挤,这段代码,这个Observable是不会立刻执行的。该段代码会华丽丽的躺在线程池的队列中,安安静静的等待轮到自己执行。
那么如果用户点击按钮,同时退出app,我们unubscribe了这个RxJava 的observable 我们就存在一个不会执行api call的风险。也就是用户点击退订按钮,退出app,返回app的时候,会发现,咦,怎么明明点了退订,竟然还是订阅状态?
这就回到了一个本质问题,来自灵魂的拷问。是不是所有异步调用,都需要和Activity或者fragment的生命周期绑定?
答案同样是NO,在很多应用场景下,当用户做出一个行为的时候,我们必须坚定不移的执行该行为背后的一切操作,至于异步操作完成之后的UI更新,则视当前Activity或者fragment的生命周期决定。也就是异步操作和生命周期无关,UI更新和生命周期有关。简单点说,很多情况下,写操作不能取消,读操作可以。
很多情况下,比如支付,订阅等等这种用户场景,需要涉及到异步操作的都是会有以上的问题。在这些场景下,我们需要遵循以下流程。

最最重点的部分,就是当用户退出的时候虽然我们停止更新UI,但当用户重新进入的时候,app需要主动的重新向后台发送请求,查看当前订阅状态。这样,才是一个健康的app。
所以很遗憾,RxJava并没有很好的支持这一场景,至于怎么解决,有什么框架比较合适,下一章再介绍。
开发者全社区
