只需一篇文章“吃透”Android多线程技术?大牛最佳总结,建议收藏

前言

前段时间在组内做了一下现有的代码分析,发现很多以前的legacy code多线程的使用都不算是最佳实践,而且坏事的地方在于,刚毕业的学生,因为没有别的参照物,往往会复制粘贴以前的旧代码,这就造成了坏习惯不停的扩散。所以本人就总结分析了一下Android的多线程技术选型,还有应用场景。借着和组内分享的机会也在简书上总结一下。因为自己的技术水平有限,有不对的地方还希望大家能多多指正。

这篇文章我会先分析一些大家可能踩过的雷区,然后再列出一些可以改进的地方

1. 在代码中直接创建新的Thread.

new Thread(new Runnable() {
      @Override
      public void run() {

      }
  }).start();

以上的做法是非常不可取的,缺点非常的多,想必大部分朋友面试的时候都会遇到这种问题,分析一下为啥不可以。浪费线程资源是第一,最重要的是我们无法控制该线程的执行,因此可能会造成不必要的内存泄漏。在Activity或者Fragment这种有生命周期的控件里面直接执行这段代码,相信大部分人都知道会可能有内存泄漏。但是就算在其他的设计模式,比如MVP,同样也可能会遇到这个问题。

//runnable->presenter->view
public class Presenter {
    //持有view引用
    private IView view;
    public Presenter(IView v){
        this.view = v;
    }
    public void doSomething(String[] args){
        new Thread(new Runnable() {
            @Override
            public void run() {
                /**
                ** 持有presenter引用
                **/
                //do something
            }
        }).start();
    }
    public static interface IView{}
}

比如图中的一段代码(我标记了引用方向),通常MVP里面的View都是一个接口,但是接口的实现可能是Activity。那么在代码中就可能存在内存泄漏了。Thread的runnable是匿名内部类,持有presenter的引用,presenter持有view的引用。这里的引用链就会造成内存泄漏了。关键是,就算你持有线程的句柄,也无法把这个引用关系给解除。

所以优秀的设计模式也阻止不了内存泄漏。。。。。

2. 频繁使用HandlerThread

虽然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() {
        @Override
        public void run() {
                //do something
        }
    });
    //如果不需要了就remove handler‘s message
    handler.removeCallbacksAndMessages(null);
}

public void test(){
    //如果我还想利用HandlerThread,但是已经丢失了handler的句柄,那么我们利用handler thread再构建一个handler
    Handler handler = new Handler(thread.getLooper());
    handler.post(new Runnable() {
        @Override
        public void run() {
           //do something
        }
    });
}

综上所述,HandlerThread最屌的地方就在于,只要你还有它的句柄,你可以随时拿到在该线程下创建的Looper对象,用于生成一个Handler。之后post的所有runnable都可以在该HandlerThread下运行。然而。。

在实际的开发中,我们好像很难找到这么一个需求,要在指定的一个线程下执行某些任务。注意了是指定的一个,不是一些(线程池)。唯一比Thread厉害的地方恐怕就是可以取消未执行的任务,减少内存泄漏的情况了吧。不过个人观点是线程池好像也可以做到。所以并没有察觉?HandlerThread有任何的优势。而且其实实现也很简单,我们可以随时手写一个简陋版的HandlerThread.

public static class DemoThread extends Thread{
    private LinkedBlockingQueue<Runnable> queue  = new LinkedBlockingQueue<>();

  @Override
  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() {
        @Override
        public void run() {
           }
        };
     thread.post(r);
    //不想执行了。。。。删掉
    thread.clearOneMessage(r);
  }

看分分钟完成HandlerThread能做到的一切。。。。是不是很简单。

3.直接使用AsyncTask.execute()

AsyncTask.execute(new Runnable() {
  @Override
  public void run() {
  }
});

个人认为AsyncTask的设计暴露了这个 接口方法 谷歌做的非常不恰当。它这样允许开发者直接使用AsyncTask本身的线程池,我们可以看看源代码做验证

@MainThread
public static void execute(Runnable runnable) {
     sDefaultExecutor.execute(runnable);
 }

果不其然,execute直接访问了executor。

这样的问题在于,这样使用完全丧失了AsyncTask本身的意图。个人的观点是,AsyncTask提供了一个后台任务切换到主线程的通道,就像RxJava的subscribeOn/observeOn一样,同时提供cancel方法,可以取消掉切换回主线程执行的代码,从而防止内存泄漏。

