Android多进程机制(四)其它IPC方式

2018-06-23  本文已影响10人  Utte

使用Bundle

Activity、Service、BroadcastReciver都支持在Intent中传递Bundle数据。

可以通过这样的方式传输。

Bundle bundle = new Bundle();
bundle.putChar("char", 'a');
Intent intent = new Intent();
intent.putExtra("bundle", bundle);

Intent中的putExtra()系列的方法其实内部也是通过Bundle来封装数据的。

public @NonNull Intent putExtra(String name, double value) {
    if (mExtras == null) {
        mExtras = new Bundle();
    }
    mExtras.putDouble(name, value);
    return this;
}

使用文件共享

两个进程通过读写同一个文件实现通信。

Android系统是基于Linux的,并发读写文件没有任何限制,两个线程同时对同一个文件进行写操作都是允许的,尽管可能出问题。

共享文件的方式对文件格式没有要求,可以是文本文件,也可以使用XML文件来存键值对。如果是对象,同样要求序列化,交换对象时可以使用ObjectInputStream和ObjectInputStream。

SharedPreferences特殊

SharedPreferences也是属于文件的一种,但是系统对它的读写有一定的缓存策略,即内存中会有一份SharedPreferences文件的缓存,并发读写时会有很大几率丢失数据,所以不推荐使用SharedPreferences进程间通信。

适用场景

文件共享适用于对数据同步要求不高的进程之间进行通信,并且要妥善处理并发读写的问题。

使用Socket

Socket分为流式套接字和数据报套接字,也就是TCP和UDP。

使用网络请求其实就使用了Socket,设备间通信,自然也就是进程间通信了。

使用Socket通信一定要声明权限,不管是否真的需要网络。

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

使用Messenger

Messenger可以在不同的进程中传递Message对象,是一种轻量级的IPC方案,它的底层实现就是AIDL。

但是Messenger是以串行的方式处理消息的,如果有并发请求,就不太合适了。同时Messenger主要用于传递消息,没有办法实现跨进程调用服务端的方法。

可以从两个构造器看出AIDL的影子。

public Messenger(Handler target) {
    mTarget = target.getIMessenger();
}

public Messenger(IBinder target) {
    mTarget = IMessenger.Stub.asInterface(target);
}

Messenger对AIDL进行了封装,使得可以更简单的使用IPC。它一次只处理一个请求,所以在服务端不需要考虑线程同步。

实现步骤

1. 服务端进程

2. 客户端进程

如果需要服务端能回应客户端,那么还需要在客户端创建一个Handler,从Handler中获取一个新的Messenger,并把这个Messenger通过Message的replyTo发送给服务端,服务端通过这个replyTo的参数回应客户端。

代码实现

1. 服务端

public class MessengerService extends Service {
    private static final String TAG = "MessengerService";
    //根据Handler对象创建Messenger对象
    private final Messenger mMessenger = new Messenger(new MessengerHandler());

    //定义一个Handler
    private static class MessengerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                //接收到客户端消息时打印
                case MSG_FROM_CLIENT:
                    Log.d(TAG, "handleMessage: " + msg.getData().getString("msg"));
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    //onBind返回Messenger对象底层的Binder
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }
}
<service android:name=".MessengerService"
    android:process=":remote"/>

2. 客户端

public class MessengerActivity extends AppCompatActivity {
    private Messenger mMessenger;
    private Message mMessage;
    private static final String TAG = "MessengerActivity";
    
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //连接时使用返回的IBinder创建Messenger
            mMessenger = new Messenger(service);
            //创建装消息的Message
            mMessage = Message.obtain(null, Constants.MSG_FROM_CLIENT);
        }

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_messenger);
        Intent intent = new Intent(this, MessengerService.class);
        //绑定Service
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        //Activity销毁时解除绑定
        unbindService(mConnection);
        super.onDestroy();
    }

    public void onClick(View view) {
        Bundle data = new Bundle();
        data.putString("msg", "hello from client");
        //将需要传递的数据装入Message
        mMessage.setData(data);
        try {
            Log.d(TAG, "onClick: " + "send");
            //通过Messenger发送Message
            mMessenger.send(mMessage);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

验证正确性的时候千万不要选择log过滤为Show only selected application,因为Service不在当前进程,会被过滤掉。

新增服务端可回应的需求

1. 服务端修改

只需要修改Handler,取出Messenger,发送回复内容。

private static class MessengerHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            //接收到客户端消息时打印
            case MSG_FROM_CLIENT:
                Log.d(TAG, "handleMessage: " + msg.getData().getString("msg"));
                //从客户端发送的Message中拿到回复的Messenger
                Messenger clientMessenger = msg.replyTo;
                //创建回复的Message
                Message replyMessage = Message.obtain(null, MSG_FROM_SERVER);
                Bundle replyData = new Bundle();
                replyData.putString("msg", "reply from server.");
                //装进Message
                replyMessage.setData(replyData);
                try {
                    //发送回复
                    clientMessenger.send(replyMessage);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
            default:
                super.handleMessage(msg);
        }
    }
}

