Android Messenger实现进程间通信及其原理

前言

之前分析Android消息机制的源码时,曾遇到过replyTo、IMessenger等属性字段,当时只是说这些字段用于进程间通信,并未作深入分析。今天这篇文字就来演示一下使用Messenger如何进行进程间通信并分析其源码实现。

Messenger进程间通信的流程

Messenger顾名思义,即信使,那么它的作用就是满足不同进程两边的通信需要了。通常我们会写AIDL来实现进程间通信,其实简单的IPC可以用Messenger来实现,需要知道的是Messenger也是基于AIDL的,只不过Messenger帮我们做了封装而已,其进程间通信框架是这样的:

如上图,假设两个进程分别为Client Process和Server Process,首先Server端需要将自己这边的Messenger引用传给Client,然后Client使用Server端传过来的Messenger来发消息给Server端,这样就实现了一个单向通信。同理,如果想要实现双向通信,则需要Client端也发送一个自己的Messenger到Server端,那么Server端也就可以利用该Messenger向Client发消息了。虽然Messenger是基于AIDL的,但它们最底层都是基于Binder的。

Messenger进程间双向通信示例

创建一个Service模拟Server进程

一般的进程间通信多是在两个App之间,但一个App中也可以有多进程,这个很常见,如应用中的推送服务一般位于单独的进程。当然我们可以把这个Service创建到另一个App中,但为了方便测试,这里只是将该Service注册为另一个进程,但还是在同一个应用中。

该Service的实现很简单,如下:

public class RemoteService extends Service {
    private WorkThread mWorkThread = new WorkThread();
    private Messenger mMessenger;

    @Override
    public void onCreate() {
        super.onCreate();
        mWorkThread.start();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mWorkThread.quit();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }

    private void prepareMessenger() {
        mMessenger = new Messenger(mWorkThread.mHandler);
    }

    private class WorkThread extends Thread {
        Handler mHandler;

        @Override
        public void run() {
            Looper.prepare();
            mHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    switch (msg.what) {
                        case MessageConstant.CLIENT_TO_SERVER:
                            Toast.makeText(RemoteService.this, "Hello Server:" + msg.arg1 + "," + msg.arg2, Toast.LENGTH_SHORT).show();
                            if (msg.replyTo != null) {
                                try {
                                    msg.replyTo.send(Message.obtain(null, MessageConstant.SERVER_TO_CLIENT, 0, msg.arg1 + msg.arg2));
                                } catch (RemoteException e) {
                                    e.printStackTrace();
                                }
                            }
                            break;
                        default:
                            break;
                    }

                }
            };
            prepareMessenger();
            Looper.loop();
        }

        public void quit() {
            mHandler.getLooper().quit();
        }
    }

上述代码虽然简单,但有几点需要注意:

1、为什么Service中要开一个工作线程?因为Service作为四大组件之一,它是运行在主线程的,所以不能执行耗时操作,一旦进程间交互是耗时操作,那么Service所在进程就会阻塞,而Client端进程则不会阻塞。
2、该Service中创建了一个Messenger对象,并在onBind中返回了IBinder对象,这里是进程间通信的关键,在后面会详细分析。
3、该Service的子线程中创建了一个Handler,并关联给Messenger,用于进程间通信的消息处理。Handler消息处理跟我们平时用的一样,但有一点提一下,子线程是没有默认Looper的,因此需要自己创建并启动,否则子线程的Handler无法收到Message。
4、Server端收到消息后,Toast一下“hello server”并显示Cient传过来的两个整数值。如果Client端也将自己的Messenger传过来了,则向Client端回复消息,将两个整数之和返回。

另外该Service在AndroidManifest.xml中的注册如下:

<service
    android:name=".messenger.RemoteService"
    android:enabled="true"
    android:exported="true"
    android:process=":remote">
    <intent-filter>
        <action android:name="com.aspook.remote.ACTION_BIND" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</service>

核心一句为android:process=":remote",将该Service置于另一个进程之中,从而可以在同一个App中模拟进程间通信。

创建一个Activity模拟Client进程

该Activity默认就是该App所在进程了,具体实现如下:

/**
 * demo for IPC by Messenger
 */
public class MessengerActivity extends AppCompatActivity {

