Android中的IPC方式

3 篇文章 0 订阅
3 篇文章 0 订阅

前面我们介绍了Serializable和Parcelable的基础知识,以及Binder的相关内容。本篇,开始分析各种跨进程通信的方式,Intent中附加extra信息,共享文件,Binder。另外ContentProvider天生就是支持跨进程访问的。此外,网络通信也是跟进程无关,所以Socket也可以实现IPC。
ipc的BookSevice和MainActivity

Bundle

Android中四大组件中的三个组件(Activity、Service、Receiver)都是支持在Intent中传递数据的,Intent和Bunlde都实现了Parcelable接口,都支持跨进程通信,因为Intent.putExtra内部就是用Bundle实现的。

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

这个跟平时一个进程中的写法都一样,可以说无感觉切换O(∩_∩)O
例子可见Bundle跨进程

文件共享

文章共享可参考前面关于serializable和Parcelable时的内容。可以实现进程间通信,因为毕竟是文件,跟进程无关,可是在处理并发时问题很严重,比如,一个进程写了一半儿,另一个进程读取了;或者一个进程写,另一个也写,那你说,同一个文件出来的是个啥?啥也不是!
Android中的SharedPreferences是个特例,虽然最终是在xml文件中,可是内部还有内存级的缓存,所以多进程模式下,数据极不可靠。不建议多进程使用。

Messenger

Messenger的底层其实也是AIDL。一次处理一个请求,所以不用考虑同步。

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

简单实用方式如下:
Service

public class MessengerService extends Service {
    private final static String TAG = "MessengerService";
    private Handler serverHanlder = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case MyConstants.MSG_FROM_CLIENT:
                    Log.d(TAG, "handleMessage: receive msg  "+msg.getData().getString("msg"));
                    Messenger clientMessenger = msg.replyTo;
                    Message obtain = Message.obtain(null, MyConstants.MSG_FROM_SERVER);
                    Bundle bundle = new Bundle();
                    bundle.putString("msg","Hi,This is from server.");
                    obtain.setData(bundle);
                    try {
                        clientMessenger.send(obtain);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
            }
        }
    };
    private Messenger serverMessenger = new Messenger(serverHanlder);
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return serverMessenger.getBinder();
    }
}

Activity

public class MessengerActivity extends AppCompatActivity {
    private static final String TAG = "MessengerActivity";
    private Handler clientHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case MyConstants.MSG_FROM_SERVER:
                    Log.d(TAG, "handleMessage: receive msg "+msg.getData().getString("msg"));
                    break;
            }
        }
    };
    private Messenger clientMessenger = new Messenger(clientHandler);

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Messenger serviceMessenger = new Messenger(service);
            Message message = Message.obtain(null, MyConstants.MSG_FROM_CLIENT);
            Bundle bundle = new Bundle();
            bundle.putString("msg", "Hello,This is client.");
            message.setData(bundle);
            message.replyTo = clientMessenger;
            try {
                serviceMessenger.send(message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_messenger);
        bindService(new Intent(this, MessengerService.class), mServiceConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        unbindService(mServiceConnection);
        super.onDestroy();
    }
}

Messenger使用message传输数据,Message的what、arg1、arg2、obj、Bundle以及replyTo都可以充当数据载体。有人就说了,你看你,有那么多简单的不用,非用一个Bundle,别急,咱们来仔细看一下。
- what 就不说了,主要是区分不同消息
- arg1、arg2 如果是int类型时的简写形式
- obj 只能是Framework中的Parcelable类型的参数,自定义的不行
- replyTo 针对Messenger
- Bundle 所以你看,还能怎么办,有用的就不错了,走起来吧

借用下开发艺术探索的原理图

1502959385(1).jpg

AIDL

AIDL的简单使用前面已经说过(Binder章节中),代码也有,可以先大概熟悉下写法。
接着以前的内容说

AIDL支持的数据类型

  • 基本数据类型
  • String、CharSequence
  • Parcelable:所有实现了Parcelable的接口的对象
  • List:只支持ArrayList,里面的每个元素需要支持AIDL
  • Map:只支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value
  • AIDL:所有的AIDL接口本身也可以在AIDL中使用

    1. Parcelable对象和AIDL必须显式的import进来。
    2. 使用都自定义的Parcelable对象时,必须新建一个同名的AIDL文件,并且声明为Parcelable类型,如:
// Book.aidl
package com.breezehan.ipc;

parcelable Book;
  1. AIDL接口方法中,参数如果不是基础类型,都需要标上方向:in、out、或者inout,in表示输入型参数,out表示输出型参数,inout表示输入输出型参数。你真的理解AIDL中的in,out,inout么?

我们改造下之前的AIDL写法,BookSevice中有个List,改造如下

public class BookService extends Service{
    CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
    private IBinder binder = 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, "三体"));
        mBookList.add(new Book(2, "Android"));
    }

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

}

可是前面不是说List只能是ArrayList吗

