Android之IPC(三常用的进程间通信方式的介绍)

Android之IPC(三常用的进程间通信方式的介绍)

使用Bundle

之前序列化介绍过Parcelable接口,是支持不同进程传输的,一般我们都时把它放在Intent中传递,当然还有其他的用法,下面的Messenger就会用到。在Bundle中我们放入我们需要的信息即可。(后面就会用的到,这里不做过多介绍,常用的Intent,相信大家很熟悉了)

使用文件共享(FileShare)

顾名思义,Android基于Linux,并发读和写是没有限制的,多个线程来写也是可以的(可能会出问题)

要点: 我们可以使用之前说的序列化和反序列化,通过指定文件保存的地方,来进行数据的传输(有点类似我们操作系统的管道)

注意:SharedPreference是一个轻量级的方案,我们平时也就是用来存储一些配置文件,这里不建议使用在进程间通信。 原因:SharedPreference有缓存方案,内存会存一份,多进程下,就会在读/写中变得不可靠。

使用Messenger

在进程之间传递Message对象,通过handler来接收来完成,当然,这个底层也是用的AIDL

步骤解读

从Client到Service,首先在服务端创建一个Service来处理连接的请求,客户端传过来的消息要通过服务端的handler来处理,于是就创建一个Handler,通过这个handler进而创建一个Messenger对象,用来返回底层的binder对象(是不是和AIDL很相像,如果要想给客户端发送信息,使用msg.replyTo调用send方法,向Client发送消息)

public class MessengerService extends Service {

private static final int MSG_FROM_CLIENT = 1;
// 用来接受信息
private final Messenger mMessenger = new Messenger(new MessengerHandler());

@SuppressLint("HandlerLeak")
private class MessengerHandler extends Handler {

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_FROM_CLIENT:
                // 接受local发送来的消息
                Toast.makeText(MessengerService.this, msg.getData().getString("msg"), Toast.LENGTH_SHORT).show();

                // 向Client发送消息
                Messenger reply = msg.replyTo;
                Message replyMessage = Message.obtain(null, 2);
                Bundle data = new Bundle();
                data.putString("reply", "good");
                replyMessage.setData(data);
                try {
                    reply.send(replyMessage);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }

                break;
            default:
                super.handleMessage(msg);
                break;
        }
    }
}

@Override
public IBinder onBind(Intent intent) {
    return mMessenger.getBinder();
}
}

当然,客户端,首先要绑定服务端的Service,通过返回的IBinder创建Messenger,通过这Messenger来向服务端发送消息(注意这里同样用的时Binder,当然,如果你想处理来自客户端的消息,同样在Client创建一个handler,进而创建一个Messenger,message.replyTo = Messenger,让handler来处理消息

private Messenger mService;
private static final int MSG_FROM_SERVER = 2;
// 接受回复信息并处理的handler
private Messenger mGetMessage = new Messenger(new MessengerHandler());

// 用于接受回复的消息
private class MessengerHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_FROM_SERVER:
                Toast.makeText(MainActivity.this, msg.getData().getString("reply"), Toast.LENGTH_LONG).show();
                break;
            default:
                super.handleMessage(msg);
                break;
        }
    }
}

// 用来发送信息
private ServiceConnection messengerConn = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        // 发送数据
        mService = new Messenger(service);
        // 携带发送数据
        Message message = Message.obtain(null, 1);
        Bundle data = new Bundle();
        data.putString("msg", "messenger");
        message.setData(data);
        // 接受信息媒介
        message.replyTo = mGetMessage;
        // 发送数据
        try {
            mService.send(message);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {

    }
};

小结:通过以上我们发现,Messenger发送消息是通过把消息填充到Bundle中,让Message携带Bundle来处理的,Bundle时实现Parcelable接口的。

AIDL

客户端代码

private IBookManager mRemoteBookManager;
private static final int MSG_NEW_BOOK_ARRIVED = 1;

// 这里设置死亡代理,当Binder死亡的时候,会回调其中的方法
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {

    // 运行在binder线程池中
    @Override
    public void binderDied() {
        if (mRemoteBookManager == null) return;

        mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
        mRemoteBookManager = null;

        // 重新绑定服务
        Intent intent = new Intent("com.ali.aidl");
        bindService(intent, aidlConn, BIND_AUTO_CREATE);
    }
};

@SuppressLint("HandlerLeak")
private Handler messengerHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what) {
            case MSG_NEW_BOOK_ARRIVED:
                System.out.println("receive new book : " + msg.obj);
                break;
            default:
                break;
        }
    }
};

