Androidプロセス間通信——Bundele、Message、Contect Provider


Androidプロセス間通信(三)——Bunndle、Message、Contect Provider
前の記事ではAndroidプロセス間通信機構のAIDLにおいてAIDLの使用を簡単に紹介した.Androidでは他のプロセス間通信方式も使用できます.
  • Bunndleを使用してデータを携帯してプロセス間の通信を行います.
  • は、Messageを使用してプロセス間の通信を行う.
  • ファイルを使用してプロセス間の通信を行う.
  • ContentProviderを使用してプロセス間の通信を行う.
  • プロセス間の通信方式を実現するには様々な種類がありますが、それらを使ってプロセス間の通信をどのように実現するかを紹介します.
    1、Bundeleを利用してプロセス間で通信する
    Bundeleは通常Intentと一緒に使用され、Activity、Service、Receiver間のデータ転送にもよく使われます.
    なぜBunndleはプロセス間でデータを伝えることができますか?BundeleはPacerbaleインターフェースを実現しました.プロセス間でデータを伝える能力を持っています.
    public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
        //......
    }
    
    基本タイプのデータと他のPacerbaleインターフェースを実現したオブジェクトの転送をサポートします.コードにそれらを使う:
    最初の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はメッセンジャーとして、メッセンジャーを伝えるために使用されます.メッセンジャーは私たちが伝えるべきデータのキャリアです.Messengerは、プロセス間でメッセージを送ることで、プロセス間の通信が可能になります.
    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オブジェクトを作成します.
    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、Bunndleを使ってParcell ableデータを転送します.正常な状況でデータを受け取る時、Class NotFoundExceptionの異常を報告します.このような状況に対して、クラスローディング方式を再設定します.
    Androidには2種類のClass Loaderがあります.Frame eWork Class LoaderとAPK Class Loader、Frame WorkにはAndroidのclassをロードします.アプリケーションが起動した直後に、デフォルトではAPKクラスLoaderが有効になります.システムが回収されるとデフォルトはFrame Work classiderになります.
    データを取得する前に、クラスローディング方式を設定する必要があります.
       Bundle bundle = msg.getData();
       bundle.setClassLoader(getClass().getClassLoader());
    
    3、プロセスをまたぐ通信方式はmsg.objをサポートしていません.
    3、Fileを使ってプロセス間の通信を行う
    SDカードのファイルを読み込むことで、異なるプロセスのファイルデータを取得します.
    4、ContentProviderを使ってプロセス間通信を行う
    Contect Providerの下層もBinderで実現しました.
    サーバー側のContentProviderを通じてデータベースを構築し、外部にインタフェースを提供し、クライアント側でgetContinitResolaver()で取得したConnect Resolaverを使ってデータベースを添削して調べる操作を行い、プロセス間の通信を実現します.
    コードの実装:
    1.SQLiteOpenHelperを継承してDbHelper類を作成する
    ContentProviderとContentResolaverの間の通信は、データテーブルを操作することによって実現されるので、まず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標識データを使用しています.これは二つの部分を含みます.
  • anthorty:符号名
  • パス/名前
  • 内容Uriの形式は:content://authority/pathContentResoloverを使ってConttentProviderのデータテーブルにアクセスするときは、URIを伝えて操作内容を区別する必要があります.AndroidではUriMatchを提供してUriを解析します.
    UriMatchはUriにマッチするために使用されます.
  • 登録するには、マッチするUriパスが必要です.マッチコードを追加することができます.
  • は、入力されたuriを、uriMatch.match(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の4つのコンポーネントの一つです.また、Android dManifest.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.他のプロセスでget ContentResoliverによってデータ表を添削して調べる.
    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("
    "
    ); } mQueryResult.setText(sb.toString()); cursor.close(); } }
    これでContentProviderを使ってプロセス間の通信が完了しました.