AIDL是运行在Binder线程池中的,可以多个客户端同时连接,会存在多个线程同时访问的情况,而CopyOnWriteArrayList支持并发的读写
AIDL中支持抽象的List,虽然服务端返回的是CopyOnWriteArrayList,但是Binder中会按照List的规范访问数据并最终形成一个ArrayList给客户端。类似的还有ConcurrentHashMap。
代码参考

改进型AIDL

有种情况,假如Service相当于图书馆,客户端不想轮询查询有什么书,图书馆加入新书,直接通知我一下就好了,也可以取消提醒,这就是常说的观察者模式。
首先,我们需要有个监听接口,客户端实现。这里只能是AIDL接口IOnNewBookArrivedListener.aidl

// IOnNewBookArrivedListener.aidl
package com.breezehan.ipc;
import com.breezehan.ipc.Book;

interface IOnNewBookArrivedListener {
    void onNewBookArrived(in Book book);
}

IBookManager也需要改造

// IBookManager.aidl
package com.breezehan.ipc;

import com.breezehan.ipc.Book;
import com.breezehan.ipc.IOnNewBookArrivedListener;

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
    void registerListener(IOnNewBookArrivedListener listener);
    void unRegisterListener(IOnNewBookArrivedListener listener);

}

Service和Activity见github代码。
运行结果如下
com.breezehan.ipc进程中

ZADI%5BL9AL3SCVOC50VI~W.png

com.breezehan.ipc:remote进程中

XJEPJI%T4JXYP0AN8OZI$}T.png

哈哈,你看,多进程的观察者模式成功了。
不过,等会儿再高兴,本来我们在Activity中的onDestroy中设置了

    @Override
    protected void onDestroy() {
        if (iBookManager != null && iBookManager.asBinder().isBinderAlive()) {
            Log.d(TAG, "onDestroy: unregister listener "+mOnNewBookArrivedListener);
            try {
                iBookManager.unRegisterListener(mOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        unbindService(mServiceConnection);
        super.onDestroy();
    }

真正推出Activity时,打印会出现

6JR59UT%NUEI]%3DX(BB]@D.png

嘛意思,not found,can not unregister.
为什么呢?为什么会解注册失败呢。
我们要知道,Binder传输的的对象,整个是一个序列化、反序列化的过程,Binder会把客户端传过来的对象反序列化成一个新对象,不是一个对象,怎么能remote掉呢!

进阶化改进型AIDL

这里我们要使用到RemoteCallbackList。使系统提供的专门用于删除跨进程listener的接口,看它的定义,接收extend IInterface的参数,即所有的AIDL都可以支持。

public class RemoteCallbackList<E extends IInterface> 

它的内部有个ArrayMap用户保存AIDL的listener,Map中的key时IBInder类型,value是Callback类型

ArrayMap<IBinder, Callback> mCallbacks
            = new ArrayMap<IBinder, Callback>();

看register(E callback)详细代码,我们知道,虽然每次客户端传输过去的listener,最终都被binder生成一个个不同的对象(内部相同),但是底层的Binder对象只是一个。

    public boolean register(E callback, Object cookie) {
        synchronized (mCallbacks) {
            if (mKilled) {
                return false;
            }
            IBinder binder = callback.asBinder();
            try {
                Callback cb = new Callback(callback, cookie);
                binder.linkToDeath(cb, 0);
                mCallbacks.put(binder, cb);//使用binder作为key,同一个binder只有一个对象。
                return true;
            } catch (RemoteException e) {
                return false;
            }
        }
    }

使用RemoteCallbackList改进的Service如下:

        @Override
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mArrivedListeners.register(listener);
        }

        @Override
        public void unRegisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mArrivedListeners.unregister(listener);
        }
     private void onNewBookArrived(Book newBook) throws RemoteException {
        mBookList.add(newBook);
        int N = mArrivedListeners.beginBroadcast();
        for(int i=0;i<N;i++) {
            IOnNewBookArrivedListener broadcastItem = mArrivedListeners.getBroadcastItem(i);
            if (broadcastItem != null) {
                broadcastItem.onNewBookArrived(newBook);
            }
        }
        mArrivedListeners.finishBroadcast();
    }

AIDL服务端 有时不想任何人都可以连接,必须加入验证服务,验证内容再次就不做探索了(我可不说是因为没用过)。

使用ContentProvider

ContentProvider跟Messenger一样,底层都是Binder实现。但是ContentProvider是Android中提供的专门用于不同应用间进行数据共享的方式,天生就适合进程间通信。封装的很好,比AIDL简单很多。
像通讯录、日历等,都实现了ContentProvider,我们只需要通过ContentResolver的query、update、insert、delete方法即可。
Provider,虽然没有具体内容,但是不耽误使用。对象代码provider示例代码

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[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        Log.d(TAG, "onCreate,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 values) {
        Log.d(TAG, "insert");
        return null;
    }

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

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

ProviderActivity

public class ProviderActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_provider);
        Uri uri = Uri.parse("content://com.breezehan.ipc.book.provider");
        getContentResolver().query(uri, null, null, null, null);
        getContentResolver().query(uri, null, null, null, null);
        getContentResolver().query(uri, null, null, null, null);
    }