    private Button btn_start;
    private Button btn_bind;
    private Button btn_send;
    private boolean mBound = false;
    private Messenger mRemoteMessenger = null;

    private ServiceConnection mRemoteConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mRemoteMessenger = new Messenger(service);
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mRemoteMessenger = null;
            mBound = false;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_messenger);

        findViews();
        setListeners();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mRemoteConnection);
    }

    public void findViews() {
        btn_start = (Button) findViewById(R.id.btn_start);
        btn_bind = (Button) findViewById(R.id.btn_bind);
        btn_send = (Button) findViewById(R.id.btn_send);
    }

    public void setListeners() {
        btn_start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // start Remote Service first
                Intent intent = new Intent(MessengerActivity.this, RemoteService.class);
                startService(intent);
                btn_start.setEnabled(false);
            }
        });

        btn_bind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // bind the Remote Service, if the Remote service run in another App, you should run the App and start the service first
                try {
                    bindRemoteService();
                    btn_bind.setEnabled(false);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

        btn_send.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mBound) {
                    Handler mClientHandler = new Handler() {
                        @Override
                        public void handleMessage(Message msg) {
                            super.handleMessage(msg);
                            switch (msg.what) {
                                case MessageConstant.SERVER_TO_CLIENT:
                                    Toast.makeText(MessengerActivity.this, "Hello Client:" + msg.arg2, Toast.LENGTH_SHORT).show();

                                    break;
                                default:
                                    break;
                            }
                        }
                    };

                    try {
                        Message msg = Message.obtain(null, MessageConstant.CLIENT_TO_SERVER, 66, 88);
                        // Messenger of client sended to server is used for sending message to client
                        msg.replyTo = new Messenger(mClientHandler);
                        mRemoteMessenger.send(msg);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                } else {
                    Toast.makeText(MessengerActivity.this, "Service not bind", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

    /**
     * bind service
     */
    public void bindRemoteService() {
        // Method one
        Intent intent = new Intent("com.aspook.remote.ACTION_BIND");// 5.0+ need explicit intent
        intent.setPackage("com.aspook.androidnotes"); // the package name of Remote Service
        bindService(intent, mRemoteConnection, BIND_AUTO_CREATE);
    }
}

代码逻辑也很简单,界面有3个按钮,操作如下:

1、先启动Server端的Service,暂且叫做启动远程Service
2、绑定远程Service
3、Client向Servcie端发送消息,并接收返回的消息

需要注意的有如下几点:

1、绑定远程Service后,Client端才拿到了Server端的Messenger引用。
2、Client端的Messenger需要关联自己的Handler,用来处理从Server端收到的消息。这里也需要注意,理论上如果Server端与Client端交互也是耗时的话,也需要开子线程,这个例子中由于只是显示下消息,直接放在UI线程了。
3、如果需要双向通信,Client端需要通过Message的replyTo参数将自己的Messenger发到Server端。
4、Android 5.0+要求绑定Service时必须使用显式Intent,可以通过设置包名的方式来解决,注意我是在同一个App中开的两个进程,因此包名相同,但如果远程Service位于另一个App,则应该填写其所在App的包名。
5、Client端收到回复消息后,Toast“Hello client”及两个整数之和。

示例效果演示

以上示例的进程间通信效果演示如下:

Messenger进程间通信原理分析

关于Service的启动、绑定不必多说,先从Client端通过绑定远程Service获取Server端的Messenger入手,代码如下:

private ServiceConnection mRemoteConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mRemoteMessenger = new Messenger(service);
        mBound = true;
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        mRemoteMessenger = null;
        mBound = false;
    }
};

接着来看mRemoteMessenger = new Messenger(service);的源码实现:

/**
 * Create a Messenger from a raw IBinder, which had previously been
 * retrieved with {@link #getBinder}.
 *
 * @param target The IBinder this Messenger should communicate with.
 */
public Messenger(IBinder target) {
    mTarget = IMessenger.Stub.asInterface(target);
}

注意到该构造方法的参数IBinder,就是远程Service中onBind返回的,具体代码如下:

@Nullable
@Override
public IBinder onBind(Intent intent) {
    return mMessenger.getBinder();
}

再来看这一句代码:

mTarget = IMessenger.Stub.asInterface(target);

mTarget是IMessenger对象,看起来越来越像AIDL的写法了,其实不能说像,本来就是AIDL。于是猜想源码必定中有一个名为IMessenger.aidl的文件,它应该定义了发送消息的相关接口。果然在源码目录 “/frameworks/base/core/java/android/os/”下找到了IMessenger.aidl文件,其内容如下:

package android.os;
import android.os.Message;

/** @hide */
oneway interface IMessenger {
    void send(in Message msg);
}

因此可知Messenger只是帮我们省去了写AIDL的工作而已,底层还是AIDL。

再来看Messenger是如何发送消息的,即Messenger的send方法:

/**
 * Send a Message to this Messenger's Handler.
 *
 * @param message The Message to send.  Usually retrieved through
 * {@link Message#obtain() Message.obtain()}.
 *
 * @throws RemoteException Throws DeadObjectException if the target
 * Handler no longer exists.
 */
public void send(Message message) throws RemoteException {
    mTarget.send(message);
}

通过注释可知,Messenger会将消息发送到其关联的Handler,且Handler不存在时会报异常,这就是我们无论是创建客户端还是服务端Messenger时都为其创建了一个Handler的原因。

另外上述示例中为了简便,只是在进程间传递了基本类型的值,其实类似单进程的消息机制,也可以传递Bundle数据,但注意需要序列化,具体说明可参考Message源码的基本字段支撑。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android进程通信之Messenger和AIDL使用详解

    1. 前言 提到的进程间通信(IPC:Inter-Process Communication),在Android系统中,一个进程是不能直接访问另一个进程的内存的,需要提供一些机制在不同的进程之间进行通信,Android官方推出了AIDL(Android Interface Definition Language),它是基于Binder机制的. 上篇提到组件在与Service通信方法有三种. 实现IBinder Messenger AIDL 后面两种可以跨进程通信,是基于Binder机制的通信方式

  • Android IPC机制利用Messenger实现跨进程通信

    写作原因:跨进程通信的实现和理解是Android进阶中重要的一环.下面博主分享IPC一些相关知识.操作及自己在学习IPC过程中的一些理解.这一章使用Messenger实现跨进程通信,其中bindService基础部分参见Android IPC机制绑定Service实现本地通信. 跨进程简介 在介绍使用Messenger跨进程通信之前先要了解以下问题:为什么需要跨进程通信?只有有了需求才有学习的价值.我个人将不同进程简单的理解为不同的应用程序(当然也有例外,比如可以在同一个应用程序中开启两个或多个

  • Android Messenger实现进程间双向通信

    简介 Messenger是安卓进程间通信 (IPC) 最为简单的方式,可以实现进程间双向通信.详见官网介绍 代码实现 服务端应用实现 MessengerService接收客户端发送的消息: package com.test.messengerservice; import android.app.Service; import android.content.Intent; import android.os.Handler; import android.os.IBinder; import

  • Android Messenger实现进程间通信及其原理

    前言 之前分析Android消息机制的源码时,曾遇到过replyTo.IMessenger等属性字段,当时只是说这些字段用于进程间通信,并未作深入分析.今天这篇文字就来演示一下使用Messenger如何进行进程间通信并分析其源码实现. Messenger进程间通信的流程 Messenger顾名思义,即信使,那么它的作用就是满足不同进程两边的通信需要了.通常我们会写AIDL来实现进程间通信,其实简单的IPC可以用Messenger来实现,需要知道的是Messenger也是基于AIDL的,只不过Me

  • Android Handler机制详解原理

    Looper是整个跨线程通信的管理者 // 内部持有的变量如下: ThreadLocal<Looper> MainLooper Observer MessageQueue Thread 1.首先先回忆一下Handler怎么用 Android线程通信分为以下两种情况 1.子线程发消息给UI线程 2.UI线程发消息给子线程 3.子线程发消息给另个子线程 1.子线程发消息给UI线程 class FragmentContentActivity : AppCompatActivity() { val F

  • 深入理解Android中Scroller的滚动原理

    View的平滑滚动效果 什么是实现View的平滑滚动效果呢,举个简单的例子,一个View从在我们指定的时间内从一个位置滚动到另外一个位置,我们利用Scroller类可以实现匀速滚动,可以先加速后减速,可以先减速后加速等等效果,而不是瞬间的移动的效果,所以Scroller可以帮我们实现很多滑动的效果. 首先我们先来看一下Scroller的用法,基本可概括为"三部曲": 1.创建一个Scroller对象,一般在View的构造器中创建: public ScrollViewGroup(Cont

  • Android检测Cursor泄漏的原理以及使用方法

    简介: 本文介绍如何在 Android 检测 Cursor 泄漏的原理以及使用方法,还指出几种常见的出错示例.有一些泄漏在代码中难以察觉,但程序长时间运行后必然会出现异常.同时该方法同样适合于其他需要检测资源泄露的情况. 最近发现某蔬菜手机连接程序在查询媒体存储(MediaProvider)数据库时出现严重 Cursor 泄漏现象,运行一段时间后会导致系统中所有使用到该数据库的程序无法使用.另外在工作中也常发现有些应用有 Cursor 泄漏现象,由于需要长时间运行才会出现异常,所以有的此类 bu

  • Android截屏方案实现原理解析

    Android截屏的原理:获取具体需要截屏的区域的Bitmap,然后绘制在画布上,保存为图片后进行分享或者其它用途 在截屏功能中,有时需要截取全屏的内容,有时需要截取超过一屏的内容(比如:Listview,Scrollview,RecyclerView).下面介绍各种场景获取Bitmap的方法 普通截屏的实现 获取当前Window的DrawingCache的方式,即decorView的DrawingCache /** * shot the current screen ,with the sta

  • Android 系统服务TelecomService启动过程原理分析

    由于一直负责的是Android Telephony部分的开发工作,对于通信过程的上层部分Telecom服务以及UI都没有认真研究过.最近恰好碰到一个通话方面的问题,涉及到了Telecom部分,因而就花时间仔细研究了下相关的代码.这里做一个简单的总结.这篇文章,主要以下两个部分的内容: 什么是Telecom服务?其作用是什么? Telecom模块的启动与初始化过程: 接下来一篇文章,主要以实际通话过程为例,分析下telephony收到来电后如何将电话信息发送到Telecom模块以及Telecom是

  • 浅谈Android中AsyncTask的工作原理

    概述 实际上,AsyncTask内部是封装了Thread和Handler.虽然AsyncTask很方便的执行后台任务,以及在主线程上更新UI,但是,AsyncTask并不合适进行特别耗时的后台操作,对于特别耗时的任务,个人还是建议使用线程池.好了,话不多说了,我们先看看AsyncTask的简单用法吧. AsyncTask使用方法 AsyncTask是一个抽象的泛型类.简单的介绍一下它的使用方式代码如下: package com.example.huangjialin.myapplication;

  • Android中的LeakCanary的原理详解

    场景:最新的leakCanary2.8.1: debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1' 原理:首先就是我们在引入最新的依赖包,什么都不用干了,因为他的初始化在清单文件中注册了contentProvider(),把初始化放到了这里面的onCreate()去初始化了,在初始化的过程中,他会用application监听观察对象activity.fragment等对象的生命周期的变化,当执行销毁的生命周期

  • 解析Android AIDL的实例与原理

    目录 一.概述 二.创建 .aidl 文件 三.生成 .java 文件 四.传输复杂数据 五.建立 service 六.获取服务 七.分析调用过程 一.概述 简单来说,AIDL 就是定义一个接口,客户端(调用端)通过 bindService 来与远程服务端建立一个连接,在该连接建立时会将返回一个 IBinder 对象,该对象是服务端 Binder 的 BinderProxy.在建立连接时,客户端通过 asInterface 函数将该 BinderProxy 对象包装成本地的 Proxy,并赋值给

  • Android实现回弹ScrollView的原理

    本文实例为大家分享了Android实现回弹ScrollView的原理,供大家参考,具体内容如下 回弹的ScrollView 网上看到的通常是ElasticScrollView,有一个Bug:点击子控件滑动时,滑动无效,所以针对此问题,我对ElasticScrollView做了改进. 原理图 代码 我在注释中做了详细的说明 import android.content.Context; import android.graphics.Rect; import android.util.Attrib

随机推荐