android IPC通信(上)-sharedUserId&&Messenger

  看了一本书,上面有一章解说了IPC(Inter-Process Communication,进程间通信)通信。决定结合曾经的一篇博客android 两个应用之间的通信与调用和自己的理解来好好整理总结一下这块的知识。因为内容较多,这部分会分上中下三篇博客来细致分析解说,第一篇上篇要解说的是sharedUserId和Messenger的使用方式。

  android IPC通信(中)-ContentProvider&&Socket

  android IPC通信(下)-AIDL

  

sharedUserId

  sharedUserId的作用是让两个应用程序共享一个user id。我们都知道linux进程给每个应用程序分配了一个独立的user id,所以假设两个或多个应用程序的签名同样而且设置了一样的sharedUserId,他们将会共享一个user id,同样user id的应用程序能够訪问对方的数据(也就是说假设应用程序中的一个文件的权限是600,同样uid能够直接訪问,反之则无法訪问)。而且设置成一个android:process就能够运行在一个进程中了。

  sharedUserId方式主要就是使用createPackageContext (String packageName, int flags)函数。该函数用来返回指定包名应用的上下文。注意是application的context。

  这种方法有两个參数:

  1. packageName:包名,要得到Context的应用程序的完整包名
  2. flags:标志位,有CONTEXT_INCLUDE_CODECONTEXT_IGNORE_SECURITY两个选项,CONTEXT_INCLUDE_CODE选项的作用就是能够在调用者的进程运行该application的代码。也就是说能够使用getClassLoader()函数来初始化该application的相关类,使用该标识将会在你能够使用的application context上施加安全约束,假设须要载入的application不能被安全的载入进进程的话,将会抛出一个SecurityException,假设这个标示没有被设置。那么将不会在被载入的类上面施加不论什么约束,getClassLoader()将会返回默认的系统类载入器;CONTEXT_IGNORE_SECURITY的意思是忽略不论什么安全警告,和CONTEXT_INCLUDE_CODE标识一起使用可能会将不安全的代码载入进进程,所以慎重使用。

  在我博客android permission权限与安全机制解析(上)中已经简介了一下SharedUserId,这次就要具体解说一下他的具体使用方法,用来A应用和B应用之间的交互。先来看看两个应用的manifest文件:

  A应用:

<manifest package="com.android.shareduserid_a"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:sharedUserId="com.android.test"
    android:sharedUserLabel="@string/share_label">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme">
        <activity android:name="com.android.shareuserid_a.Server">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

    </application>

</manifest>

  B应用:

<manifest package="com.android.shareduserid_b"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:sharedUserId="com.android.test"
    android:sharedUserLabel="@string/share_label">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme">
        <activity android:name="com.android.shareuserid_b.Client"
            android:exported="true">
        </activity>
    </application>
</manifest>

  B应用的Client Activity加上了android:exported=”true”用来与A应用交互,这个在android permission权限与安全机制解析(上)已经有具体的介绍。在这就略过了。

  A应用有一个Server的Activity,B应用有一个Client的Activity。先来看看交互的效果。左側为A应用。右側为B应用:

  

  A应用中的Server activity通过以下代码获取到B应用的application context:

context = createPackageContext("com.android.shareduserid_b", CONTEXT_INCLUDE_CODE|CONTEXT_IGNORE_SECURITY);

  获取到该application的上下文之后,我们能做的事情就许多了,以下介绍几种:

获取B应用drawable

  获取B应用的drawable。使用getIdentifier函数就可以获取B应用该资源的id:

//获取B应用图片
int id = context.getResources().getIdentifier("share", "mipmap", "com.android.shareduserid_b");
iv_pic.setImageDrawable(ContextCompat.getDrawable(context, id));

获取B应用string

  获取B应用字符串,统一使用getIdentifier函数获取id:

id = context.getResources().getIdentifier("share_string", "string", "com.android.shareduserid_b");
tv_string.setText(context.getString(id));

打开B应用页面

  打开B应用activity。使用ComponentName设置B应用的activity名称和包名就能成功跳转过去了,类名须要带上完整的包名,当然使用intent.setClassName()函数也是能够成功的。记得在B应用一定要加上android:exported=”true”:

Intent intent = new Intent();
ComponentName componentName = new ComponentName("com.android.shareduserid_b",
        "com.android.shareuserid_b.Client");
intent.setComponent(componentName);
//intent.setClassName(context, "com.android.shareuserid_b.Client");
//intent.setClassName("com.android.shareduserid_b",
//        "com.android.shareuserid_b.Client");
startActivity(intent);

运行B应用指定类函数

  运行B应用函数,当然是利用反射,要运行B应用的代码,调用createPackageContext函数时flag中一定要有CONTEXT_INCLUDE_CODE。

假设被反射的函数參数是可变类型,相似于int… integers,那么getMethod函数第二个參数传入一个int[]数组就能够了。

注意在B应用中要有相关的类和函数:

try {
    Class clazz = context.getClassLoader().loadClass("com.android.shareuserid_b.Method");
    Object object = clazz.newInstance();
    int[] ints = new int[]{1,2,3};
    int sum = (int) clazz.getMethod("add", int[].class).invoke(object, ints);
    tv_sum.setText("sum is :"+sum);
} catch (Exception e) {
    L.e(e);
    e.printStackTrace();
}

获取B应用SharedPreferences

  获取B应用SharedPreferences,这个须要特殊说明一下,因为SharedPreferences是有缓存机制的,所以假设在B应用中改动了该SharedPreferences文件,接着A应用去读取该文件里改动的那个值。这时你会发现还是改动前的值。这就是缓存机制导致的问题,只是有一个flag能够解决问题:MODE_MULTI_PROCESS,可是很不幸的是api23已经将该标识deprecated了,原因是在一些版本号上不可靠,有兴趣的能够去了解一下,看代码:

//注意Context.MODE_MULTI_PROCESS不可靠
SharedPreferences sharedPreferences = context.getSharedPreferences("permanent", MODE_MULTI_PROCESS);
String time = sharedPreferences.getString("time", "get time error");
tv_shared_preference.setText(time);

获取B应用数据库

  获取B应用的数据库,注意数据库名字和表名一定要相应上,要不然会抛出Exception:

String DBPath = context.getDatabasePath("permanentCache.db").getAbsolutePath();
SQLiteDatabase sqLiteDatabase = SQLiteDatabase.openDatabase(DBPath, null, SQLiteDatabase.OPEN_READONLY);
Cursor cursor = sqLiteDatabase.query("cache_1", null, "key=?", new String[]{"time"}, null, null, null, null);
cursor.moveToNext();
tv_DB.setText(cursor.getString(1));
cursor.close();
sqLiteDatabase.close();

用途:这样的方式能够用来进行轻量级的补丁操作。比如皮肤,第一步从服务器获取全部皮肤的包名,第二步看用户选择的皮肤包是否已经安装到手机上,假设没有从服务器下载安装。假设有直接第三步;第三步当然就是从该皮肤包中获取资源等等等了。

下载地址

源代码地址:https://github.com/zhaozepeng/IPC-demo/tree/master/ShareUserId

Messenger

  Messenger能够用来在不同进程中传递对象。在Messenger中放入我们须要传递的对象,就能轻松地实现数据的进程间传递了。Messenger是一种轻量级的IPC方案,它对AIDL进行封装,所以使用起来很的方便,当然AIDL通信的底层实现也是对Binder的封装。须要特别注意的是这个Binder类并不会影响系统对进程生命周期的管理(你须要使用一些更高等级的组件来告诉系统你的进程须要继续运行而不被系统回收内存)。假设因为某些原因被系统杀死回收。连接就会断开。

  因为是进程之间的通信,所以能够在一个应用中开两个进程通信。也能够在两个应用中实现通信,我就以两个应用之间的通信为例。

两个应用之间的通信能够使用两种方式:

  1. client使用bindService函数绑定服务端Service,而且利用handler进行两个应用之间message的处理。
  2. client使用bindService绑定服务端Service。然后直接使用反射机制,让client反射service端的函数来进行操作。注意。这样的方式一定要让两个应用运行在同一个进程中(使用sharedUserId+android:process),要不然无法反射出相关函数。具体代码中会指出。

Handler处理方式

  handler方式最主要是通过Messenger+message进行两个应用的通信,先来看看服务端Service代码:

Messenger messenger = null;
private static class MessengerHandler extends Handler{
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what){
            case 1:
                L.i("i receive ‘" + msg.getData().getString("message")+"‘");
                Messenger client = msg.replyTo;

                //回应client
                if (client != null){
                    Message reply = Message.obtain();
                    Bundle message = new Bundle();
                    message.putString("message", "i have received your message");
                    L.i("i have received your message");
                    reply.setData(message);
                    try {
                        client.send(reply);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
                break;
            case 2:
                L.i("i receive ‘" + msg.getData().getString("message")+"‘");
                L.i("client has disconnect this connection, bye~");
                break;
            default:
                break;
        }
    }
}

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

@Override
public void onCreate() {
    super.onCreate();
    messenger = new Messenger(new MessengerHandler());
}

  client代码:

private Button connect_handler;
private TextView tv_handler;
private Button connect_binder;
private TextView tv_binder;

private ServiceConnection serviceConnection;
private Messenger serverMessenger;
private Messenger messenger;

private boolean hasBindService = false;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.client_handler);
    connect_handler = (Button) findViewById(R.id.connect_handler);
    connect_handler.setOnClickListener(this);
    tv_handler = (TextView) findViewById(R.id.tv_handler);
    connect_binder = (Button) findViewById(R.id.connect_binder);
    connect_binder.setOnClickListener(this);
    tv_binder = (TextView) findViewById(R.id.tv_binder);

    serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            serverMessenger = new Messenger(service);
            communicate();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            serverMessenger = null;
        }
    };

    messenger = new Messenger(new Handler(){
        @Override
        public void handleMessage(Message msg) {
            L.i("i have received ‘" + msg.getData().getString("message") + "‘");
            Message message = Message.obtain();
            Bundle bundle = new Bundle();
            bundle.putString("message", "OK, bye bye~");
            message.setData(bundle);
            L.i("i have send ‘" + message.getData().getString("message") + "‘");
            message.what = 2;
            if (serverMessenger != null){
                try {
                    serverMessenger.send(message);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    });
}

private void communicate(){
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss");
    Message message = Message.obtain();
    Bundle msg = new Bundle();
    msg.putString("message", "i have send handler a message at " + simpleDateFormat.format(System.currentTimeMillis()));
    message.setData(msg);
    L.i("i have send ‘" + message.getData().getString("message") + "‘");
    message.what = 1;
    message.replyTo = messenger;
    if (serverMessenger != null){
        try {
            serverMessenger.send(message);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

@Override
public void onClick(View v) {
    switch (v.getId()){
        case R.id.connect_handler:
            if (!hasBindService) {
                Intent intent = new Intent();
                intent.setClassName("com.android.messenger_a", "com.android.messenger_a.ServerWithHandler");
                bindService(intent, serviceConnection, BIND_AUTO_CREATE);
                hasBindService = true;
            }else{
                if (serverMessenger == null){
                    return;
                }
                communicate();
            }
            break;
        case R.id.connect_binder:
            startActivity(new Intent(this, ClientForBinder.class));
            break;
    }
}

@Override
protected void onDestroy() {
    super.onDestroy();
    if (serverMessenger != null)
        unbindService(serviceConnection);
}

  运行结果例如以下图:

  

  服务器端Service先须要自己定义一个Handler类用来处理client发过来的消息。接着用一个Handler对象新建一个Messenger对象,这样就相当于把发送给Messenger的message处理交给了该Handler,最后就在onBind函数中返回该Messenger的IBinder就可以。

  client用bindService绑定该服务端service。而且在onServiceConnected回调中用IBinder參数构造一个Messenger,这个Messenger就是client用来和服务端通信的中介了,另一点须要注意的是。为了服务端接收到client的消息之后能够回复client。在client也须要新建一个Messenger。而且将其通过message.replyTo变量传递给服务端,服务端就能够通过该replyTo变量传递消息给client了。

反射方式

  该模式须要使用sharedUserId+android:permission的方式将两个应用置于一个进程才干使用(这样想一想好像就不是跨进程通信了呢-, -)。要不然是无法反射到相关函数的。

  服务端代码:

private final InnerBinder binder = new InnerBinder();

public class InnerBinder extends Binder {
    public ServerWithBinder getServer(){
        return ServerWithBinder.this;
    }
}

public int add(int... ints){
    int sum = 0;
    for (int temp : ints){
        sum += temp;
    }
    return sum;
}

@Override
public IBinder onBind(Intent intent) {
    return binder;
}

  client代码:

private ServiceConnection serviceConnection;
private Messenger serverMessenger;
private IBinder mBoundService;
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.client_binder);
    findViewById(R.id.connect_binder).setOnClickListener(this);

    serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            try {
                mBoundService = service;
                Class clazz = mBoundService.getClass();
                //假设两个应用程序没有运行在同一个进程中,则无法反射到该函数
                Method method = clazz.getDeclaredMethod("getServer");
                Object object = method.invoke(mBoundService);
                Class messenger = object.getClass();
                Method add = messenger.getDeclaredMethod("add", int[].class);
                L.e("1+2+3=" + add.invoke(object, new int[]{1,2,3}));
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            serverMessenger = null;
        }
    };
}

@Override
public void onClick(View v) {
    Intent intent = new Intent();
    intent.setClassName("com.android.messenger_a", "com.android.messenger_a.ServerWithBinder");
    bindService(intent, serviceConnection, BIND_AUTO_CREATE);
}

@Override
protected void onDestroy() {
    if (serverMessenger != null)
        unbindService(serviceConnection);
    super.onDestroy();
}

  运行结果例如以下图所看到的:

  

  client通过onServiceConnected函数回调获取到该Binder对象。通过该Binder对象反射服务端Binder类的相关方法,在服务端的该相关方法getServer()中,直接返回该Service对象,获取到该Service对象之后就能够成功反射该类的全部方法,也就能够成功进行两个应用之间(尽管说不是两个进程)的通信了。

下载地址

源代码地址:https://github.com/zhaozepeng/IPC-demo/tree/master/Messenger

Service相关flag介绍

  关于Service的具体介绍能够看我曾经的一篇博客:

  http://blog.csdn.net/self_study/article/details/11650293

  这里简介Service的相关flag标识。每次通过startService(Intent)函数启动Service都会调用到onStartCommand (Intent intent, int flags, int startId)函数,该函数第二个參数能够为 0,START_FLAG_REDELIVERY或者START_FLAG_RETRY

  1. START_FLAG_REDELIVERY
  2. 让系统又一次发送一个intent。这样假设你的服务在处理它的时候被Kill掉。Intent不会丢失。

  3. START_FLAG_RETRY
  4. 表示服务之前被设为START_STICKY。则会被传入这个标记。

  onStartCommand (Intent intent, int flags, int startId)函数返回值有四个START_STICKYSTART_NOT_STICKYSTART_REDELIVER_INTENTSTART_STICKY_COMPATIBILITY

  1. START_STICKY
  2. 假设service进程被kill掉,保留service的状态为開始状态,但不保留递送的intent对象。

    随后系统会尝试又一次创建service。因为服务状态为開始状态。所以创建服务后一定会调用onStartCommand(Intent,int,int)方法。假设在此期间没有不论什么启动命令被传递到service。那么參数Intent将为null。

  3. START_NOT_STICKY
  4. “非粘性的”,使用这个返回值时,假设在运行完onStartCommand后。服务被异常kill掉,系统不会自己主动重新启动该服务

  5. START_REDELIVER_INTENT
  6. 重传Intent。使用这个返回值时,假设在运行完onStartCommand后。服务被异常kill掉,系统会自己主动重新启动该服务,并将Intent的值传入。

  7. START_STICKY_COMPATIBILITY
  8. START_STICKY的兼容版本号,但不保证服务被kill后一定能重新启动。

时间: 2024-09-28 19:18:14

android IPC通信(上)-sharedUserId&amp;&amp;Messenger的相关文章

android IPC通信(下)-AIDL

android IPC通信(上)-sharedUserId&&Messenger android IPC通信(中)-ContentProvider&&Socket 这篇我们将会着重介绍AIDL的使用方式和原理,要介绍AIDL先要简单介绍一下Binder,而且Messenger,ContentProvider和AIDL的最底层都是使用的Binder. Binder 直观来说,Binder是Android中的一个类,它实现了IBinder接口.从IPC角度来说,Binder是A

android ipc通信机制之之三,进程通讯方式。

IPC通讯方式的优缺点: IPC通讯方式的对比 名称 优点 缺点 适用场景 Bundle 简单易用 只能传输Bundle支持的数据类型 四大组件的进程通信 文件共享 简单易用 不适合高并发场景,并无法做到进程间即时通讯. 无并发访问情形,交换简单的数据是实时性不高的场景. AIDL 功能强大,支持一对多并发通信,支持实时通信. 使用稍微复杂,需要处理好线程同步. 一对多通信且有RPC需求 Messenger 功能一般,支持一对多串行通信,支持实时通信. 不能很好处理高并发情形,不支持RPC,数据

android ipc通信机制之二序列化接口和Binder

IPC的一些基本概念,Serializable接口,Parcelable接口,已经Binder.此核心为最后的IBookManager.java类!!! Serializable接口,Parcelable接口都是可以完成对象的序列化过程. 序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程.在序列化期间,对象将其当前状态写入到临时或持久性存储区.以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象. 两者均可以实现序列化并且都可以用于Intent数

Android IPC通信机制之一

IPC:Intent-process Communication,跨进程通信. 在一个应用程序中,可以很方便的开启多进程.开启进程的方式只有一种,只能在AndroidMenifest中为四大组件指定 Android:process属性. 在非常规情况下,也可以通过JNI在native层去fork一个新的进程. <activity android:name=".MainActivity" android:label="@string/app_name" andr

Android IPC通信以及AIDL技术运用

首先我们了解一下 IPC和AIDL IPC:进程间通信 AIDL:Android Interface Definition Language,即Android接口定义语言. 为什么使用: Android系统中的进程之间不能共享内存,因此,需要提供一些机制在不同进程之间进行数据通信. 为了使其他的应用程序也可以访问本应用程序提供的服务,Android系统采用了远程过程调用(Remote Procedure Call,RPC)方式来实现.与很多其他的基于RPC的解决方案一样,Android使用一种接

Android IPC机制(三)在Android Studio中使用AIDL实现跨进程方法调用

在上一篇文章Android IPC机制(二)用Messenger进行进程间通信中我们介绍了使用Messenger来进行进程间通信的方法,但是我们能发现Messenger是以串行的方式来处理客户端发来的信息,如果有大量的消息发到服务端,服务端仍然一个一个的处理再响应客户端显然是不合适的.另外,Messenger用来进程间进行数据传递但是却不能满足跨进程的方法调用,接下来我们来使用AIDL来实现跨进程方法调用,此前我们都是用Eclipse来实现的,这次我们看看在Android Studio中使用AI

Android IPC机制(五)用Socket实现跨进程聊天程序

相关文章: Android IPC机制(一)开启多进程 Android IPC机制(二)用Messenger进行进程间通信 Android IPC机制(三)在Android Studio中使用AIDL实现跨进程方法调用 Android IPC机制(四)用ContentProvider进行进程间通信 1.Socket简介 Socket也称作"套接字",是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信.它分为流式套接字和

Android Service IPC通信之Messenger机制

概述 之前我写过一篇博客介绍Service:Android Service全面解析,里面讲过如何实现Service的跨进程(IPC)通信,主要是通过编写AIDL接口文件来实现的.本篇我们来讲讲Service IPC通信的另外一种方式-Messenger. Messenger,也称为信使,通过它可以在不同的进程间传递message对象,在message中放入我们需要传递的数据你就可以实现跨进程通信和传递数据了.所以说Messenger机制是基于消息的跨进程通信方式. 可以看到,我们可以在客户端发送

Android使用Messenger进行Service IPC通信分析

如果想要进行IPC通信,一般写一个AIDL接口,再写一个Service子类,然后实现AIDL接口 当做IBinder返回给Activity界面层. 如果不想写AIDL接口文件,只是单I线程中与Service进行通信 我们可以用Android写好的Messenger类来处理,一样能将消息传递给Service进行通信. 先写上基本代码: public class MyService extends Service { Messenger messenger = null; public MyServ