清单文件配置,provider中android:authorities需要时唯一的,android:permission=”com.breezehan.PROVIDER”表示权限,其他app想使用这个provider,必须声明权限;process就不说了,不想弄两个应用,开个进程。

<provider
    android:name=".provider.BookProvider"
    android:authorities="com.breezehan.ipc.book.provider"
    android:permission="com.breezehan.PROVIDER"
    android:process=":provider" />
<activity android:name=".provider.ProviderActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

运行结果如下

6JR59UT%NUEI]%3DX(BB]@D.png
看到了吧,Binder线程池。onCreate是UI线程,三次query可能会在不同线程。

我们用Sqlite简单实现逻辑内容,其实ContenProvider内部想怎么实现都行。
DbOpenHelper

public class DbOpenHelper extends SQLiteOpenHelper {
    private static final String DB_NAME = "bookprovider.db";
    private static final int DB_VERSION = 1;
    public static final String BOOK_TABLE_NAME = "book";
    public static final String USER_TABLE_NAME = "user";

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


    public DbOpenHelper(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) {

    }
}

BookProvider改造

public class BookProvider extends ContentProvider {
    private static final String TAG = "BookProvider";
    public static final String AUTHRORITY = "com.breezehan.ipc.book.provider";
    public static final Uri BOOK_CONTENT_URI = Uri.parse("content://" + AUTHRORITY + "/book");
    public static final Uri USER_CONTENT_URI = Uri.parse("content://" + AUTHRORITY + "/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(AUTHRORITY, "book", BOOK_URI_CODE);//将uri和uricode关联起来
        sUriMatcher.addURI(AUTHRORITY,"user",USER_URI_CODE);
    }

    private Context mContext;
    private SQLiteDatabase sqLiteDatabase;


    @Override
    public boolean onCreate() {
        Log.d(TAG, "onCreate,current Thread: "+Thread.currentThread().getName());
        mContext  =getContext();
        //初始化数据库,真正使用不能这样写,MainThread
        initProviderData();
        return true;
    }

    private void initProviderData() {
        sqLiteDatabase = new DbOpenHelper(mContext).getWritableDatabase();
        sqLiteDatabase.execSQL("delete from "+DbOpenHelper.BOOK_TABLE_NAME);
        sqLiteDatabase.execSQL("delete from "+DbOpenHelper.USER_TABLE_NAME);
        sqLiteDatabase.execSQL("insert into book values(3,'Android');");
        sqLiteDatabase.execSQL("insert into book values(4,'Ios');");
        sqLiteDatabase.execSQL("insert into book values(5,'Html5');");
        sqLiteDatabase.execSQL("insert into user values(1,'jake',1);");
        sqLiteDatabase.execSQL("insert into user values(5,'Html5',0);");
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        Log.d(TAG, "query,current Thread: "+Thread.currentThread().getName());
        String tableName = getTableName(uri);
        if (tableName == null) {
            throw new IllegalArgumentException("Unsupported URI:" + uri);
        }

        return sqLiteDatabase.query(tableName,projection,selection,selectionArgs,null,null,sortOrder,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 values) {
        Log.d(TAG, "insert");
        String table = getTableName(uri);
        if (table == null) {
            throw new IllegalArgumentException("Unsupported URI:" + uri);
        }
        sqLiteDatabase.insert(table, null, values);
        mContext.getContentResolver().notifyChange(uri,null);//通知外界,ContentProvider的数据已经改变
        return uri;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.d(TAG, "delete");
        String table = getTableName(uri);
        if (table == null) {
            throw new IllegalArgumentException("Unsupported URI:" + uri);
        }
        int count = sqLiteDatabase.delete(table, selection, selectionArgs);
        if (count > 0) {
            mContext.getContentResolver().notifyChange(uri,null);
        }
        return count;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        Log.d(TAG, "update");
        String table = getTableName(uri);
        if (table == null) {
            throw new IllegalArgumentException("Unsupported URI:" + uri);
        }
        int row = sqLiteDatabase.update(table, values, selection, selectionArgs);
        if (row > 0) {
            mContext.getContentResolver().notifyChange(uri,null);
        }
        return row;
    }

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

ProviderActivity中

public class ProviderActivity extends AppCompatActivity {

    private static final String TAG = "ProviderActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_provider);
        Uri bookUri = Uri.parse("content://com.breezehan.ipc.book.provider/book");//BOOK_CONTENT_URI
        ContentValues values = new ContentValues();
        values.put("_id", "6");
        values.put("name","程序设计的艺术");

        getContentResolver().insert(bookUri, values);

        Cursor bookCursor = getContentResolver().query(bookUri, new String[]{"_id", "name"}, null, null, null);
        while (bookCursor.moveToNext()) {
            Book book = new Book(bookCursor.getInt(0),bookCursor.getString(1));
            Log.d(TAG, "query book:"+book.toString());
        }
        bookCursor.close();
    }
}

运行结果如下

YOTY3EL__DCU{F@JC~{$AC5.png

上面只是简单介绍ContentProvider的基本使用方法,深入使用还有很多知识。

使用Socket

内容略…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值