Android异步消息处理机制

Android异步消息处理机制
android常用异步框架分为handler、AsyncTask、handlerThread、IntentService。

什么是handler

android消息机制的上层接口,通过发送和处理Message和Runnable对象来关联相对应线程的MessageQueue。

1. 可以让对应的Message和Runnable在未来的某个时间点进行相应的处理。
2. 让自己想要处理的耗时操作放在子线程,让更新ui放在主线程。

handler的使用方法

  • post(runnable)
  • sendMessage(msg)注意 msg的获取最好用Message.obtain()获取。底层采用对象池减少内存消耗。

handler机制原理

-w687

Looper是每一个线程所独有的,通过loop()方法读取下面的MessageQueue,读到消息后把消息发送给Handler来处理.MessageQueue是一个消息队列,是FIFO。创建Loop的时候就创建了MessageQueue,2者就关联在了一起。Message就是消息对象。Handler就是处理消息和发送消息。 但是它只能发送跟它相关联的MessageQueue,而MessageQueue又跟looper相关联,所以handler发送消息必须有一个维护它的Looper。

源码分析:

-w540
  1. 首先我们看下handler,MessageQueue,Looper是怎么关联在一起的?
 public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

在handler的构造方法中创建looper,然后根据looper的成员变量创建MessageQueue这样handler就和MessageQueue关联在了一起,而MessageQueue是通过Looper来管理的,从而三者关联在了一起;
我们点进去看下Looper.myLooper方法,发现是从sThreadLocal获取的,ThreadLocal是每个线程都独有的一份数据,所以每个handler获取的都对应自己线程的Looper。

 public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

接着往下看发现sThreadLocal实在prepare()中赋值的,这样保证了每个线程的唯一性。

   private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }
  1. 接着我们看下looper是怎么获取MessageQueue消息的。
 public static void loop() {
        ...省略部分不重要代码
        for (;;) {
            Message msg = queue.next(); // might block
                     try {
                msg.target.dispatchMessage(msg);
              
            }             msg.recycleUnchecked();
        }
    }
    

其实就是创建了一个for死循环,逐个取出msg,通过Handler(msg.target)dispatchMessage(msg)

  public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

dispatchMessage,其实就是中转器,根据不同的条件不同的处理方式。

-w817
  1. 总结
    Looper开启了一个循环,不断的从MessageQueue获取消息,把MessageQueue头部获取的msg,已msg.target形式交由handler.handleMessage中处理。 最终处理完成后,还是会返回到loop中,不断的处理。

handler内存泄露及解决方案

非静态内部类持有外部类的引用,handler里面有可能里面有耗时或者延时操作,当activity销毁后由于handler持有activity,导致activity无法释放,造成了内存泄露。
解决方案:handler设置为静态内部类,在activity的onDestroy中调用handler.removeCallBack()。注意如果在静态内部类中,如果要使用activity,一定用弱引用,而不是直接使用activity。

AsyncTask

AsyncTask可以用来处理一些后台较耗时的任务,查看源码发现其内部就是一个Handler和线程池的封装,可以帮助我们处理耗时任务的同时去更新UI。

AsyncTask的使用
  • AsyncTask抽象类的3参数
public abstract class AsyncTask<Params, Progress, Result> {
......
}
 Params 启动任务执行的输入参数,比如下载URL
 Progress 后台任务执行的百分比,比如下载进度
 Result 后台执行任务最终返回的结果,比如下载结果
  • 子类可以实现的函数
 onPreExecute():(运行在UI线程中) (非必须方法)

在任务执行前调用,通常用来做一些准备操作,比如下载文件前,在显示一个进度条等。

doInBackground(Params... params):(运行在子线程中)(必须实现)

可以在此方法中处理比较耗时的操作,比如下载文件等等。

onProgressUpdate(Progress... values) (运行在UI线程中) (非必须方法)

此函数异步任务执行时,回调给UI主线程的进度,比如上传或者下载进度。

onPostExecute(Result result)(运行在UI线程中) (非必须方法)

此函数代表任务执行结束了,回调给UI主线程的结果。比如下载结果。

onCancelled(Result result)onCancelled()任务关闭的函数
  • 常用函数
cancel (boolean mayInterruptIfRunning)取消执行任务

execute (Params... params)用指定的参数来执行此任务

executeOnExecutor(Executor exec,Params... params)在指定的Executor中执行任务。

getStatus ()获得任务的当)前状态  PENDING(等待执行)、RUNNING(正在运行)、FINISHED(运行完成)

isCancelled ()在任务正常结束之前能成功取消任务则返回true,否则返回false

AsyncTask内部原理简介

-w916

标记1.内部也实例化了带Looper的Handler,为UI更新做好准备
标记2异步执行的地方,WorkRunnable可以理解为一个工作线程,同时自身实现了Callable接口,Call方法中包含了AyncTask最终要执行的任务,并返回结果。
postResult就是将Result携带的信息,发送给指定target的Handler。就是我们前面所说的线程池+handler实现的。

