• IPC机制2


    1、使用Messenger

     Messenger可以翻译为信使,通过它可以在不同进程中传递messenge对象,在messenge中放入我们需要传递的数据,就可以轻松实现数据在进程中传递。

     服务段进程:

      需要在服务端创建一个Service来处理客户端的连接需求,同时创建一个Handler并通过它来创建一个Messenger对象,然后在Service的onBind中返回这个Messenger对象底层的Binder即可。

      如果需要回复给客户端消息,可以通过Messange的replyTo参数创建一个Messenger,然后再创建一个想要传递的messenge,再然后使用Messenger传递这个messenge

    public class MyService extends Service {
        private static final String TAG = "MessengerService";
    
        private static class MessengerHandler extends Handler{
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what){
                    case 0:
                //打印接收到的Message对象的内容 Log.i(TAG,
    "receive msg form Client:" + msg.getData().getString("msg"));
                
                //使用接收到的messenge对象的replyTo参数创建Messenger进行回复 Messenger client
    = msg.replyTo; Message replyMessage = Message.obtain(null,1); Bundle bundle = new Bundle(); bundle.putString("reply","已收到消息"); replyMessage.setData(bundle);
    try{ client.send(replyMessage); }catch (RemoteException e){ e.printStackTrace(); } break; default: super.handleMessage(msg); } } } private final Messenger mMessenger = new Messenger(new MessengerHandler()); @Nullable @Override public IBinder onBind(Intent intent) { return mMessenger.getBinder(); } }

      客户端进程:

        先绑定服务端的Service,绑定成功后使用服务端返回的使用服务端返回的IBinder创建Messenger并使用其发送消息。

        如果想要接受服务端的回复同样需要创建一个Handler并创建新的Messenger,并把这个Messenger通过replyTo传递给服务端

    public class MainActivity extends AppCompatActivity {
    
        private static final String TAG = "MessengerService";
    
        //服务端传来的Messenger
        private Messenger mService;
    
        //客户端的Messnger
        private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());
    
        private static class MessengerHandler extends Handler{
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what){
                    case 1:
                        //打印服务端返回的消息
                        Log.i(TAG, "receive msg from Service:" + msg.getData().getString("reply"));
                        break;
                    default:
                        super.handleMessage(msg);
                }
            }
        }
        private ServiceConnection mConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
    
                //使用服务端返回的IBinder创建Messenger并使用其发送消息
                mService = new Messenger(iBinder);
                Message msg = Message.obtain(null,0);
                Bundle data = new Bundle();
                data.putString("msg","hello this is client");
                msg.setData(data);
    
                //将客户端的Messenger通过replyTo传递给服务端
                msg.replyTo = mGetReplyMessenger;
    
                try{
                    mService.send(msg);
                }catch (RemoteException e){
                    e.printStackTrace();
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName componentName) {
    
            }
        };
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Intent intent = new Intent(this,MyService.class);
            //绑定Service
            bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
        }
    
        @Override
        protected void onDestroy() {
            unbindService(mConnection);
            super.onDestroy();
        }
    }

    2、使用AIDL

      上面的Messenger是以串行的方式处理客户端发来的消息,如果大量的消息同时发送到服务端,服务端仍然只能一个一个的处理,这种就可以使用AIDL

      首先是创建AIDL接口(Book类,Book.aidl,IBookManager.aidl 具体代码可以看IPC机制1

        在AIDL文件中,并不是所有的数据类型都是可以使用的,支持的类型有:

      • 基本类型(int、long、char、boolean等)
      •   List:只支持ArrayList,且每个元素都必须被AIDL支持
      • Map:只支持HashMap,且每个元素都必须被AIDL支持,包括key和value;
      • Parcelable:所有实现了Parcelable接口的对象
      •   AIDL:所有的AIDL接口本身也可以在AIDL中使用,

        以上为AIDL所支持的所有类型,其中现了Parcelable接口的对象和AIDL对象必须显式的important进来,不管它们是否与当前文件位于同一包内。

        如果AIDL文件用到 了自定义的Parcelable对象,就必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型。

        还要注意的是,AIDL中除了基本数据类型,其他类型的参数必须标上方向:in、out或者inout,in表示输入型参数,out表示输出型参数,inout表示输入输出型参数。

        最后,AIDL接口中只支持方法,不支持声明静态变量。

      然后是远程服务端的Service的实现:

        上面只是定义了接口,现在就需要实现这个接口:

    public class BookManagerService extends Service {
    
        private static final String TAG = "BMS";
    
        private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();
    
        private Binder mBinder = new IBookManager.Stub() {
            @Override
            public List<Book> getBookList() throws RemoteException {
                return mBookList;
            }
    
            @Override
            public void addBook(Book book) throws RemoteException {
                mBookList.add(book);
            }
    
        };
    
        @Override
        public void onCreate() {
            super.onCreate();
            mBookList.add(new Book(1,"Android"));
            mBookList.add(new Book(2,"ios"));
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            return mBinder;
        }
    }

      这里采用了CopyOnWriteArrayList,这是因为CopyOnWriteArrayList支持并发读写,类似的还有ConcurrentHashMap,这是因为AIDL方法是在服务端的Binder线程池中执行的,因此当多个客户端同步连接的时候,会出现多线程同时访问的情形。

      再然后就是客户端的实现:

    public class BookManagerActivity extends AppCompatActivity {
    
        private static final String TAG = "BMS";private ServiceConnection mConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
                IBookManager bookManager = IBookManager.Stub.asInterface(iBinder);
                try {
                    List<Book> list = bookManager.getBookList();
                    Log.i(TAG,"query book list, list type:" + list.getClass().getCanonicalName());
                    for (Book book:list) {
                        Log.i(TAG, "query book list: [book id:" + book.bookId + " bookName:" +  book.bookName + "]");
                    }
                }catch (RemoteException e){
                    e.printStackTrace();
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName componentName) {
            }
        };
    
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_book_manager);
            Intent intent = new Intent(this, BookManagerService.class);
            bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
        }
    
        @Override
        protected void onDestroy() {
            unbindService(mConnection);
            super.onDestroy();
        }
    }

      Log输出如下:

    12-07 14:48:48.516 9198-9198/com.example.administrator.test I/BMS: query book list, list type:java.util.ArrayList
    12-07 14:48:48.516 9198-9198/com.example.administrator.test I/BMS: query book list: [book id:1 bookName:Android]
    12-07 14:48:48.516 9198-9198/com.example.administrator.test I/BMS: query book list: [book id:2 bookName:ios]

      可以发现,虽然我们在服务端返回的是CopyOnWriteArrayList,但是客户端收到的却是ArrayList.这是因为AIDL中支持的是抽象的List,而List只是一个接口,在Binder中是按照List的规范去访问数据,并最终返回ArrayList给客户端。

    3、使用ContentProvider

      ContentProvider是Android中提供的专门用于不同应用间进行数据共享的方式。不过ContentProvider的底层实现同样也是Binder。

      首先我们需要创建一个继承ContentProvider的类,这里我命名为BookProvider,具体代码如下:

    public class BookProvider extends ContentProvider {
    
        private static final String TAG = "BookProvider";
        @Override
        public boolean onCreate() {
            Log.d(TAG, "onCreate ,current thread:" + Thread.currentThread().getName());
            return false;
        }
    
        @Nullable
        @Override
        public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
            Log.d(TAG, "query ,current thread:" + Thread.currentThread().getName());
            return null;
        }
    
        @Nullable
        @Override
        public String getType(@NonNull Uri uri) {
            Log.d(TAG, "getType" );
            return null;
        }
    
        @Nullable
        @Override
        public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
            Log.d(TAG, "insert" );
            return null;
        }
    
        @Override
        public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
            Log.d(TAG, "delete" );
            return 0;
        }
    
        @Override
        public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
            Log.d(TAG, "update" );
            return 0;
        }
    
    }

      想要实现一个自定义的ContentProvider类需要实现上面6个抽象方法:

    • onCreate代表 ContentProvider的创建,在这个方法中做一些初始化的工作
    • getType用来返回一个Uri请求所对应的MIME类型(媒体类型),如果我们的应用不关心这个选项可以直接返回null或是"*/*",
    • query、insert、delete、update就分别对应对数据表的增删改查功能

      这里的除了onCreate方法由系统回调并运行在主线程里,其他五个方法由外界回调并运行在Binder线程池中。

      定义了一个这样的类之后,还需要在AndroidManifest中注册这个ContentProvider

     <provider
                android:name="provider.BookProvider"
                android:authorities="com.xw.provider"
                android:permission="com.xw.test.BookProvider"
                android:process="book.test"
                android:exported="true">
            </provider>        
     

      其中前四个属性都是任意指定,android:exported表示是否允许外部程序访问ContentProvider,不过android:authorities是ContentProvider的唯一标识,外部应用就是通过这个属性来访问ContentProvider,所以android:authorities必须是唯一的。android:permission给我们的ContentProvider添加了权限,外部应用如果想要访问这个ContentProvider就必须声明这个"com.xw.test.BookProvider"权限,还要注意我们还需要这个权限属于我们自定义的所以需要加上:

        <permission android:name="com.xw.test.BookProvider"
                        android:protectionLevel="normal"/>

      接下来是客户端,首先是同应用的一个Activity:

    public class ProviderActivity extends AppCompatActivity {
    
        private static final String TAG = "BookProvider";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_provider);
            Log.d(TAG, "ProviderActivity,onCreate ,current thread:" + Thread.currentThread().getName());
            Uri uri = Uri.parse("content://com.xw.provider");
            getContentResolver().query(uri,null,null,null,null);
        }
    }

      ContentProvider是通过Uri来指定使用的,这个Uri被称作内容Uri,内容Uri给ContentProvider中的数据提供了唯一的标识符,通常就是"content://"+authorities+表名。这里我们就可以直接指定为"content://com.xw.provider"。

      这里访问ContentProvider使用的是调用getContentResolver(),这个方法能得到一个ContentResolver类,通过这个ContentResolver类和Uri就能使用ContentProvider的query、insert、delete、update、getType五个方法。

      这里的输出是:

    12-11 09:57:21.977 5551-5551/com.example.administrator.test D/BookProvider: ProviderActivity,onCreate ,current thread:main
    12-11 09:57:22.079 5590-5590/book.test D/BookProvider: BookProvider,onCreate ,current thread:main
    12-11 09:57:22.082 5590-5629/book.test D/BookProvider: query ,current thread:Binder:5590_3

      从输出我们可以看出ContentProvider的onCreate方法是运行在主线程里的。

      然后我们再创建一个新的应用测试一下真正的跨应用使用这个ContentProvider:

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Uri uri = Uri.parse("content://com.xw.provider");
            getContentResolver().query(uri,null,null,null,null);
        }
    }

      Activity的代码和刚才一样,还要注意在AndroidManifest中加入权限:

        <uses-permission android:name="com.xw.test.BookProvider"/>

      输出:

    12-11 10:50:54.304 16073-16073/? D/BookProvider: ProviderActivity,onCreate ,current thread:main
    12-11 10:50:54.431 16092-16092/? D/BookProvider: BookProvider,onCreate ,current thread:main
    12-11 10:50:54.434 16092-16105/? D/BookProvider: query ,current thread:Binder:16092_2

      如果是具体使用一个数据库的话,看这个android——实现跨程序访问数据

  • 相关阅读:
    jdbctemplate的batchUpdate使用方法
    js利用html5的canvas实现图像等比例压缩
    js前台通过EXIF.js获取图片中携带的经纬度信息
    Linux expr相关
    expect用于scp传输文件
    linux 自动登录ftp 获取文件
    作业8:单元测试练习(个人练习)
    作业7-用户体验设计案例分析
    作业6:团队作业——学生成绩录入系统设计与实现
    作业5:需求分析
  • 原文地址:https://www.cnblogs.com/xxbbtt/p/7999345.html
Copyright © 2020-2023  润新知