// 从这里可以看出一个binder对应一个listener,跨进程传输的时候,binder是同一个
private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
    @Override
    public void onNewBookArrived(Book book) {
        // 此时mOnNewBookArrivedListener是观察者的具体实现(换言之治理就是具体的实现逻辑,
        //相当于服务端,remote必须想要通知观察者,就必须调用这里的方法,remote相当于客户端
        messengerHandler.obtainMessage(MSG_NEW_BOOK_ARRIVED, book).sendToTarget();
    }
};

// ServiceConnection方法运行在主线程,所以调用服务端的方法最好在非UI线程(远程调用本地也是一样)
private ServiceConnection aidlConn = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        // 获取接口对象
        IBookManager bookManager = IBookManager.Stub.asInterface(service);

        try {
            mRemoteBookManager = bookManager;
            // 设置死亡代理
            mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0);
            List<Book> bookList = bookManager.getBookList();
            System.out.println("bookList size : " + bookList.size());

            // 向服务端提交信息
            bookList.add(new Book(3, "JAVA"));

            List<Book> newList = bookManager.getBookList();
            bookManager.registerListener(mOnNewBookArrivedListener);


            System.out.println("bookList size : " + newList.size());
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        // 档然在这里也是可以重新绑定绑定服务的(相当于死亡代理的,具体区别后面有介绍)
        System.out.println("binder died");
    }
};

服务端代码

public class BookManagerService extends Service {

private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();

private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();

//private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList = new CopyOnWriteArrayList<>();

private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);

private Binder mBinder = new IBookManager.Stub() {

    @Override
    public List<Book> getBookList() {
        return mBookList;
    }

    @Override
    public void addBook(Book book) {
        mBookList.add(book);
    }

    // 如果没有特殊的处理,跨进程之后传过来就会变你成一个不确定对象(对象的地址控件会发生变化,导致对象不一样
    // 所以要使用RemoteCallbackList(实现了自动同步,如果客户端停止,自动移除相关的listener)下面的unRegister一样如此
    @Override
    public void registerListener(IOnNewBookArrivedListener listener) {
        //if (!mListenerList.contains(listener)) mListenerList.add(listener);
        //else System.out.println("not found");
        mListenerList.register(listener);
    }

    @Override
    public void unregisterListener(IOnNewBookArrivedListener listener) {
       // if (mListenerList.contains(listener)) mListenerList.remove(listener);
        //else System.out.println("not found");
        mListenerList.unregister(listener);
    }
};

@Override
public void onCreate() {
    super.onCreate();
    mBookList.add(new Book(1, "Android"));
    mBookList.add(new Book(2, "IOS"));
    new Thread(new ServiceWorker()).start();
}

@Override
public IBinder onBind(Intent intent) {
    // 在这里可以进行权限的验证,不是所有的都可以调用来自服务端的方法
    return mBinder;
}

private void onNewBookArrived(Book book) throws RemoteException {
    mBookList.add(book);
    //for (IOnNewBookArrivedListener listener : mListenerList) {
    //    listener.onNewBookArrived(book);
    //}

    IOnNewBookArrivedListener listener;
    int count = mListenerList.beginBroadcast();
    for (int i = 0; i < count; i++) {
        listener = mListenerList.getBroadcastItem(i);
        // 这里是为了演示,其实这个应该也放在非UI线程
        if (listener != null) listener.onNewBookArrived(book);
    }
    mListenerList.finishBroadcast();
}

private class ServiceWorker implements Runnable {