AsyncTask注意事项

  1. 内存泄露 非静态内部类持有外部类的引用;如果Activity已经被销毁,AsyncTask的后台线程还在执行,它将继续在内存里保留这个引用,导致Activity无法被回收,引起内存泄露。
    解决方案:把AsyncTask设置为静态内部类,里面持有activity的弱引用。并在onDestroy中cancel()任务。

  2. AsyncTask不与任何组件绑定生命周期
    解决方案:在Activity/Fragment的onDestory()调用 cancel(true);

  3. 串行或者并行的执行异步任务
    当想要串行执行时,直接执行execute()方法,如果需要并行执行,则要执行executeOnExecutor(Executor)。建议串行,保证线程池的稳定,AsyncTask一般做不了高并发,太耗时的操作。

  4. 结果丢失
    屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity的重新创建,之前运行的AsyncTask会持有一个之前Activity的引用,这个引用已经无效,这时调用onPostExecute()再去更新界面将不再生效。

handlerThread

开启子线程进行耗时操作,多次创建和销毁线程是很消耗系统资源的,google为我们封装了handlerThread。它是有handler +thread +looper构成的。它是通过获取handlerThread的 looper对象传递给handler,然后在handleMessage执行异步任务。
优点是不会阻塞UI线程,缺点是串行执行,处理效率较低。

handlerThread使用

  1. 初始化HandlerThread然后调用其start方法
mHandlerThread = new HandlerThread("mHandlerThread");//这里的mHandlerThread其实就是线程的名字
 mHandlerThread.start();

初始化Handler进行,传入mHandlerThread中的Looper,这样就保证了Handler运行在子线程,loop轮训,不同的交给handleMessage处理消息。

 workHandler = new Handler(mHandlerThread.getLooper()) {
 
             @Override
             public void handleMessage(Message msg) {
                 ...//消息处理
                }             }
         };

3.使用工作线程Handler向工作线程的消息队列发送消息

 Message msg = Message.obtain();
  msg.what = 2; //消息的标识
  msg.obj = "B"; // 消息的存放
  // b. 通过Handler发送消息到其绑定的消息队列
  workHandler.sendMessage(msg);

4.结束线程,即停止线程的消息循环
mHandlerThread.quit();

handlerThread源码分析

//1.Thread的子类
public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;
    private @Nullable Handler mHandler;

//还记得上面的HandlerThread使用的初始化吗?
    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
    
    //thread在开启start后会执行run方法,在这里会准备Looper并开启轮训。
    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
    
    //还记得上面的Handler的创建吗?
      public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

  //可以发送消息的异步Handler
    @NonNull
    public Handler getThreadHandler() {
        if (mHandler == null) {
            mHandler = new Handler(getLooper());
        }
        return mHandler;
    }

        public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit();
            return true;
        }
        return false;
    }

       public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely();
            return true;
        }
        return false;
    }

  
    public int getThreadId() {
        return mTid;
    }
}

注意点1:这里用了java线程知识,wait();和notifyAll(),为什么这么用呢
首先,Handler的创建一般是在主线程完成的,创建的时候获取HandlerThread.getLooper(),而Looper的创建是在子线程中创建的,这里就有线程同步问题了,比如我们调用getLooper()的时候HandlerThread中run()方法还没执行完,mLooper变量还未赋值,此时就执行了wait()等待逻辑,一直等到run()方法中mLooper被赋值,之后立即执行notifyAll(),然后getLooper()就可以正确返回mLooper了。
注意点2:quitSafely()和quit()有什么区别?

区别在于该方法移除消息、退出循环时是否在意当前队列是否正在处理消息,无论是否正在执行此时quit()都会立即退出该循环。 若是正在处理quitSafely,则等待该消息处理处理完毕再退出该循环。

IntentService

一个封装了HandlerThread和Handler的特殊Service,可以多次启动,每个耗时操作都会以工作队列的方式串行在IntentService的onHandleIntent回调方法中执行。任务执行完后会自动停止。

IntentService 使用

1.自定义LocalIntentService继承自IntentService

public class MyIntentService extends IntentService{
 public LocalIntentService(String name) {
        super(name);
    }
}

2.实现onHandleIntent(), 是耗时操作。

@Override
    protected void onHandleIntent(@Nullable Intent intent) {
        String action = intent.getStringExtra("task_action");
      //dosomething
    }

IntentService 源码分析

  1. IntentService继承Service,首先我们看下onCreate(),可以看到里面创建了HandlerThread,所以可以耗时操作。
 //IntentService第一次启动调用
        public void onCreate() {
            super.onCreate();
                       HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
            thread.start();
            mServiceLooper = thread.getLooper();
            mServiceHandler = new ServiceHandler(mServiceLooper);
        }

2.多次启动会走startService()->onStartCommand()->onStart()通过HandlerThread的handler去发送消息。

@Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }

 @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

3.IntentService的SerivceHandler

HandlerThread顺序取出任务执行,会调用ServiceHandler的
handleMessage()->onHandleIntent()。任务执行完毕后stopSelf(startId)停止Service。任务结束后,在onDestory()中会退出HandlerThread中Looper的循环。

//ServiceHandler接收并处理onStart()方法中发送的Msg
        private final class ServiceHandler extends Handler {
            public ServiceHandler(Looper looper) {
                super(looper);
            }
            @Override
            public void handleMessage(Message msg) {
                onHandleIntent((Intent)msg.obj);
                stopSelf(msg.arg1); //会判断启动服务次数是否与startId相等
            }
        }

        public void onDestroy() {
            mServiceLooper.quit();
        }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容