Android IPC操作步骤

一、什么是IPC?

IPC是Inter-Process-Communication的缩写,意思是进程间通信或者跨进程通信;
说起进程间通信,我们应该首先来了解一下什么是进程。按照操作系统的描述,线程是CPU调度的最小单元,而进程一般指一个执行单元,在移动设备上指一个程序或应用;一个进程可以包含多个线程;

为什么要用到多进程?
在Android系统中一个应用默认只有一个进程,每个进程都有自己独立的资源和内存空间,其它进程不能任意访问当前进程的内存和资源,系统给每个进程分配的内存会有限制。如果一个进程占用内存超过了这个内存限制,就会报OOM的问题,很多涉及到大图片的频繁操作或者需要读取一大段数据在内存中使用时,很容易报OOM的问题,为了彻底地解决应用内存的问题,Android引入了多进程的概念,它允许在同一个应用内,为了分担主进程的压力,将占用内存的某些页面单独开一个进程,比如Flash、视频播放页面,频繁绘制的页面等。Android多进程使用很简单,只需要在AndroidManifest.xml的声明四大组件的标签中增加”android:process”属性即可,process分私有进程和全局进程,以“:”号开头的属于私有进程,其他应用组件不可以和他跑在同一个进程中;不以“:”号开头的属于全局进程,其他应用可以通过ShareUID的方式和他跑在同一个进程中;

但是多进程模式出现以下问题:
1、静态成员和单例模式完全失效
2、线程同步机制完全失效
3、SharedPreferences的可靠性下降
4、Application多次创建

因此为了避免这些问题Android中有多种IPC机制,如AIDL,Messenger,Socket,ContentProvider,但是这些机制底层全部都是用了Binder机制来实现的,什么是Binder?要了解Android系统中的IPC我们首先要了解的就是Binder;

具体详细可阅《Android开发艺术探索》中第二章-IPC机制(上) :https://blog.csdn.net/qq_26787115/article/details/52664405

二、Binder机制原理

1、Binder机制

Binder是Android系统中的一种IPC进程间通信结构。
Binder的整个设计是C/S结构,客户端进程通过获取服务端进程的代理,并通过向这个代理接口方法中读写数据来完成进程间的数据通信。
Android之所以选择Binder,有2个方面的原因。
1是安全,每个进程都会被Android系统分配UID和PID,不像传统的在数据里加入UID,这就让那些恶意进程无法直接和其他进程通信,进程间通信的安全性得到提升。
2是高效,像Socket之类的IPC每次数据拷贝都需要2次,而Binder只要1次,在手机这种资源紧张的情况下很重要。
Binder机制原理图:

1.客户端获取服务端的代理对象(proxy)。我们需要明确的是客户端进程并不能直接操作服务端中的方法,如果要操作服务端中的方法,那么有一个可行的解决方法就是在客户端建立一个服务端进程的代理对象,这个代理对象具备和服务端进程一样的功能,要访问服务端进程中的某个方法,只需要访问代理对象中对应的方法即可;
2.客户端通过调用代理对象向服务端发送请求。
3.代理对象将用户请求通过Binder驱动发送到服务器进程;
4.服务端进程处理客户端发过来的请求,处理完之后通过Binder驱动返回处理结果给客户端的服务端代理对象;
5.代理对象将请求结果进一步返回给客户端进程。
通过以上5个步骤,就完成了一次Binder通信。

2、Binder机制的组成

Binder机制由三部分组成,即:
1.Client;
2.Server;
3.ServiceManager。

三部分组件之间的关系:
1.Client、Server、ServiceManager均在用户空间中实现,而Binder驱动程序则是在内核空间中实现的;
2.在Binder通信中,Server进程先注册一些Service到ServiceManager中,ServiceManager负责管理这些Service并向Client提供相关的接口;
3.Client进程要和某一个具体的Service通信,必须先从ServiceManager中获取该Service的相关信息,Client根据得到的Service信息与Service所在的Server进程建立通信,之后Clent就可以与Service进行交互了;
4.Binder驱动程序提供设备文件/dev/binder与用户空间进行交互,Client、Server和ServiceManager通过open和ioctl文件操作函数与Binder驱动程序进行通信;
5.Client、Server、ServiceManager三者之间的交互都是基于Binder通信的,所以通过任意两者这件的关系,都可以解释Binder的机制。

三、AIDL的实现

在Android中有多种实现IPC的方式,各有各的优缺点,我们拿其中一种最常用方式来更深入的了解一下Android中IPC的实现方式,从而彻底理解Binder机制的工作方式;

服务器代码:

(1)定义Book类,包含ID, Name 属性,实现 Parcelable 接口

package com.uiuno.myapplication;

import android.os.Parcel;
import android.os.Parcelable;

public class Book implements Parcelable {

    public int bookId;
    public String bookName = "";

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }

    public void setBookName(String bookName){
        this.bookName = bookName;
    }

    public String getBookName(){
        return this.bookName;
    }

    public void setBookId(int bookId){
        this.bookId = bookId;
    }

    public int getBookId(){
        return this.bookId;
    }
    public Book(Parcel in) {
        bookId = in.readInt();
        bookName = in.readString();
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(bookId);
        dest.writeString(bookName);
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

    @Override
    public String toString() {
        return String.format("编号:%d, 书名:%s", bookId, bookName);
    }
}

(2).申明Book的AIDL文件(如果创建的时候出现与Book.java同名提示,请先创建好aidl申明文件。):

