Android 进程间通信——Bundle、Message、ContentProvider

Android 进程间通信(三)——Bundle、Message、ContentProvider

在前面的文章Android进程间通信机制之AIDL中我们简单的介绍了AIDL的使用。在Android中我们还可以使用其他的进程间通信方式:

  • 使用Bundle携带数据进行进程间的通信;
  • 使用Message进行进程间的通信;
  • 使用文件进行进程间的通信;
  • 使用ContentProvider进行进程间的通信;

我们可以看到要实现进程间的通信方式有很多种,下面我们就来介绍一下使用它们如何实现进程间的通信。

1、利用Bundle在进程间进行通信

Bundle通常与Intent在一起使用,它们也常用于Activity、Service和Receiver之间的数据传递。

那么为什么Bundle可以在进程间传递数据,因为Bundle实现了Parcelable接口,拥有了在进程间传递数据的能力。

public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
    //......
}

它支持基本类型的数据和其他实现了Parcelable接口的对象的传递。在代码中使用它们:

在第一个Activity中:

 Intent intent = new Intent(this,OtherProcessActivity.class);
 Bundle extras = new Bundle();
 extras.putString("test0", "string");
 extras.putParcelable("parcelable", new Book(0, "bookName 0"));
 intent.putExtras(extras);
 startActivity(intent);

在跳转到的Activity中获取数据:

 Intent intent = getIntent();
 Bundle extras = intent.getExtras();
 String extrasString = extras.getString("test0");
 Book book = ((Book) extras.getParcelable("parcelable"));
 Log.e("bundle", extrasString + "  ===  " + book.toString());

如此,就完成了进程间数据的传递。

2、使用Messenger进行进程间的通信

Messenger即为信使,用来传递Message,Message是我们要传递的数据的载体。Messenger通过在不同的进程间传递Message就可以实现进程间的通信。

Messenger其实就是用AIDL来实现的,Messenger是对AIDL的封装,所以Messenger和AIDL其实是一样的。Google建议我们在Messenger和AIDL都可以使用的情况下优先使用Messenger,因为它使用起来更加便捷。

服务端

新建一个Service类,在内部创建一个Messenger对象,用来接收Client传递过来的信息,并在处理完信息后把结果反馈给Client。

服务端代码:

public class MessengerService extends Service {

    @SuppressLint("HandlerLeak")
    private Messenger mMessenger = new Messenger(new Handler() {
        @Override
        public void handleMessage(Message msg) {
            //创建一个Message用于存放发送回Client的消息
            Message sendToClientMsg = Message.obtain(msg);
            switch (msg.what) {
                case Constant.CLIENT_TO_SERVICE_WHAT:
                    try {
                        Bundle bundle = msg.getData();
                        bundle.setClassLoader(getClass().getClassLoader());
                        Log.e("debug", "从客户端来的消息:"+bundle.getParcelable("book").toString());
                        sendToClientMsg.what = Constant.SERVICE_TO_CLIENT_WHAT;
                        Bundle data = new Bundle();
                        data.putParcelable("bundle",new Book(1,"Service To Client book"));
                        sendToClientMsg.setData(data);
                        msg.replyTo.send(sendToClientMsg);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                default:
                    break;
            }
            super.handleMessage(msg);
        }
    });

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger != null ? mMessenger.getBinder() : null;
    }

}

客户端需要创建一个Messenger对象用来接收服务端反馈给客户端的数据,还要创建一个ServiceConnection对象获取Message对象用来向服务端发送数据。

public class OtherProcessActivity extends AppCompatActivity {
    public Messenger mServiceMessenger;

