Android线程间通讯

0. 前言

Android 系统中,应用在运行时是一个独立的进程,可是每一个进程中能够包含多个线程提升运行效率。在多线程开发中,有一个很重要的原则:不能在子线程中更新 UI 。java

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

为解决这个问题,目前有多重方案实现子线程和主线程(UI 线程)之间的通讯。github

1. 判断代码执行的线程

在一些简单代码逻辑中,也许可以很清晰的辨别出运行在子线程或主线程中。一般在复杂的类关系依赖、函数嵌套调用中,可能须要花费很大精力去阅读代码以后去判断。不过,巧法子也是有的,一行代码解决。多线程

Log.d("TAG","test");
复制代码

日志内容中,2368-2393 表示是在子线程中输出日志。ide

11-16 01:08:31.584 2368-2393/com.flueky.demo D/TAG: test函数

其中 2368 表示 PID 指进程id, 2393 表示 TID 指线程id 。若是 TID 也是 2368 ,则表示日志输出在主线程中。oop

可能也有人听过 UID ,应用第一次安装在设备上时,系统会分配一个序号给应用,做为其惟一标识。UID 在覆盖安装时不会变化,卸载安装时系统会从新分配一个。post

下面是在代码中获取三个 id 的方式。ui

// 获取 tid
Process.myTid()
// 获取 pid
Process.myPid()
// 获取 uid
Process.myUid()
复制代码

遇到须要在子线程中更新 UI 操做时,能够经过下面的这些方式解决。this

2. 使用 View.post

子线程代码运行在 Activity 或 Fragment 中,能获取到任意 view 的引用时,可使用此方式将须要实现的代码放在主线程中运行。

// post 方法在子线程中调用
textView.post(new Runnable() {
    @Override
    public void run() {
        // 此处代码会在 UI 线程执行
    }
});
复制代码

3. 使用 Activity.runOnUiThread

若是可以直接获取到 Activity 实例,使用 runOnUiThread 方法。

// runOnUiThread 方法在子线程中调用
activity.runOnUiThread(new Runnable() {
    @Override
    public void run() {
        // 此处代码会在 UI 线程执行
    }
});
复制代码

4. 使用 Handler.post

使用 Handler 比较讲究,由于须要考虑到 Handler 实例初始化的位置。

// post 方法在子线程中调用
handler.post(new Runnable() {
    @Override
    public void run() {
        // handler 在主线程中初始化时,此处代码在主线程中执行
        // handler 在子线程中初始化事,此处代码在子线程中执行
    }
});
复制代码

以上说法其实不够严谨,存在下面的状况,初始化 handler 实例时传入 Looper.getMainLooper() ,则 handler.post 也在主线程中执行。

// 下面的代码在子线程中执行
Looper.prepare();
handler = new Handler(Looper.getMainLooper());
Looper.loop();
复制代码

5. 使用 EventBus

EventBus 出自 greenrobot ,经过订阅的方式,告知函数运行在哪一个线程中。为使订阅函数在主线程中执行,使用注解 MAINMAIN_ORDERED

/** * eventbus 简单示例 */
public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    /** * 订阅函数, * ThreadMode.MAIN 表示在主线程中运行,可能会阻塞子线程。 * ThreadMode.MAIN_ORDERED 表示在主线程中运行,不会阻塞子线程。 */
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent(Object event) {
        if(event instanceof Runnable)
            ((Runnable)event).run();
    }

    @Override
    protected void onStart() {
        super.onStart();
        // 注册 eventbus 监听
        EventBus.getDefault().register(this);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // 注销 eventbus 监听
        EventBus.getDefault().unregister(this);
    }
}

// 在子线程中发送消息
EventBus.getDefault().post(new Runnable() {
    @Override
    public void run() {
        // 此处代码会在 UI 线程执行
    }
});
复制代码

6. 传递数据

前面四种方式演示了如何在子线程中作更新 UI 操做。 AsyncTask 也具有相同用法,可是有点牵强,由于只有 execute 方法在主线程中执行,onPostExecute 才会在主线程中调用。因为 onPostExecute 能够接收到子线程传递的任意类型的对象数据,因此 AsyncTask 能够做为线程间的数据交互的载体。对此 HandlerEventBus 表示不服。

EventBus 如以前所示,能够将 Runnable 对象换成任意实例。

Handler 也能够经过 sendMessage 方法发送 Message 对象。其中 Message.obj 用做传递对象数据的载体。

建议使用 Message.obtain() 方法复用 Message 实例。

顺便提下,BroadcastReceiver 也能够做为此类用途,只不过没有 EventBusHandler 方便。

以为有用?那打赏一个呗。去打赏

我的主页已经更新 ,欢迎收藏flueky.github.io/