// Book.aidl
package com.uiuno.myapplication;

parcelable Book;

(3).定义AIDL文件

// IBookManager.aidl
package com.uiuno.myapplication;

// Declare any non-default types here with import statements
//aidl中如果使用非基本类型,需要import
import com.uiuno.myapplication.Book;


interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}

创建或修改过AIDL文件后需要clean下工程,使系统及时生成我们需要的文件

(4).创建一个 Service 供客户端远程绑定了,这里命名为 AIDLService

package com.uiuno.myapplication;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;

public class AIDLServices extends Service {

    private final String TAG = "Server";

    private List<Book> bookList;

    public AIDLServices() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
        bookList = new ArrayList<Book>();
        initData();
    }

    private void initData() {
        Book book1 = new Book(1,"活着");
        Book book2 = new Book(2,"或者");
        Book book3 = new Book(3,"叶应是叶");
        bookList.add(book1);
        bookList.add(book2);
        bookList.add(book3);
        Log.e(TAG, "初始化书籍");
    }

    private final IBookManager.Stub stub = new IBookManager.Stub() {

        @Override
        public List<Book> getBookList() throws RemoteException {
            return bookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            if (book != null) {
                bookList.add(book);
                Log.e(TAG, "服务器接收到了一个新书对象:" + book.toString());
            } else {
                Log.e(TAG, "服务器接收到了一个空对象");
            }
        }

    };

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

}

可以看到, onBind 方法返回的就是 BookController.Stub 对象,实现当中定义的两个方法

最后,服务端还有一个地方需要注意,因为服务端的Service需要被客户端来远程绑定,所以客户端要能够找到这个Service,可以通过先指定包名,之后再配置Action值或者直接指定Service类名的方式来绑定Service
如果是通过指定Action值的方式来绑定Service,那还需要将Service的声明改为如下所示:

<service android:name=".AIDLServices"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.uiuno.myapplication.bookservice.action" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </service>

客户端代码:

需要把服务端的AIDL文件以及Book类复制过来,将 aidl 文件夹整个复制到和Java文件夹同个层级下,不需要改动任何代码

此处需要复制三个文件,并保持和服务器相同的包名:1.Book.java    2.Book.aidl    3.IBookManager.aidl

服务器只需要操作获取书籍列表和添加书籍操作:

1.布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn_getBookList"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="获取书籍列表" />

    <Button
        android:id="@+id/btn_addBook_inOut"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="添加书籍" />

</LinearLayout>

2.MainActivity.java

package com.uiuno.myapplication;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

import com.uiuno.myapplication2.R;

import java.util.List;

public class MainActivity extends AppCompatActivity {

    private final String TAG = "Client";

    private IBookManager bookController;

    private boolean connected;

    private List<Book> bookList;

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            bookController = IBookManager.Stub.asInterface(service);
            connected = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            connected = false;
        }
    };

    private View.OnClickListener clickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.btn_getBookList:
                    if (connected) {
                        try {
                            bookList = bookController.getBookList();
                            log();
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                    break;
                case R.id.btn_addBook_inOut:
                    if (connected) {
                        Book book = new Book(10, "这是一本新书 InOut");
                        try {
                            bookController.addBook(book);
                            Log.e(TAG, "向服务器以InOut方式添加了一本新书");
                            Log.e(TAG, "新书名:" + book.getBookName());
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.btn_getBookList).setOnClickListener(clickListener);
        findViewById(R.id.btn_addBook_inOut).setOnClickListener(clickListener);
        bindService();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (connected) {
            unbindService(serviceConnection);
        }
    }

    private void bindService() {
        Intent intent = new Intent();
        intent.setPackage("com.uiuno.myapplication");
        intent.setAction("com.uiuno.myapplication.bookservice.action");
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
    }

    private void log() {
        if(bookList.size() == 0){
            return;
        }
        for (Book book : bookList) {
            if(book != null) {
                Log.e(TAG, book.toString());
            }
        }
    }

}

两个按钮分别用于获取服务端的书籍列表和添加书籍,在添加书籍时,服务端还改变了Book对象的Name属性,据此观察客户端和服务端数据的变化情况


接下来我们来分析一下整个AIDL的执行过程:
1、首先服务端的MyAddService在自己的进程中向Binder驱动申请创建了一个MyAddService的Binder实体;Binder驱动为MyAddService创建了位于内核中的Binder实体节点以及Binder的引用,并将名字和新建的引用打包传递给SM(实体没有传给SM),通知SM注册一个MyAddService;

2、SM收到数据包后,从中取出MyAddService名字和引用,填入一张查找表中。在启动服务的时候,SM就会从这张查找表中查找相应的服务;

3、客户端Client申请访问MyAddService,SM就会从请求数据包中获得MyAddService的名字,在查找表中找到该名字对应的条目,取出Binder的引用打包回复给client。之 后,Client就可以利用MyAddService的引用使用MyAddService的服务了。


AIDL注意事项:
1.AIDL编译错误: ‘aidl.exe” finished with non-zero exit value 1

解决方法:

1.包名问题

2.AIDL不支持同名不同参函数

3.使用自定义类型时需要在AIDL中声明

        4.AIDL中如果需要使用非基础类型,需要定义Parcelable类,然后在申明一个AIDL文件,在需要AIDL的实现文件(实现文件中即便aidl和java文件属于同级报名,也需要import使用的java文件,不然编译错误。)

 

相关文章

  • 没有相关文章 :(
0 条评论
发表一条评论