    @Override
    public void run() {
        Book newBook;
        // 这里处理一些后太进程
        while (!mIsServiceDestroyed.get()) {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            int bookId = mBookList.size() + 1;
            newBook = new Book(bookId, "newBook" + bookId);
            try {
                onNewBookArrived(newBook);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
}
}

AIDL文件

1.
// Book.aidl
package com.example.remote;

parcelable Book;

2.
// IBokManager.aidl
package com.example.remote;

import com.example.remote.Book;
import com.example.remote.IOnNewBookArrivedListener;

interface IBookManager {

    List<Book> getBookList();

    void addBook(in Book book);

    void registerListener(IOnNewBookArrivedListener listener);

    void unregisterListener(IOnNewBookArrivedListener listener);
}

3.

// IOnNewBookArrivedListener.aidl
package com.example.remote;

import com.example.remote.Book;

interface IOnNewBookArrivedListener {

     void onNewBookArrived(in Book book);
}

小结:

1.这里从Client通过Binder传递到Server的时候,会产生两个对象,但是Binder确是一样的,因此,这里我们用的观察者模式,在unRegistere的时候就会出意外,完全是两个对象,因此,要利用Binder一样,使用RemoteCallbackList(专门用来提供删除跨进程的接口,是一个泛型,支持管理任意的AIDL接口。而且内部自动实现了线程同步的功能,我们就不需要额外的线程同步的功能是不是很NB)

2.同时RemoteCallbackList并不是一个List,在遍历的时候beginBroadCase和finishBroadcase要配对使用,哪怕是获取其中元素的个数。

3.之前说过,客户端请求当前线程会挂起,不同进程的方法会运行在服务端的Binder线程池中,当前线程阻塞,如果在UI线程可能导致ANR。也要注意,onServiceConnected和onServiceDisconnected也是运行在UI线程,不能有耗时方法。

4.我们为了方法对远程连接不意外中断,都有重连机制。其一,我们可以使用设置死亡代理,通过监听来判断。其二,在onServiceDisconnect中重新连接。死亡代理,我们用的到服务端传过来的Ibinder来设置监听,运行在客户端的Binder线程池中,不能访问UI。但是后者是运行在UI线程当中的。

5.aidl文件中用到了自定义序列化对象不要忘记导入(哪怕是在同一个包)

使用ContentProvider

这是Android的四大组件之一,通过表格的形式组织数据,和数据库很类似,一般也是和数据库联系起来用的。

客户端

private void contentProvider() {

    Uri bookUri = Uri.parse("content://com.ali.contentProvider/book");
      ContentValues bookValues = new ContentValues();
      bookValues.put("_id", 6);
      bookValues.put("name", "Python");
      getContentResolver().insert(bookUri, bookValues);

    Cursor bookCursor = getContentResolver().query(bookUri, new String[]{"_id", "name"}, null, null, null);

    Book book;
    while (bookCursor.moveToNext()) {
        book = new Book(bookCursor.getInt(0), bookCursor.getString(1));
        System.out.println(book.toString());
    }
    bookCursor.close();

    Uri userUri = Uri.parse("content://com.ali.contentProvider/user");
    Cursor userCursor = getContentResolver().query(userUri, new String[]{"_id", "name", "sex"}, null, null, null);

    User user;
    while (userCursor.moveToNext()) {
        user = new User(userCursor.getInt(0), userCursor.getString(1), userCursor.getInt(2) == 1);
        System.out.println(user.toString());
    }
    userCursor.close();
}

服务端

ContentProvider

public class BookProvider extends ContentProvider {
// 可以理解为URI匹配的必要前缀(自己定义的)
private static final String AUTHORITY = "com.ali.contentProvider";
// content:// 默认前缀
private static final Uri BOOK_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/book");

private static final Uri USER_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/user");
// 表的选择索引
private static final int BOOK_URI_CODE = 0;
private 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 Context mContext;
private SQLiteDatabase mDataBase;

@Override
public boolean onCreate() {
    mContext = getContext();
    // 初始化数据库(这是为了演示,实际开发中不应该在主线程中进行)
    initProviderData();
    return true;
}
// 初始化创建数据库并插入部分数据
private void initProviderData() {
    mDataBase = new DBOpenHelper(mContext).getReadableDatabase();
    mDataBase.execSQL("delete from " + DBOpenHelper.BOOK_TABLE_NAME);
    mDataBase.execSQL("delete from " + DBOpenHelper.USER_TABLE_NAME);
    mDataBase.execSQL("insert into book values(3, 'Android');");
    mDataBase.execSQL("insert into book values(4, 'IOS');");
    mDataBase.execSQL("insert into book values(5, 'HTML5');");
    mDataBase.execSQL("insert into user values(1, 'jake', 1);");
    mDataBase.execSQL("insert into user values(2, 'jasmine', 0);");
}

// 以下是具体的增删改查等基本操作


@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
    String tableName = getTableName(uri);
    if (tableName == null) throw new IllegalArgumentException("Unsupported URI :" + uri);
    return mDataBase.query(tableName, projection, selection, selectionArgs, null, null, sortOrder, null);
}


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

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

@Override
public Uri insert(@NonNull Uri uri, ContentValues values) {
    String tableName = getTableName(uri);
    if (tableName == null) throw new IllegalArgumentException("Unsupported URI :" + uri);
    mDataBase.insert(tableName, null, values);
    mContext.getContentResolver().notifyChange(uri, null);
    return uri;
}

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

// 选择表
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;
        default:
            break;
    }
    return tableName;
}
}