2. 客户端修改

  1. 创建一个处理回复消息的Handler
private static class MessengerHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_FROM_SERVER:
                Log.d(TAG, "handleMessage: " + msg.getData().getString("msg"));
                break;
            default:
                super.handleMessage(msg);
        }
    }
}
  1. 创建回复的Messenger、用replyTo装进Message并发送。
private Messenger mReplyMessenger = new Messenger(new MessengerHandler());
mMessage.replyTo = mReplyMessenger;

使用ContentProvider

ContentProvide用于在不同应用之间进行数据共享,底层实现也是Binder。

实现一个ContentProvider

自定义一个ContentProvider只需要继承ContentProvider并实现它的六个抽象方法。

如果我们应用不关注,就可以直接返回null或者/

这六个方法都运行在ContentProvier的进程中,除了onCreate由系统回调,运行在主线程,其他都是由外界调用的,运行在Binder线程中。

public class BookProvider extends ContentProvider {
    private static final String TAG = "BookProvider";
    @Override
    public boolean onCreate() {
        Log.d(TAG, "onCreate: " + Thread.currentThread());
        return false;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        Log.d(TAG, "query: " + Thread.currentThread());
        return null;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        Log.d(TAG, "getType: " + Thread.currentThread());
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        Log.d(TAG, "insert: " + Thread.currentThread());
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.d(TAG, "delete: " + Thread.currentThread());
        return 0;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.d(TAG, "update: " + Thread.currentThread());
        return 0;
    }
}

ContentProvider支持表格形式和文件数据,MediaStore就是文件类型的ContentProvider。ContentProvider对底层的数据存储没有任何要求,可以使用SQLite、普通文件、甚至可以是内存中的一个对象。

Manifest中注册这个ContentProvider。

<provider
    android:name=".provider.BookProvider"
    android:authorities="com.utte.aidltest.provider.BookProvider"
    android:permission="com.utte.PROVIDER"
    android:process=":provider" />

authorities是ContentProvider的唯一标识,必须是唯一的。可以使用permission属性给ContentProvider指定权限,外界想要访问这个ContentPovider就必须要声明这个permission。权限还可以细分为读权限和写权限,如下,如果需要对ContentProvider进行写操作就需要声明com.utte.write,读操作需要com.utte.read

android:writePermission="com.utte.write"
android:readPermission="com.utte.read"

为了测试,我们声明这个ContentProvider在provider进程中。

访问BookProvider

public class BookProviderActivity extends AppCompatActivity {

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

    }

    public void onClick(View view) {
        Uri uri = Uri.parse("content://com.utte.aidltest.provider.BookProvider");
        getContentResolver().query(uri, null, null, null, null);
        getContentResolver().query(uri, null, null, null, null);
    }
}

uri中的值就是authorities的值,ContentProvider的唯一标识。

看到打印的是这样的,证明了onCreate()运行在main线程,而query()等方法是在Binder线程池中的,所以onCreate()中不能做耗时操作。

06-23 11:28:57.426 18292-18292/com.utte.aidltest:provider D/BookProvider: onCreate: Thread[main,5,main]
06-23 11:28:57.431 18292-18306/com.utte.aidltest:provider D/BookProvider: query: Thread[Binder:18292_3,5,main]
06-23 11:28:57.434 18292-18305/com.utte.aidltest:provider D/BookProvider: query: Thread[Binder:18292_2,5,main]

添加数据