AsyncTask asyncTask = new AsyncTask() {
    @Override
    protected Object doInBackground(Object[] objects) {
         return null;
    }

    @Override
    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() {
            @Override
            public void onClick(View v) {
                subscription = Observable.create(new Observable.OnSubscribe<Void>() {
                    @Override
                    public void call(Subscriber<? super Void> subscriber) {
                        try {
                            //在这里我们做取消订阅的API, http
                            API 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<Void>() {
                      @Override
                      public void call(Void aVoid) {
                          //API call成功!,在这里更新订阅button的ui
                          unsubscribeButton.toggleSubscriptionStatus();
                      }
                 });
            }
        });
    }
    @Override
    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。

文章不易,如果大家喜欢这篇文章,或者对你有帮助希望大家多多, 点赞转发关注 。文章会持续更新的。绝对干货!!!

原文地址:https://blog.51cto.com/14775360/2485470

时间: 2024-10-07 17:20:42

只需一篇文章“吃透”Android多线程技术?大牛最佳总结,建议收藏的相关文章

一篇文章吃透iOS、JS的时间日期(Date, Calendar, Locale, TimeZone)

iOS 时间相关类 NSDate - 表示一个绝对的时间点. NSCalendar - 代表一个特定的日历,例如公历或者希伯来日历.它提供了一系列基于日期的计算,并且可以让你在"NSDate"和"NSDateComponents"对象之间进行转换. NSDateComponents - 允许你获取一个Date的特定内容,例如小时.分钟.年月日等等. NSTimeZone - 代表一个特定的时区信息,可以帮助跨时区的计算任务. 代码分析 废话少说,Show me th

Unity 5 中的全局光照技术详解(建议收藏)

2015-07-01 10:43 编辑: cocopeng 分类:游戏开发 来源:Unity全球官方网站 1 47108 Unity 5全局光照技术 招聘信息: 资深软件研发工程师 嵌入式软件工程师 cocos2d-x休闲游戏开发 产品经理 Cocos2d-x游戏客户端开发 Java工程师 Cocos2d-x js高级开发工程师 Mac开发 应用开发工程师(iOS) 技术合伙人-后端工程师 高级iOS开发工程师 本文整理自Unity全球官方网站,原文:UNITY 5 - LIGHTING AND

Android开发技术周报 Issue#19

教程 Android开源项目源码解析 由@Trinea发起的Android开源项目源码解析项目,目前已校对发布的分析有:Volley源码解析.Android Universal Image Loader源码分析.Dagger源码解析.EventBus 源码解析.xUtils源码解析.ViewPagerindicator源码解析.HoloGraphLibrary源码解析.CircularFloatingActionMenu源码解析.PhotoView 源码解析.Android Lock Patte

一篇文章看懂Android学习最佳路线

为什么中高级Android程序员不多呢?这是一个问题,我不好回答,但是我想写一篇文章来描述下Android的学习路线,期望可以帮助更多的Android程序员提升自己. 作者:来源:Android开发中文站|2015-11-12 10:40 收藏 分享 前言 看到一篇文章中提到"最近几年国内的初级Android程序员已经很多了,但是中高级的Android技术人才仍然稀缺",这的确不假,从我在百度所进行的一些面试来看,找一个适合的高级Android工程师的确不容易,一般需要进行大量的面试才

Android:学习AIDL,这一篇文章就够了(上)

前言 在决定用这个标题之前甚是忐忑,主要是担心自己对AIDL的理解不够深入,到时候大家看了之后说--你这是什么玩意儿,就这么点东西就敢说够了?简直是坐井观天不知所谓--那样就很尴尬了.不过又转念一想,我辈年轻人自当有一种一往无前的锐气,标题大气一点岂不更好?并且大家都是文明人,总归更多的是理解与补充而不是侮辱与谩骂?所以最终还是厚颜用了这么一个不怎么有耻的标题. 好了,接下来进入正题,谈谈我对AIDL的理解和认识. 正文 1,概述 AIDL是一个缩写,全称是Android Interface D

Android实战——轻松集成百度自动更新SDK,只需3步

轻松集成百度自动更新SDK,只需3步 集成百度自动更新SDK前提: 1.需要上线作品进行测试,本人用自己上线的作品进行了测试. 2.需要上线的作品defaultConfig中的versionCode大于手机端的versionCode,本人上线的作品versionCode为2,测试机为1. 本人作品:一款免下载,免安装,即点即用的应用收纳集,应用袋:http://shouji.baidu.com/software/9529251.html 步骤一:在百度开发者平台选择下载百度自动更新SDK,htt

Android官方技术文档翻译——清单合并

本文译自Android官方技术文档<Manifest Merger>,原文地址:http://tools.android.com/tech-docs/new-build-system/user-guide/manifest-merger. 翻译不易,转载请注明CSDN博客上的出处: http://blog.csdn.net/maosidiaoxian/article/details/42671999 翻译工作耗时费神,如果你觉得本文翻译得还OK,请点击文末的"顶":如有错讹

一篇文章带你了解spring框架

虽然现在流行用SpringBoot了,很多配置已经简化和封装了,但是对于Spring的一些基础我们了解一些是对我们自己的架构思想很有帮助的!接下来和笔者一起来探讨一下Spring框架吧! 1.什么是Spring框架?Spring框架有哪些主要模块? Spring框架是一个为Java应用程序的开发提供了综合.广泛的基础性支持的Java平台.Spring帮助开发者解决了开发中基础性的问题,使得开发人员可以专注于应用程序的开发.Spring框架本身亦是按照设计模式精心打造,这使得我们可以在开发环境中安

(转) TensorFlow深度学习,一篇文章就够了

TensorFlow深度学习,一篇文章就够了 2016/09/22 · IT技术 · TensorFlow, 深度学习 分享到:6 原文出处: 我爱计算机 (@tobe迪豪 ) 作者: 陈迪豪,就职小米科技,深度学习工程师,TensorFlow代码提交者. TensorFlow深度学习框架 Google不仅是大数据和云计算的领导者,在机器学习和深度学习上也有很好的实践和积累,在2015年年底开源了内部使用的深度学习框架TensorFlow. 与Caffe.Theano.Torch.MXNet等框