SQLiteOpenHelper

public class DBOpenHelper extends SQLiteOpenHelper {

private static final String DB_NAME = "book_provider.db";

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

private static final int DB_VERSION = 1;

// 图书和用户信息表
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 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) {

}
}

小结:

1.这个一般就是一些固定的格式,书写没有什么过多讲解的。ContentProvider中处理onCreate方法运行在UI线程,其他的五个方法都是运行在Binder线程池中的,query,update,insert,delete,getType。在我们对数据有更改的时候,一般都是调用mContext.getContentResolver().notifyChange(uri, null)来发出通知。我们通过ContentResolyer来注册观察者检测即可。

2.同时query,inset,update,delete,存在多线程并发访问,内部要做好同步。当时SQLiteDatabase内部对数据库是由同步处理的,当时多个SQLiteDatabase就无法保证了,因为多个SQLiteDatabase对象之间没有办法做到同步,需要使用者自己处理。

Socket

也是可以使用进程间通信的,我们注意不要在主线程中访问网络。

客户端(UI的更新通过Handler在UI更新)下面是主要方法

private void connectTCPServer() {
    Socket socket = null;
    try {
        // 建立连接
        socket = new Socket("localhost", 8688);
        mClientSocket = socket;
        // 向服务端提供客户端给的消息
        writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
        socketHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED);
        System.out.println("connected success");
    } catch (IOException e) {
        e.printStackTrace();
        System.out.println("connected failed");
    }

    try {
        // 接受服务端的信息
        BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        while (!isFinishing()) {
            String content = reader.readLine();
            if (content != null) {
                String time = "server " + formatDateTime(System.currentTimeMillis()) + ":" + content + "\n";
                socketHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG, time).sendToTarget();
                System.out.println("对话");
            }
        }
        System.out.println("quit...");
        reader.close();
        socket.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

@SuppressLint("SimpleDateFormat")
private String formatDateTime(long time) {
    return new SimpleDateFormat("(HH:mm:ss)").format(new Date(time));
}

// 发送信息
@SuppressLint("SetTextI18n")
private void sendMessage() {

    String content = etContent.getText().toString().trim();
    if (!TextUtils.isEmpty(content) && writer != null) {
        writer.println(content);
        etContent.setText("");
        String showMsg = "self" + formatDateTime(System.currentTimeMillis()) + ":" + content + "\n";
        tvShow.setText(tvShow.getText() + showMsg);
    }

}

服务端

public class TCPService extends Service {

private boolean mIsServiceDestroyed = false;

private String[] mDefinedMessages = new String[]{
        "你好啊,很高兴认识你!",
        "欢迎来到我见做客。",
        "今天天气不错啊,很适合出去走走,约不?",
        "你知道吗?据说爱笑的女孩子,运气不会太差。",
        "做我女朋友吧。"
};

@Override
public void onCreate() {
    super.onCreate();
    new Thread(new TCPServer()).start();
}

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

@Override
public void onDestroy() {
    super.onDestroy();
    mIsServiceDestroyed = true;
}

private class TCPServer implements Runnable {

    @Override
    public void run() {
        ServerSocket serverSocket;

        try {
            // 监听8688端口
            serverSocket = new ServerSocket(8688);
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("establish tcp server failed, port:8688");
            return;
        }

        while (!mIsServiceDestroyed) {

            try {
                // 接受客户端的请求
                Socket client = serverSocket.accept();
                System.out.println("accept");
                responseClient(client);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

private void responseClient(Socket client) throws IOException {

    // 用于接受客户端消息
    BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream()));
    // 用于向客户端发送消息
    PrintWriter writer = new PrintWriter(new OutputStreamWriter(client.getOutputStream()),true);

    writer.println("欢迎来到聊天室");

    String clientContent, serverContent;
    while (!mIsServiceDestroyed) {
        clientContent = reader.readLine();
        if (clientContent == null) break; // 断开连接
        serverContent = mDefinedMessages[(int) (Math.random() * (mDefinedMessages.length))];
        writer.println(serverContent);
    }
    System.out.println("client quit");
    // 关闭输入输出流
    reader.close();
    writer.close();
    client.close();
}
}

小结: 形成一个循环,客户端和服务端两者之间相互传递消息即可,就是输入输出流的使用。

总结

以上的通信方式,都不要放及解绑和停止服务即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值