    @SuppressLint("HandlerLeak")
    Messenger mMessenger = new Messenger(new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case Constant.SERVICE_TO_CLIENT_WHAT:
                   Bundle bundle = msg.getData();
                    bundle.setClassLoader(getClass().getClassLoader());
                    Log.e("debug", "从服务端来的反馈"+msg.getData().getParcelable("bundle").toString());
                    break;
                default:
                    break;
            }
            super.handleMessage(msg);
        }
    });

    ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mServiceMessenger = new Messenger(service);
            Log.e("debug", "连接成功");
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mServiceMessenger = null;
            Log.e("debug", "连接断开");
        }
    };

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

        getData();
        bindMessengerService();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //解绑service
        unbindService(mConnection);
    }

    private void bindMessengerService() {
        Intent intent = new Intent(this, MessengerService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
        Log.e("debug", "服务已绑定");
    }

    private void getData() {
        Intent intent = getIntent();
        Bundle extras = intent.getExtras();
        String extrasString = extras.getString("test0");
        Book book = ((Book) extras.getParcelable("parcelable"));
        Log.e("bundle", extrasString + "  ===  " + book.toString());
    }

    public void sendMessage(View view) {
        if (mServiceMessenger == null) return;
        try {
            Message message = Message.obtain();
            message.what = Constant.CLIENT_TO_SERVICE_WHAT;
//            message.obj = new Book(2, "Client-To-Service Book");
            Bundle bundle = new Bundle();
            bundle.putParcelable("book", new Book(2, "Client-To-Service Book"));
            message.setData(bundle);
            //不要忘记此处代码
            message.replyTo = mMessenger;
            mServiceMessenger.send(message);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

注意:

1、在sendMessage()方法中,如果我们需要在服务端反馈结果给客户端那么必须要写上这行代码message.replyTo = mMessenger;,否则在服务端会报空指针异常。

2、在使用Bundle传递Parcelable数据,正常情况下我们就收数据时会报错ClassNotFoundException异常。对于这种情况我们要重新设置类加载方式。

Android有两种ClassLoader:FrameWork ClassLoader和APK ClassLoader,FrameWork加载Android的class,APK可以加载用户自定义的类,apk继承自FrameWork所以都可以加载。在应用刚启动时,默认启用APK ClassLoader。当系统被回收,默认会变成FrameWork classloader。

所以我们在获取数据前需要设置一下类加载方式:

   Bundle bundle = msg.getData();
   bundle.setClassLoader(getClass().getClassLoader());

3、跨进程通信方式不支持msg.obj。

3、使用File的方式进行进程间的通信

通过读取SD卡中的文件,获取不同进程的文件数据。

4、使用ContentProvider进行进程间通信

ContentProvider的底层也是用Binder实现的。

通过服务端的ContentProvider建立数据库,然后向外提供接口,在客户端使用getContentResolver()获取到的ContentResolver去对数据库进行增删改查的操作,以此来实现进程间的通信。

代码实现:

1. 继承SQLiteOpenHelper创建DbHelper类

ContentProvider与ContentResolver之间的通信其实是通过操作数据表实现的,所以先创建一个DBHelper类。

public class ProviderDbHelper extends SQLiteOpenHelper {
    private static final String DB_NAME = "ipc_provider.db";
    private static final String TABLE_NAME = "book";
    private static final int DB_VERSION = 1;

    private final String SQL_CREATE_TABLE = "create table if not exists " + TABLE_NAME + " (_id integer primary key, name TEXT, description TEXT)";

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

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

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

    }
}

2. 继承ContentProvider创建自定义Provider

ContentProvider使用Uri标识数据,它包括两个部分:

  1. anthority:符号名称
  2. path:指向表的路径/名称

内容Uri的形式为:

content://authority/path

在我们使用ContentResolver方法来访问ContentProvider的数据表时,需要传递URI来区分要操作的内容。Android中提供了UriMatcher来解析Uri。

UriMatcher用来匹配Uri:

  1. 注册需要匹配的Uri路径,可以添加匹配码;
  2. 使用uriMatcher.match(uri)方法对输入的uri进行匹配,使用返回的匹配码进行匹配。
public class IPCProvider extends ContentProvider {
    //ContentProvider 的授权字符串
    public static final String AUTHORITY = "com.kanlulu.ipc_contentprovider.provider.IPCProvider";
    // 内容 URI 用于在 ContentProvider 中标识数据的 URI,可以使用 content:// + authority 作为 ContentProvider 的 URI
    public static final Uri uri = Uri.parse("content://" + AUTHORITY + "/book");

    //在 ContentProvider 中可以通过 UriMatcher 来为不同的 URI 关联不同的 code,便于后续根据 URI 找到对应的表
    private static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    public static final int CODE_BOOK = 1;

    static {
        uriMatcher.addURI(AUTHORITY, "book", CODE_BOOK);
    }

    public Context mContext;
    public ProviderDbHelper dbHelper;
    public SQLiteDatabase mDatabase;
    public String mTableName;

    @Override
    public boolean onCreate() {
        mContext = getContext();
        initProvider();
        return false;
    }

    private void initProvider() {
        mTableName = ProviderDbHelper.TABLE_NAME;
        dbHelper = new ProviderDbHelper(mContext);
        mDatabase = dbHelper.getWritableDatabase();

        new Thread(new Runnable() {
            @Override
            public void run() {
                mDatabase.execSQL("delete from " + mTableName);
                mDatabase.execSQL("insert into " + mTableName + " values(1,'test_book_name','test_book_desc')");
            }
        }).start();

    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        String tableName = getTableName(uri);
        Log.e("debug", tableName + " 查询数据");
        return mDatabase.query(tableName, projection, selection, selectionArgs, null, sortOrder, null);
    }

    private String getTableName(Uri uri) {
        String tableName = "";
        int match = uriMatcher.match(uri);
        switch (match) {
            case CODE_BOOK:
                tableName = ProviderDbHelper.TABLE_NAME;
        }
        return tableName;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        String tableName = getTableName(uri);
        Log.e("debug", tableName + " 插入数据");
        mDatabase.insert(tableName, null, values);
        mContext.getContentResolver().notifyChange(uri, null);
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        String tableName = getTableName(uri);
        Log.e("debug", tableName + " 删除数据");
        int deleteCount = mDatabase.delete(tableName, selection, selectionArgs);
        if (deleteCount > 0) {
            mContext.getContentResolver().notifyChange(uri, null);
        }
        return deleteCount;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        String tableName = getTableName(uri);
        int updateCount = mDatabase.update(tableName, values, selection, selectionArgs);
        if (updateCount > 0) {
            mContext.getContentResolver().notifyChange(uri, null);
        }
        return updateCount;
    }
}

ContentProvider是Android中的四大组件之一,我们还需要在AndroidManifest.xml文件中声明provider标签:

<provider
            android:name=".provider.IPCProvider"
            android:authorities="com.kanlulu.ipc_contentprovider.provider.IPCProvider"
            android:exported="false"
            android:grantUriPermissions="true"
            android:process=":ipc_provider" />

3. 在其他进程中通过getContentResolver对数据表进行增删改查

public class MainActivity extends AppCompatActivity {

    public TextView mQueryResult;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mQueryResult = (TextView) findViewById(R.id.tv_query_result);
    }

    public void insert(View view) {
        ContentResolver contentResolver = getContentResolver();
        ContentValues contentValues = new ContentValues();
        int id = (int) (Math.random() * 100);
        contentValues.put("_id", id);
        contentValues.put("name", "book-name-" + id);
        contentValues.put("description", "book-description-" + id);
        contentResolver.insert(IPCProvider.uri, contentValues);
    }

    public void query(View view) {
        mQueryResult.setText("");
        StringBuilder sb = new StringBuilder();
        ContentResolver contentResolver = getContentResolver();
        Cursor cursor = contentResolver.query(IPCProvider.uri, new String[]{"name", "description"}, null, null, null);
        if (cursor == null) return;
        while (cursor.moveToNext()) {
            String result = cursor.getString(0) + " === " + cursor.getString(1);
            Log.e("debug", result);
            sb.append(result).append("\n");
        }
        mQueryResult.setText(sb.toString());
        cursor.close();
    }
}

如此便完成了使用ContentProvider进行进程间的通信。

你可能感兴趣的:(android学习笔记)