1. 数据库管理类

public class DBBookHelper extends SQLiteOpenHelper {
    private static final String DB_NAME = "book_provider.db";
    private static final int DB_VERSION = 1;

    public static final String BOOK_TABLE_NAME = "book";
    public static final String USER_TABLE_NAME = "user";

    private String CREATE_BOOK_TABLE = "CREATE TABLE IF NOT EXISTS " + BOOK_TABLE_NAME + "(_id INTEGER PRIMARY KEY, name TEXT)";
    private String CREATE_USER_TABLE = "CREATE TABLE IF NOT EXISTS " + USER_TABLE_NAME + "(_id INTEGER PRIMARY KEY, name TEXT, sex INT)";

    public DBBookHelper(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK_TABLE);
        db.execSQL(CREATE_USER_TABLE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

2. Uri UriCode关联

private static final String TAG = "BookProvider";
public static final String AUTHORITY = "com.utte.aidltest.provider.BookProvider";

public static final Uri BOOK_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/book");
public static final Uri USER_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/user");

public static final int BOOK_URI_CODE = 0;
public static final int USER_URI_CODE = 1;

private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
    sUriMatcher.addURI(AUTHORITY, "book", BOOK_URI_CODE);
    sUriMatcher.addURI(AUTHORITY, "user", USER_URI_CODE);
}

private String getTableName(Uri uri) {
    String tableName = null;
    switch (sUriMatcher.match(uri)) {
        case BOOK_URI_CODE:
            tableName = DBBookHelper.BOOK_TABLE_NAME;
            break;
        case USER_URI_CODE:
            tableName = DBBookHelper.USER_TABLE_NAME;
            break;
    }
    return tableName;
}

外界需要通过Uri来指定要访问的是数据库的什么信息,使用UriMatcher将Uri和UriCode绑定,写方法通过Uri获取对应的表名。

3. 初始数据

@Override
public boolean onCreate() {
    Log.d(TAG, "onCreate: " + Thread.currentThread());
    mContext = getContext();
    initData();
    return true;
}
private void initData() {
    mDatabase = new DBBookHelper(mContext).getWritableDatabase();
    mDatabase.execSQL("insert into book values(0, 'Android');");
    mDatabase.execSQL("insert into user values(0, 'pppig');");
}

其实添加数据这种数据库操作是属于耗时的,并不建议在onCreate()中调用。

4. 实现query()

public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortO
    Log.d(TAG, "query: " + Thread.currentThread());
    String table = getTableName(uri);
    return mDatabase.query(table, projection, selection, null, null, sortOrder, null);
}

通过上面写的getTableName()获得到表名,再进行数据库操作。

5. 实现增删改

public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
    Log.d(TAG, "insert: " + Thread.currentThread());
    String table = getTableName(uri);
    mDatabase.insert(table, null, values);
    mContext.getContentResolver().notifyChange(uri, null);
    return null;
}

和上面query()的实现非常相似,唯一不同的就是insert()对数据库进行了更改,所以需要通知外界数据已更改,外界可以通过registerContentObserver()来注册观察者。

其他delete()和update()都和insert()非常类似。

注意点

增删改查四个方法是存在多线程并发访问的,所以内部需要做好线程同步的工作。这里的一个SQLiteDatabase内部对数据库的操作是有同步处理的,但是如果有个SQLiteDatabase就无法进行线程同步。如果数据是内存中的List,那么也是需要自己做好线程同步工作的。

IPC方式比较

方法 优点 缺点 适用场景
Bundle 使用简单 只能传输Bundle支持的类型 四大组件IPC
文件共享 使用简单 不适合高并发且无法做到及时通信 无并发、交换简单实时性不高
AIDL 支持并发,支持及时通信 使用稍复杂,需要处理好线程同步 一对多且有RPC需求
Messenger 支持及时通信 不能处理并发、只能传输Bundle支持的数据类型 无RPC需求,低并发一对多即时通信
ContentProvider 在数据源访问方面强大,支持一对多并发数据共享 可理解为受约束的AIDL,只提供CRUD操作 一对多进程间数据共享
Socket 支持一对多并发实时通信 使用稍微繁琐 网络数据交互

RPC就是远程调用。

上一篇 下一篇

猜你喜欢

热点阅读