精华内容
下载资源
问答
  • android 异步
    万次阅读
    2020-12-03 11:16:50

    Android 异步任务的6种实现方式详解


    Android UI线程(主线程)有几个特点:

    • 只能在 UI 线程操作 UI 视图,不能在子线程中操作。
    • 不能在 UI 线程中进行耗时操作,否则会阻塞 UI 线程,引起 ANR、卡顿等问题。

    在 Android 开发中,我们通常将一些耗时的操作使用异步任务的方式进行处理。例如这样一种这种场景,子线程在后台执行一个异步任务,任务过程中,需要 UI 进程展示进度,这时我们就需要一个工具来实现这种需求。Android 系统为开发人员提供了一个异步任务类(AsyncTask)来实现上面所说的功能,即它会在一个子线程中执行计算任务,同时通过主线程的消息循环来获得更新应用程序界面的机会。

    Android 开发中常用的异步任务有哪些呢?

    1. 使用 Thread 创建

    最直接的方式,就是使用 Java 提供的 Thread 类进行线程创建,从而实现异步。

    关于 Thread 的创建方式,请参考《Java 创建线程的三种方式总结》

    2. Thread + Looper + handler

    Android 提供了 Handler 机制来进行线程之间的通信,我们可以使用 Android 最基础的异步方式:Thread + Looper + handler 来进行异步任务。

    关于 Handler 相关机制原理,请参考:《Handler线程通信机制:实战、原理、性能优化!》

    示例代码:

    Handler mHandler = newHandler(){
        @Override
        publicvoid handleMessage(Message msg){
            if(msg.what == 1){
                textView.setText("Task Done!!");
            }
        }
    };
    mRunnable = new Runnable() {
        @Override
        publicvoid run() {
            SystemClock.sleep(1000);    // 耗时处理
            mHandler.sendEmptyMessage(1);  
        }
    };
    private void startTask(){
        new Thread(mRunnable).start();
    }
    

    优点:

    • 操作简单,无学习成本。

    缺点:

    • 代码规范性较差,不易维护。
    • 每次操作都会开启一个匿名线程,系统开销较大。

    3. AsyncTask

    较为轻量级的异步类,封装了 FutureTask 的线程池、ArrayDeque 和 Handler 进行调度。AsyncTask 主要用于后台与界面持续交互。

    我们来看看 AsyncTask 这个抽象类的定义,当我们定义一个类来继承 AsyncTask 这个类的时候,我们需要为其指定3个泛型参数:

    AsyncTask <Params, Progress, Result>
    
    • Params: 这个泛型指定的是我们传递给异步任务执行时的参数的类型。
    • Progress: 这个泛型指定的是我们的异步任务在执行的时候将执行的进度返回给UI线程的参数的类型。
    • Result: 这个泛型指定的异步任务执行完后返回给UI线程的结果的类型。

    我们在定义一个类继承 AsyncTask 类的时候,必须要指定好这三个泛型的类型,如果都不指定的话,则都将其写成 void。

    我们来看一个官方给的例子:

    private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
         protected Long doInBackground(URL... urls) {
             int count = urls.length;
             long totalSize = 0;
             for (int i = 0; i < count; i++) {
                 totalSize += Downloader.downloadFile(urls[i]);
                 publishProgress((int) ((i / (float) count) * 100));
                 // Escape early if cancel() is called
                 if (isCancelled()) break;
             }
             return totalSize;
         }
         protected void onProgressUpdate(Integer... progress) {
             setProgressPercent(progress[0]);
         }
         protected void onPostExecute(Long result) {
             showDialog("Downloaded " + result + " bytes");
         }
    }
    

    使用时只需要集成 AsyncTask,创建对象并调用 execute 执行即可:

    new DownloadFilesTask().execute(url1, url2, url3);
    

    doInBackground(Params…) 方法里执行耗时逻辑,然后在 onPostExecute(Result) 中将结果更新回UI组件

    AsyncTask 的几个主要方法中,doInBackground 方法运行在子线程,execute、onPreExecute、onProgressUpdate、onPostExecute 这几个方法都是在 UI 线程运行的。

    使用 AsyncTask 的注意事项

    • AsyncTask 的实例必须在 UI Thread 中创建。
    • 只能在 UI 线程中调用 AsyncTask 的 execute 方法。
    • AsyncTask 被重写的四个方法是系统自动调用的,不应手动调用。
    • 每个 AsyncTask 只能被执行一次,多次执行会引发异常。
    • AsyncTask 的四个方法,只有 doInBackground 方法是运行在其他线程中,其他三个方法都运行在 UI 线程中,也就说其他三个方法都可以进行 UI 的更新操作。
    • AsyncTask 默认是串行执行,如果需要并行执行,使用接口 executeOnExecutor 方法。

    优点:

    • 结构清晰,使用简单,适合后台任务的交互。
    • 异步线程的优先级已经被默认设置成了:THREAD_PRIORITY_BACKGROUND,不会与 UI 线程抢占资源。

    缺点:

    • 结构略复杂,代码较多。
    • 每个 AsyncTask 只能被执行一次,多次调用会发生异常。
    • AsyncTask 在整个 Android 系统中维护一个线程池,有可能被其他进程的任务抢占而降低效率。

    注:关于 AsyncTask 的原理及更多详情,请参考《AsyncTask你真的会用吗?实战、原理、最佳实践!(Android Q)》

    4. HandlerThread

    HandlerThread 是一个自带 Looper 消息循环的线程类。处理异步任务的方式和 Thread + Looper + Handler 方式相同。

    优点:

    • 简单,内部实现了普通线程的 Looper 消息循环。
    • 可以串行执行多个任务。
    • 内部拥有自己的消息队列,不会阻塞 UI 线程。

    缺点:

    • 没有结果返回接口,需要自行处理。
    • 消息过多时,容易造成阻塞。
    • 只有一个线程处理,效率较低。
    • 线程优先级默认优先级为 THREAD_PRIORITY_DEFAULT,容易和 UI 线程抢占资源。

    注:更多详情请参考:《HandlerThread原理分析、实战、最佳实践!》

    5. IntentService

    IntentService 继承自 Service 类,用于启动一个异步服务任务,它的内部是通过 HandlerThread 来实现异步处理任务的。

    我们来看下 IntentService 的主要方法:

        @Override
        public void onCreate() {
            // TODO: It would be nice to have an option to hold a partial wakelock
            // during processing, and to have a static startService(Context, Intent)
            // method that would launch the service & hand off a wakelock.
    
            super.onCreate();
            HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
            thread.start();
    
            mServiceLooper = thread.getLooper();
            mServiceHandler = new ServiceHandler(mServiceLooper);
        }
    
        @Override
        public void onStart(@Nullable Intent intent, int startId) {
            Message msg = mServiceHandler.obtainMessage();
            msg.arg1 = startId;
            msg.obj = intent;
            mServiceHandler.sendMessage(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);
            }
        }
    

    优点:

    • 只需要继承 IntentService,就可以在 onHandlerIntent 方法中异步处理 Intent 类型任务了。
    • 任务结束后 IntentService 会自行停止,无需手动调用 stopService。
    • 可以执行处理多个 Intent 请求,顺序执行多任务。
    • IntentService 是继承自 Service,具有后台 Service 的优先级。

    缺点:

    • 需要启动服务来执行异步任务,不适合简单任务处理。
    • 异步任务是由 HandlerThread 实现的,只能单线程、顺序处理任务。
    • 没有返回 UI 线程的接口。

    6. 使用线程池来处理异步任务

    利用 Executors 的静态方法 newCachedThreadPool()、newFixedThreadPool()、newSingleThreadExecutor() 及重载形式实例化 ExecutorService 接口即得到线程池对象。

    • 动态线程池 newCachedThreadPool():根据需求创建新线程的,需求多时,创建的就多,需求少时,JVM 自己会慢慢的释放掉多余的线程。
    • 固定数量的线程池 newFixedThreadPool():内部有个任务阻塞队列,假设线程池里有2个线程,提交了4个任务,那么后两个任务就放在任务阻塞队列了,即使前2个任务 sleep 或者堵塞了,也不会执行后两个任务,除非前2个任务有执行完的。
    • 单线程 newSingleThreadExecutor():单线程的线程池,这个线程池可以在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去。

    优点:

    • 线程的创建和销毁由线程池来维护,实现了线程的复用,从而减少了线程创建和销毁的开销。
    • 适合执行大量异步任务,提高性能。
    • 灵活性高,可以自由控制线程数量。
    • 扩展性好,可以根据实际需要进行扩展。

    缺点:

    • 代码略显复杂。
    • 线程池本身对系统资源有一定消耗。
    • 当线程数过多时,线程之间的切换成本会有很大开销,从而使性能严重下降。
    • 每个线程都会耗费至少 1040KB 内存,线程池的线程数量需要控制在一定范围内。
    • 线程的优先级具有继承性,如果在 UI 线程中创建线程池,线程的默认优先级会和 UI 线程相同,从而对 UI 线程使用资源进行抢占。

    总结


    本文详细介绍了 Android 中,实现异步任务的六种方式:

    1. 使用 Thread 创建
    2. 使用 Thread + Looper + handler
    3. 使用 AsyncTask
    4. 使用 HandlerThread
    5. 使用 IntentService
    6. 使用线程池来处理异步任务

    同时,我们也分析了,每种方式的使用的场景以及它的优点和缺点。


    **PS:更多精彩内容,请查看 --> 《Android 开发》
    **PS:更多精彩内容,请查看 --> 《Android 开发》
    **PS:更多精彩内容,请查看 --> 《Android 开发》

    更多相关内容
  • Android强制异步转同步方法,供大家参考,具体内容如下 Android系统中规定耗时任务需要在异步线程中进行,特别是网络请求必须在异步线程中进行否则会抛出NetworkOnMainThreadException,但是在一些特殊的情况我们需要...
  • Android程序编码过程中,回调无处不在。从最常见的Activity生命周期回调开始,到BroadcastReceiver、Service以及Sqlite等。Activity、BroadcastReceiver和Service这些基本组件的回调路径和过程也就是通常意义上所谓...
  • Android的LazyLoad主要体现在网络数据(图片)异步加载、数据库查询、复杂业务逻辑处理以及费时任务操作导致的异步处理等方面。在介绍Android开发过程中,异步处理这个常见的技术问题之前,我们简单回顾下Android...
  • Android客户端模拟一个HTTP的Post请求到服务器端,服务器端接收相应的Post请求后,返回响应信息给给客户端。 背景 网上很多上传到java服务器上的,找了好久,找到了上传到php的了,思路跟我当初想的差不多,就是...
  • 这里说有设计思想是我根据查看Android源代码提炼出来的代码逻辑,所以不会跟Google工程师的原始设计思想100%符合(也有可能是0%),但是本文一定可以帮助你理解AsyncTask,也可能有一些你以前没有发现的内容。...
  • Android异步加载图像小结 (含线程池,缓存方法).rar
  • android 异步显示View

    2019-07-26 07:43:36
    NULL 博文链接:https://hulefei29.iteye.com/blog/625597
  • android异步http请求

    2014-01-25 15:53:36
    实现android异步http请求,可以使用post和get方式
  • 演化理解 Android 异步加载图片
  • Android异步接口测试

    2021-03-23 14:06:32
    Android异步接口测试.基于Android的C/S移动应用中访问后端数据的场景是非常多的,异步接口测试主要是在单元测试完成的基础上检查接口级访问是否正确,主要保证对外请求的组装与发送是否符合后端的约定  基于Android...
  • Android异步消息机制从入门到精通

    千次阅读 2022-03-12 14:12:00
    一、什么是Android异步消息机制? 二、异步消息机制入门 三、源码解析 1.Message 1.1消息内容 1.2.处理消息 1.3.缓存机制 1.4.小结 2.Handler和Looper 2.1.Handler里的Looper 2.2.Handler发送消息 2.3....

    目录

    一、什么是Android异步消息机制?

    二、异步消息机制入门

    三、源码解析

    1.Message

    1.1消息内容

    1.2.处理消息

    1.3.缓存机制

    1.4.小结

    2.Handler和Looper

    2.1.Handler里的Looper

    2.2.Handler发送消息

    2.3.Looper取出消息

    2.4.Handler处理消息

    2.5.小结

    四、解决疑问

    为什么Hander在子进程里发消息,最后会由handler所在的进程处理消息?

    Looper.loop()在主进程里死循环,不会造成阻塞吗?

    五、总结


    一、什么是Android异步消息机制?

    想象这样一个需求场景,你的App现在需要从网络请求或数据库中去获取数据,然后将数据展示到屏幕上。因为网络请求和数据库查询属于是耗时操作,如果在主进程里进行,那么就会造成App卡顿,用户体验极差的后果。

    所以,这时候你需要开启一个子线程去执行这些耗时操作,等耗时操作完成之后,再去更新UI界面。

    这时候你兴冲冲的去写代码,

    button.setOnClickListener {
        thread {
            ....          //耗时操作
            textView.text = date     //更新UI
        }
    }

    执行程序,点击按钮,发现程序直接崩溃了。然后日志里出现了这样一句话

    Only the original thread that created a view hierarchy can touch its views.

    说人话就是,只有创建这个Activity或者Fragment的线程才能够修改他的view。

    这和很多的GUI库一样,UI操作是线程不安全的,所以禁止在子线程里去修改UI内容,只能在创建他的线程里去修改。

    这时候你犯难了,那应该怎么办呢,这时候就需要用到异步消息机制了。

    异步消息机制的作用在于不同线程之间的通信。也就是说,我们可以去在子线程里执行耗时操作,等操作执行完成,再利用异步消息机制告诉主线程我拿到数据了,这时候主线程再去修改UI。就能解决我们的需求了。

    那么Android为我们提供了这样一套机制,他由下面几部分组成:

    • Message

      在线程之间传递的消息

    • Handler

      消息的处理者,用于发送和接受消息。

    • MessageQueue

      消息队列,Handler发送的消息都会存在这个队列中。一个线程只有一个消息队列。

    • Looper

      Looper顾名思义是循环,他是每个线程里MessageQueue的管理者,负责取出消息然后发送给handle。一个线程只有一个Looper

    整个的工作流程如下图:

    二、异步消息机制入门

    前面我们说过了异步消息机制的主要成员,这里我们就来简单的应用一下,看异步消息机制是如何使用的。

    首先我们在Activity的布局里添加一个TextView用户展示文字,这也是我们要修改的UI组件,再写一个Button用来开启一个线程。

    <?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:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".ThreadUpdateUiActivity">
    ​
        <Button
            android:id="@+id/btn_changeUI"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="修改内容"/>
    ​
        <TextView
            android:id="@+id/tv_content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="this is a TextView"
            android:gravity="center_horizontal"
            android:textSize="20sp"
            android:textColor="@color/black"/>
    ​
    </LinearLayout>

    然后到Activity的代码里,去添加异步消息机制。

    class HandlerUpdateUiActivity : AppCompatActivity() {
    ​
        val updateText = 1
    ​
        val handler = object : Handler(Looper.getMainLooper()) {
            override fun handleMessage(msg: Message) {
                when(msg.what) {
                    updateText -> {
                        tv_content.text = "我收到了消息"
                    }
                }
            }
        }
    ​
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_thread_update_ui)
            btn_changeUI.setOnClickListener {
                thread {
                    val msg = Message()
                    msg.what = updateText
                    handler.sendMessage(msg)
                }
            }
        }
    }

    首先我们定义了一个成员变量handler,并重写了其中的handleMessage()方法。这个方法里,根据接收到的msg.what这个参数来确定message的意图,然后进行相应的操作。

        val updateText = 1
    ​
        val handler = object : Handler(Looper.getMainLooper()) {
            override fun handleMessage(msg: Message) {
                when(msg.what) {
                    updateText -> {
                        tv_content.text = "我收到了消息"
                    }
                }
            }
        }

    接着在button的点击事件中,我们创建了一个Message对象,并把msg.what这个变量设置为开头定义的udateText。最终调用handler.sendMessage()将事件发送。

    最终运行效果呢,就是点击按钮后,TextView的内容就改变了。

    点击后->

    基本的入门用法呢就是这样了,可能你还有疑问,为什么没有看到Looper和MessageQueue,他们又是怎么工作的。那接下来就是到源码里的去解析的时候了。

    三、源码解析

    1.Message

    异步消息机制,我们先来看看消息是由什么构成的。

    1.1消息内容

    public final class Message implements Parcelable {
    ​
        public int what;
    ​
        public int arg1;
    ​
        public int arg2;
    ​
        public Object obj;
        
        ...
        
        Bundle data;
        ...   
    }

    先来看看五个重要的成员变量。

    • what

      这个变量我们之前的例子中使用过,他的作用是让接收者知道你的消息是关于什么的。

    • arg1、arg2

      如果你的消息在传输过程中,只需要传输一个或两个int变量,那么就可以直接用这两个变量赋值和获取。

    • obj

      如果你需要传输一个对象,那么可以使用这个变量去存储。不过注意,这个对象需要通过Parcelable序列化才可以。

    • data

      这是一个Bundle对象,利用Bundle就可以把你想要传输的所有数据都存在这里面,然后进行传输了。

    利用这五个变量,你就可以在Message里传递数据了。

    其中设置data的方法是调用setDate()

        public void setData(Bundle data) {
            this.data = data;
        }

    获取有两个方法,一个是getData(),一个是peekData()。

        public Bundle getData() {
            if (data == null) {
                data = new Bundle();
            }
    ​
            return data;
        }
    ​
        public Bundle peekData() {
            return data;
        }

    通过源码可以很清楚的看出来,getData()一定能获取到一个Bundle对象,而peekData()有可能获取到null。

    1.2.处理消息

    Message内部还有一个Handler变量和Runnable变量

        @UnsupportedAppUsage
        Handler target;
    ​
        @UnsupportedAppUsage
        Runnable callback;

    target很好理解,这条消息最终要发给哪一个handler。

    callback会和handler的处理消息是一起讲到,现在只用知道Message的callback可以自己处理消息。

    1.3.缓存机制

    想象一下,我们一个App里必定会有很多地方都会用到异步消息机制去传递消息。如果我们每次都去new一个Message,那么会造成内存浪费。而message这个对象,其实只要传递到,handler执行了相应的操作就可以了。那么这些Message就可以缓存下来,以便后面去使用。

    官方在Message类前有这样一句话:

    • ​While the constructor of Message is public, the best way to get
    • one of these is to call {@link #obtain Message.obtain()} or one of the

    • {@link Handler#obtainMessage Handler.obtainMessage()} methods, which will pull

    • them from a pool of recycled objects.</p>

    翻译一下就是,虽然Message的构造函数是公开的,但是获取一个Message最好的方式是通过Message.obtain()或者handler.obtainMessage()来获取,这会从缓存池里去获取。

    那我们就去看一看Message.obtain()的源码。先来看一看相关的几个成员变量

        
        @UnsupportedAppUsage
        //  Message可以被组成一个链表,而缓存池就是一个链表结构
        /*package*/ Message next;
    ​
        /** @hide */
        public static final Object sPoolSync = new Object();
        // 缓存池
        private static Message sPool;
        // 缓存池的大小
        private static int sPoolSize = 0;
        // 缓存池的最大长度
        private static final int MAX_POOL_SIZE = 50;
        // 该版本系统是否支持回收标志位
        private static boolean gCheckRecycle = true;

    然后就可以看一下obtain()的源码了。

        public static Message obtain() {
            // 同步锁 保证线程安全
            synchronized (sPoolSync) {
                if (sPool != null) {
                    Message m = sPool;
                    sPool = m.next;
                    m.next = null;
                    m.flags = 0; // clear in-use flag
                    sPoolSize--;
                    return m;
                }
            }
            return new Message();
        }

    源码也比较好读懂。首先缓存池其实就是一个链表结构,sPool这个变量就是链表的头结点。

    如果缓存池不为空呢,那么就取出头结点返回;如果缓存池为空,那就new一个对象返回。

    那么现在问题来了,Message是怎么加入缓存池的呢?继续阅读源码,找到了recycle()这个方法

         public void recycle() {
            if (isInUse()) {
                if (gCheckRecycle) {
                    throw new IllegalStateException("This message cannot be recycled because it "
                            + "is still in use.");
                }
                return;
            }
            recycleUnchecked();
        }

    首先他会进入isInUse()判断消息是否正在使用

        boolean isInUse() {
            return ((flags & FLAG_IN_USE) == FLAG_IN_USE);
        }

    然后如果这条消息正在使用的话,就会抛出一个异常,这条消息不能被回收因为正在被使用。

    如果不是正在使用,那么就会进入到recycleUnchecked()里面去。

       void recycleUnchecked() {
            flags = FLAG_IN_USE;
            what = 0;
            arg1 = 0;
            arg2 = 0;
            obj = null;
            replyTo = null;
            sendingUid = UID_NONE;
            workSourceUid = UID_NONE;
            when = 0;
            target = null;
            callback = null;
            data = null;
    ​
            synchronized (sPoolSync) {
                if (sPoolSize < MAX_POOL_SIZE) {
                    next = sPool;
                    sPool = this;
                    sPoolSize++;
                }
            }
        }

    可以看到这个函数里把成员变量里能置空的都赋值为null了。然后进入同步锁,如果缓存池的大小没有超过最大值,那么就把这条消息放在缓存池的头结点处。

    整个缓存机制和流程还是比较好理解的。

    为了方便使用呢,Message还提供了一系列有参的obtain()方法,来满足各种需求,去对一些成员变量赋值。

        // 基本上都是你传入什么参数,就帮你给相应的成员变量赋值
        public static Message obtain(Message orig)
        public static Message obtain(Handler h)
        public static Message obtain(Handler h, Runnable callback)
        public static Message obtain(Handler h, int what)
        public static Message obtain(Handler h, int what, Object obj)
        public static Message obtain(Handler h, int what, int arg1, int arg2)
        public static Message obtain(Handler h, int what, int arg1, int arg2, Object obj)

    1.4.小结

    这部分解析了Message的组成,如何携带数据传输,还有Message的缓存机制,利用链表做缓存池子。

    这部分还没解决的问题:Message何时回收?成员变量callback有什么用。

    2.Handler和Looper

    因为这两个关系比较密切,所以我打算放在一起解析。

    2.1.Handler里的Looper

    我们先看一下Handler的构造函数

    
    ​
        public Handler(@Nullable 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 " + Thread.currentThread()
                            + " that has not called Looper.prepare()");
            }
            mQueue = mLooper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        }

    值得注意的地方是

            mLooper = Looper.myLooper();
            if (mLooper == null) {
                throw new RuntimeException(
                    "Can't create handler inside thread " + Thread.currentThread()
                            + " that has not called Looper.prepare()");
            }

    这里handler要去获取一个自己的Looper,如果获取不到,则会抛出异常,

    接着进入Looper.myLooper()看一看

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

    sThreadLocal这个变量是一个ThreadLocal类型的。

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    ThreadLocal可以使我们创建变量后,每个线程访问的都是自己的变量。

    Looper创建变量是在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));
    }

    这个方法里保证了每个线程里都只有一个Looper。

    所以 这就是说 我们创建Handler之前,应该先调用Looper.prepare()去创建一个本线程的Looper,否则的话会抛出异常!这就是为什么我们在子线程里创建handler一定要调用Looper.prepare()。

    有人说主线程里也没调用啊,这是因为在主程序启动时,系统已经帮我们调用过了,所以在主线程里才不需要再去调用。

    public static void main(String[] args) {
        SamplingProfilerIntegration.start();
        CloseGuard.setEnabled(false);
        Environment.initForCurrentUser();
        EventLogger.setReporter(new EventLoggingReporter());
        Process.setArgV0("<pre-initialized>");
        Looper.prepareMainLooper();             //这一行!!!
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        AsyncTask.init();
        if (false) {
            Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));
        }
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
    
    public static final void prepareMainLooper() {
        prepare();
        setMainLooper(myLooper());
        if (Process.supportsProcesses()) {
            myLooper().mQueue.mQuitAllowed = false;
        }
    }

    最终这里就会调用prepare().

    不过,Handler的无参构造函数不建议使用了。

    /**
         * Default constructor associates this handler with the {@link Looper} for the
         * current thread.
         *
         * If this thread does not have a looper, this handler won't be able to receive messages
         * so an exception is thrown.
         *
         * @deprecated Implicitly choosing a Looper during Handler construction can lead to bugs
         *   where operations are silently lost (if the Handler is not expecting new tasks and quits),
         *   crashes (if a handler is sometimes created on a thread without a Looper active), or race
         *   conditions, where the thread a handler is associated with is not what the author
         *   anticipated. Instead, use an {@link java.util.concurrent.Executor} or specify the Looper
         *   explicitly, using {@link Looper#getMainLooper}, {link android.view.View#getHandler}, or
         *   similar. If the implicit thread local behavior is required for compatibility, use
         *   {@code new Handler(Looper.myLooper())} to make it clear to readers.
         *
         */
        @Deprecated
        public Handler() {
            this(null, false);
        }

    因为隐式的选择Looper可能会导致操作丢失、崩溃或出现竞争。所以更推荐指定Looper的方式去创建Handler。

        public Handler(@NonNull Looper looper) {
            this(looper, null, false);
        } 
    
        public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
            mLooper = looper;
            mQueue = looper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        }

    所以在前面的例子中,创建Handler的方式是

    val handler = object : Handler(Looper.getMainLooper())

    2.2.Handler发送消息

    前面的入门例子中提到,handler发送消息是通过sendMessage()方法发送的,其实还有很多发送消息的方法,比如

    sendMessageDelayed(@NonNull Message msg, long delayMillis)   //在指定毫秒后发送消息

    不过这些方法最终都会走到sendMessageAtTime()这个方法里。

        public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
            MessageQueue queue = mQueue;
            if (queue == null) {
                RuntimeException e = new RuntimeException(
                        this + " sendMessageAtTime() called with no mQueue");
                Log.w("Looper", e.getMessage(), e);
                return false;
            }
            return enqueueMessage(queue, msg, uptimeMillis);
        }

    首先,这里先获取了成员变量mQueue,也就是消息队列。如果为空,则会抛出异常;如果不为空,就会走到enqueueMessage()

        private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
                long uptimeMillis) {
            msg.target = this;
            msg.workSourceUid = ThreadLocalWorkSource.getUid();
    ​
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);
        }

    函数的参数都很好理解,第一个是消息队列,第二个msg是要传递的消息,第三个是延迟发送的时间。

    这条消息就会通过queue.enqueueMessage()进入到队列中去。这里我们暂时不去分析MessageQueue的源码,只需要知道这条消息现在已经进入队列了。

    2.3.Looper取出消息

    前面说到,Looper是MessageQueue的管家,他自然是去消息队列中取出消息的。

    而取出消息的方法在loop()里

        public static void loop() {
            final Looper me = myLooper();
            ...    
            final MessageQueue queue = me.mQueue;
            ...    
            for (;;) {
                Message msg = queue.next(); // might block
                if (msg == null) {
                    return;
                }
                ...   
                try {
                    msg.target.dispatchMessage(msg);
                    ...
                } catch (Exception exception) {
                    ...
                    throw exception;
                } finally {
                    ...
                }
               ...
                msg.recycleUnchecked();
            }
        }

    可以看到,这个方法里开了一个死循环,然后不断地从消息队列中取出消息。

    如果取出的消息为空,或者取出的消息不知道该发给谁,就什么都不做。

    否则,就会走到msg.target.dispatchMessage(msg); 这就是handler去处理消息的方法了。

    方法的最后,我们还能见到msg.recycleUnchecked(); 也就是在处理完之后,在这里,msg被回收了。和前面分析的Message对应了起来。

    分析到这里的时候我有一个问题,Looper.loop()是一个死循环,并且主线程会创建一个自己的Looper对象,说明Looper.loop()是在主线程里的一个死循环。这样的设计不会导致阻塞吗?答案在文末给出。

    2.4.Handler处理消息

    前面Looper取出消息后,会调用handler的dispatchMessage()这个方法。

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

    前面在Message里提到一个Runnable callback的成员变量。在这里就能看出他的作用,如果在Message里设置了callback,那么最终会走到这里。

        private static void handleCallback(Message message) {
            message.callback.run();
        }

    也就是说,Message是可以开启一个子线程去自己处理一些事情的,并不一定是handler来处理。

    如果Message自己不处理呢,那就由Handler自己处理了。

    首先Handler内部有一个接口

        public interface Callback {
            boolean handleMessage(@NonNull Message msg);
        }

    这个接口只需要实现一个handleMessage的方法,也就是处理消息。不过这个是需要一个boolean返回值的。通过dispatchMessage()源码可以分析出,如果callback的handleMessage()返回false,那么handler本身也能继续处理这条消息;如果返回true,handler自己就不能处理了。

    这个机制是不是很熟悉,在事件分发里也有这样的机制。

    2.5.小结

    这部分解析了为什么子线程创建handler需要先调用Looper.prepare(),Handler和Looper是怎样协作,使得消息传递并得到处理的。

    这时候再去看这张图是不是就更清晰了

    异步消息机制到此为止整个原理就比较清晰了,MessageQueue的源码因为篇幅原因就不在这里分析了。

    四、解决疑问

    为什么Hander在子进程里发消息,最后会由handler所在的进程处理消息?

    首先因为Looper和MessageQueue是一个进程只有一个,Handler在哪个进程创建,则该进程应该有自己的Looper和MessageQueue。而Handler在发送消息时,是将消息放到了自己创建线程里的MessageQueue,最终由所在线程的Looper取出并发给Handler。

    所以无论handler是在哪个线程发送消息,都会在创建它的线程里去处理消息。

    Looper.loop()在主进程里死循环,不会造成阻塞吗?

    首先为什么需要死循环。我们的App都是在运行时保持运行状态的,如果没有这个死循环,那么程序就会结束,就会退出。所以死循环是很有必要的。Android本身是基于事件驱动的,每一次点击事件或者Activity的声明周期都是在Looper的控制下的。所以其实我们的代码就是基于这个死循环执行的,如果它停了,应用也就停了。

    所以在这样的基础下,也就不会造成阻塞。

    五、总结

    本文从入门到源码解析,分析了Android异步消息的机制。

    Message的结构和缓存机制,Handler和Looper的协作。

    希望能对读者的理解有一点帮助。

    如果错误,恳请指出。

    展开全文
  • 安卓源码包android图片缓存&展示Android 异步加载图片等24个合集: ‘360全景查看demo.rar afinal框架实现图片的简单异步缓存加载.rar andengine中直接加载多张小图片合成一张大图片生成动画精灵.rar android gif...
  • Handler 、 Looper 、Message 这三者都与Android异步消息处理线程相关的概念。那么什么叫异步消息处理线程呢? 异步消息处理线程启动后会进入一个无限的循环体之中,每循环一次,从其内部的消息队列中取出一个消息,...
  • android项目中访问网络图片是非常普遍性的事情,如果我们每次请求都要访问网络来获取图片,会非常耗费流量,而且图片占用内存空间也比较大,图片过多且不释放的话很容易造成内存溢出。针对上面遇到的两个问题,...
  • Android中的异步消息机制分为四个部分:Message、Handler、MessageQueue和Looper。 其中,Message是线程之间传递的消息,其what、arg1、arg2字段可以携带整型数据,obj字段可以携带一个Object对象。 Handler是处理者...
  • 前言 我们知道在Android开发中不能在非...android中有下列几种异步更新ui的解决办法: Activity.runOnUiThread(Runnable) View.post(Runnable) long) View.postDelayed(Runnable, long) 使用handler(线程间通讯)
  • Android异步消息机制

    千次阅读 2021-11-16 15:12:16
    异步消息机制 Message Message是在线程之间传递的消息,可以在内部携带少量信息。 成员: what, arg1, arg2 Handler 用于发送和处理消息。 成员方法: sendMessage() handleMessage() MessageQueue 消息队列,用于...

    异步消息机制

    1. Message
      Message是在线程之间传递的消息,可以在内部携带少量信息。
      成员:
      what, arg1, arg2
    2. Handler
      用于发送和处理消息。
      成员方法:
      sendMessage()
      handleMessage()
    3. MessageQueue
      消息队列,用于存放所有通过Handler发送的消息。每个线程中只有一个MessageQueue对象。
    4. Looper
      每个线程中的MessageQueue管家。调用Looper的loop()方法后,就会进入一个无限循环,然后每当发现MessageQueue中存在一条消息,就会把它取出,并传递到Handler的handleMessage()方法中。每个线程只有一个Looper对象。

    在这里插入图片描述
    异步消息处理机制示意图

    展开全文
  • android 封装异步调用c#Webservice,需要时调用一下就ok。
  • Android异步请求网络图片demo,博客地址:http://blog.csdn.net/yayun0516
  • 摘要:Java源码,Android,Android源码,异步加载 Android异步加载,通过异步加载外部网站的多张图片,来介绍和演示Android环境下如何去实现文件异步加载功能,想搞Android软件开发的新手,有必要掌握的一个技巧。...
  • Android演化理解 Android 异步加载图片.zip项目安卓应用源码下载Android演化理解 Android 异步加载图片.zip项目安卓应用源码下载 1.适合学生毕业设计研究参考 2.适合个人学习研究参考 3.适合公司开发项目技术参考
  • Android异步操作

    2014-11-03 10:49:20
    Android异步操作----AsyncTask类
  • Android 异步通信.zip

    2021-04-10 09:53:21
    Android 异步通信.zip
  • 主要为大家详细介绍了Android异步更新UI的四种方式,感兴趣的小伙伴们可以参考一下
  • Android演化理解 Android 异步加载图片.zip源码资源下载Android演化理解 Android 异步加载图片.zip源码资源下载
  • Android 应用开发源码 参考与学习使用
  • Android 应用开发源码

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 148,674
精华内容 59,469
关键字:

android 异步