Android的IPC:Binder

时间:2024-04-08 19:40:11

IPC是一种概念:进程间的通信,有很多实现方法,例如android系统内的binder、contentProvider等

  • 一个进程中可以有多个线程,也可以只有一个线程,在android系统中,这唯一的线程就是主线程,即UI线程,用来操作界面元素
  • android系统对每个应用都有内存限制,如果需要扩大内存就可以通过创建新进程来获取更大内存

一个应用内的多进程 --- android:process

android系统中,一个应用内使用多进程方法,给四大组件定义属性android:process,还有一种就是native层的fork新进程

  • 实例
        <activity android:name=".MainActivity"
            android:excludeFromRecents="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".secondActivity"
            android:process=":remote">
            <intent-filter>
                <action android:name="secondactivity" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

        <activity android:name=".thirdActivity"
            android:process="com.app.typicallifecycle.remote">
            <intent-filter>
                <action android:name="thirdactivity" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
    </application>

结果:成功创建出两个新进程
:代表当前应用的私有进程,其他应用组件不可以和它跑在一个进程中
没用到:代表是全局进程,可以通过ShareUID的方式跑在一个进程中(还需要签名相同)

系统会为每一个应用分配一个UID,通过UID可以共享应用数据

u0_a30    9930  182   927684 42572 ffffffff b764535b S com.app.typicallifecycle
u0_a30    9956  182   931808 40108 ffffffff b764535b S com.app.typicallifecycle:remote
u0_a30    9969  182   936668 39852 ffffffff b764535b S com.app.typicallifecycle.remote

多进程的运行机制的一些问题

  • 实例:
  1. 定一个有静态变量的类
public class userProcess {
    public static int number=0;
}
  1. 在MainActivity中自加1,然后日志输出、接着去sencondactivity中输出这个静态变量,看结果,sencondactivity中的值依然是0
11-30 17:33:42.524 10189-10189/? E/xxxxx: number的值为1
11-30 17:33:45.244 10215-10215/com.app.typicallifecycle:remote E/xxxxx: number的值为0

为什么会出现这种情况?

  • Android系统为每个应用(进程)分配一个虚拟机
  • 每个虚拟机使用的内存地址是不一样的,所以依靠内存地址进行数据共享不会成功的。也就是这个两个进程中都有一个userProcess 类,他们互不干扰

多进程常出现的问题:

  1. 静态类、单例模式失效(如上)
  2. SharedPreference可靠性下降:SharedPreference是不支持两个进程同时的写操作的(底层是对xml文件的读写,并发读写是会出问题的)
  3. Application重复创建:为组件创建新进程,首先创建虚拟机、然后创建相应的应用,然后启动组件。具体实现如下
    将这个myApplication 放入AndroidManifest文件,作为应用的实现类
public class myApplication extends Application {
    private static Context context;
    private int processID;

    @Override
    public void onCreate() {
        context = getApplicationContext();
        processID = Process.myPid();
        Log.e("process", String.valueOf(processID));
        super.onCreate();
    }

开启,活动的时候,这个应用被创建了多次

11-30 18:09:10.814 10762-10762/com.app.typicallifecycle E/process: 10762
11-30 18:09:11.894 10788-10788/com.app.typicallifecycle:remote E/process: 10788
11-30 18:09:14.584 10801-10801/com.app.typicallifecycle.remote E/process: 10801

多进程一些方法

即使多进程有一些问题,当时依然可以被克服,这就是IbinderIntent,先引入基础,序列化和反序列化

Serializable
用法:随意声明一个类继承Serializable接口,它内部的属性就可以实例化

public class userProcess implements Serializable {
    private static final long serialVersionUID=111;
    public String fater;
    public String getFater() {
        return fater;
    }
    public void setFater(String fater) {
        this.fater = fater;
    }
}

具体序列化和反序列化过程
        //序列化过程
        userProcess u1 = new userProcess();
        u1.setFater("father007");
        try {
            File file = new File("/sdcard/cache.txt");
            ObjectOutputStream objoutput = new ObjectOutputStream(new FileOutputStream(file));
            objoutput.writeObject(u1);
            objoutput.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        //反序列化过程
        try {
            FileInputStream filein = new FileInputStream(new File("/sdcard/cache.txt"));
            ObjectInputStream objIN = new ObjectInputStream(filein);
            userProcess u1 = (userProcess) objIN.readObject();
            Log.e("userprocess", u1.getFater());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

serialVersionUID:需要的注意一个属性,是一个类的HASH值。

  1. 序列化和反序列化过程中的标识码,如果这个值在序列化和反序列化的过程中不一样,是不能成功反序列化的
  2. 这个UID是保证这样一种情况的,当序列化完一个对象到本地后,过了一段时间要进行反序列化的时候,由于这个被序列化的类属性发生了更改,如果是默认标识码的话,这个hash值会更改,就会失败,但是如果手动设置好固定的serialVersionUID,就可以尽量避免这种情况,但是如果类的改动过大,还是出问题的。
    例如:
序列化之前的类:
public class userProcess implements Serializable {
    private static final long serialVersionUID=111;
    public String fater;
    public String getFater() {
        return fater;
    }
    public void setFater(String fater) {
        this.fater = fater;
    }
}
序列化完后,对这个类的属性进行了更改,多了一个son属性,但是其他没什么改变,这个小改动,并且没有改变被序列化的属性,是不会影响反序列化的
    private static final long serialVersionUID=110;
    public static int number=0;
    public String fater;
    public String son;

但是如果改动的是fater这个属性的化就会出问题,因为这个属性是被序列化的属性,如果类属性更改,就会出现不匹配,报出异常
    public int fater;

Parcelable
另一种序列化的接口,实现序列化之后可以通过Intnet、Binder传输序列化对象。
他的具体是实现是:将序列化和反序列化都封装在一个接口中

简单的实例

public class userProcess implements Parcelable {

    private String father;
    private int son;
    private Boolean mom;
    public String getFather() {
        return father;
    }

    public int getSon() {
        return son;
    }

    public Boolean getMom() {
        

    //描述信息,基本上都是返回0
    @Override
    public int describeContents() {
        return 0;
    }


    /*
    先实例化对象,然后序列化对象
     */
    public userProcess(String f, int s, Boolean m){
        father=f;
        son=s;
        mom=m;
    }

    //序列化过程
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(father);
        dest.writeInt(son);
        //bool型数据用int存储
        dest.writeInt(mom ? 1 : 0);
    }


    //反序列化过程
    public static final Creator<userProcess> CREATOR = new Creator<userProcess>() {
        @Override
        public userProcess createFromParcel(Parcel in) {
            return new userProcess(in);
        }

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

    protected userProcess(Parcel in) {
        father=in.readString();
        son=in.readInt();
        mom=in.readInt()==1;
    }
}

Serializable和Parcelable的区别:Serializable是java提供的序列化接口,存在许多I/O操作,消耗比Parcelable大,建议使用于本地持久化也就是往本地存数据等方面。而Parcelable是android提供的,使用虽然比serializable复杂点,但是更高效一些

Binder

从哪来::继承iBinder接口,是其实现类
到哪去::1. 在android framwork里,它是连接各种manager的桥梁 2.在Application层:他是客户端和服务端通信的媒介(这里的客户端和服务端可以看作同一App内的两个进程,也可以看作同一厂家的不同App),服务端返回一个binder给客户端,通过binder让其访问服务端的资源和服务 ,主要应用于同一个App内的跨进程通信,下面从AIDL看binder

这里引入了一个概念AIDL:android接口定义语言

为什么使用它?

官网解释::只有允许不同应用的客户端用 IPC 方式访问服务,并且想要在服务中处理多线程时,才有必要使用 AIDL。 如果您不需要执行跨越不同应用的并发 IPC,就应该通过实现一个 Binder 创建接口;或者,如果您想执行 IPC,但根本不需要处理多线程,则使用 Messenger 类来实现接口

Android:学习AIDL,这一篇文章就够了(上)

简单的实例:
先右键new 一个AIDL file,下面我只定义了一个方法,用来获取PID:int getPID();

// IMyAidlInterface.aidl
package com.app.typicallifecycle;

// Declare any non-default types here with import statements

interface IMyAidlInterface {

    int getPID();
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

然后点击菜单build->rebuild project就可以手动利用gradle在build/generated/source/aidl/包名/IMyAidlInterface.java方法,构建相应接口的java文件并且会在内部自动填写int getPID();。这个文件内部有一个子类,是继承Binder类的,即IMyAidlInterface.sub这个类的实例就是具体客户端和服务端传递数据、服务的传输工具

public static abstract class Stub extends android.os.Binder implements com.app.typicallifecycle.IMyAidlInterface

ibinder = IMyAidlInterface.Stub.asInterface(service);:主要这一步,将ibinder对象转成IMyAidlInterface类型对象,否则跨进程的时候会报错,不跨进程不会出错,因为不跨进程,服务启动之后就在这个进程中,直接返回一个IMyAidlInterface.Sub类,当然没问题了。但是如果服务开启在另一个进程中,返回的是一个BinderProxy类,是不能强转程IMyAidlInterface.Sub类的,所以需要asInterface方法来转化

客户端关键代码
 private ServiceConnection myserviceconnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //连接上服务的回调函数
            Log.e("xxxxx", name.getClassName());
            ibinder = IMyAidlInterface.Stub.asInterface(service);
            try {
                int pid =  ibinder.getPID();
                Log.e("xxxxx", "pid等于"+ pid);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            //断开连接的回调函数
        }
    };

bindService(new Intent("AIDLservice"), myserviceconnection, BIND_AUTO_CREATE);
记住别忘了在AndroidManifest.xml中注册服务组件

Android 接口定义语言 (AIDL)

稍微复杂的实例:
引入自定义类Book类,也就是说需要过进程传输这个对象。引入这个自定义类之前我们需要知道,AIDL文件有很多系统默认的基础类是可以序列化的,所以我们这个自定义类也需要序列化,并且生成一个aidl文件,供给其他aidl文件使用

Android的IPC:Binder
目录结构

  1. 先自定Book类
package com.app.typicallifecycle;
import android.os.Parcel;
import android.os.Parcelable;

public class Book implements Parcelable {
    private int BookID;
    private String BookName;

    public Book(int bookid, String bookname){
        BookID = bookid;
        BookName = bookname;
    }

    //序列化
    @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];
        }
    };

    protected Book(Parcel in) {
        BookID = in.readInt();
        BookName = in.readString();
    }
    @Override
    public int describeContents() {
        return 0;
    }
}
  1. 生成aidl文件,让其他aidl接口文件使用
    必须:New->file,不要new一个AIDL文件,android studio会识别会接口文件,在generated目录下生成java文件
package com.app.typicallifecycle;
parcelable Book;
  1. 接口文件:将需要给客户端使用的功能接口,数据接口都在这定义
    这里有个点需要注意:in Book book,从客户端传参数到服务端,数据流向是从客户端到服务端,用in。如果是数据流向流向服务端就用out,即服务端数据更改,服务端也跟着更改,但是如果这是向服务端传数据,就是空对像。双向流通inout。
// BookManager.aidl
package com.app.typicallifecycle;

// Declare any non-default types here with import statements
import com.app.typicallifecycle.Book;

interface BookManager {

    void addBooks(in Book book);
    Book getBook();

}

4.服务端

package com.app.typicallifecycle;

import com.app.typicallifecycle.Book;
public class AIDLService extends Service {


    private final BookManager.Stub bookManager = new BookManager.Stub() {
        @Override
        public void addBooks(Book book) throws RemoteException {
        }

        @Override
        public Book getBook() throws RemoteException {
            return null;
        }
    };

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

这里需要注意一下,因为要用到Book这个自定义的类,但是这个类没有放在java包内,而且它默认是不检查main/aidl包内的java文件,需要手动加入,所以直接在build.gradle内android下加入如下代码即可:

sourceSets {
        main {
            java.srcDirs = ['src/main/java', 'src/main/aidl']
        }
    }

5.客户端:部分关键代码

    private ServiceConnection myserviceconnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //连接上服务的回调函数
            Log.e("xxxxx", name.getClassName());
            ibinder = BookManager.Stub.asInterface(service);
            Book book1 = new Book(007, "android艺术探索");
            try {
                ibinder.addBooks(book1);
                Log.e("xxxxx", "上传完第一本书:艺术探索");
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            try {
                 Book book;
                book = ibinder.getBook();
                Log.e("xxxxx", "下载完书籍:"+book);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            //断开连接的回调函数
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Log.e("xxxxx", "onCreate: 活动创建");
        super.onCreate(savedInstanceState);
        editText = findViewById(R.id.editText);
        setContentView(R.layout.activity_main);
        button = findViewById(R.id.button);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                bindService(new Intent("AIDLservice"), myserviceconnection, BIND_AUTO_CREATE);
            }
        });
    }
Android的IPC:Binder

主要分析一下BookManager.java文件,他是android studio自动生成的,也是AIDL的目的所在,就是为了方便我们来写这种接口,这个文件存在不同进程通信的逻辑所在

首先客户端请求绑定另外一个进程的服务,来获取上传书籍和下载书籍的功能。服务端返回一个Binder对象来作为双方通信的桥梁。服务端把所有向客户端开放的接口都放在了proxy类下。将需要输入的参数有_data传递,将需要返回的数据由_reply传递,然后将请求通过transact传递到服务端

private static class Proxy implements com.app.typicallifecycle.BookManager
{
    private android.os.IBinder mRemote;
    Proxy(android.os.IBinder remote)
    {
        mRemote = remote;
    }
    @Override public android.os.IBinder asBinder()
    {
        return mRemote;
    }
    public java.lang.String getInterfaceDescriptor()
    {
        return DESCRIPTOR;
    }
    @Override public void addBooks(com.app.typicallifecycle.Book book) throws android.os.RemoteException
    {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            if ((book!=null)) {
                _data.writeInt(1);
                book.writeToParcel(_data, 0);
            }
            else {
                _data.writeInt(0);
            }
            mRemote.transact(Stub.TRANSACTION_addBooks, _data, _reply, 0);
            _reply.readException();
        }
        finally {
            _reply.recycle();
            _data.recycle();
        }
    }
    @Override public com.app.typicallifecycle.Book getBook() throws android.os.RemoteException
    {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        com.app.typicallifecycle.Book _result;
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            mRemote.transact(Stub.TRANSACTION_getBook, _data, _reply, 0);
            _reply.readException();
            if ((0!=_reply.readInt())) {
                _result = com.app.typicallifecycle.Book.CREATOR.createFromParcel(_reply);
            }
            else {
                _result = null;
            }
        }
        finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }
}

然后另一个进程上的服务端通过这个onTransact在线程池中的方法,来处理客户端的请求,从code获取客户端请求的方法、从data获取参数、如果有返回值通过reply返回,如果返回false表示请求失败,通过这个做了一个权限控制,有选择的允许访问

@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
    java.lang.String descriptor = DESCRIPTOR;
    switch (code)
{
    case INTERFACE_TRANSACTION:
{
    reply.writeString(descriptor);
    return true;
}
    case TRANSACTION_addBooks:
{
    data.enforceInterface(descriptor);
    com.app.typicallifecycle.Book _arg0;
if ((0!=data.readInt())) {
    _arg0 = com.app.typicallifecycle.Book.CREATOR.createFromParcel(data);
}
else {
    _arg0 = null;
}
    this.addBooks(_arg0);
    reply.writeNoException();
    return true;
}
    case TRANSACTION_getBook:
{
    data.enforceInterface(descriptor);
    com.app.typicallifecycle.Book _result = this.getBook();
    reply.writeNoException();
if ((_result!=null)) {
    reply.writeInt(1);
    _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
}
else {
    reply.writeInt(0);
}
    return true;
}
    default:
{
    return super.onTransact(code, data, reply, flags);
}
}
}

然后就是将远程传递过来的binder对象转成客户端可以使用的binder对象通过asInterface方法

/**
 * Cast an IBinder object into an com.app.typicallifecycle.BookManager interface,
 * generating a proxy if needed.
 */
public static com.app.typicallifecycle.BookManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.app.typicallifecycle.BookManager))) {
    return ((com.app.typicallifecycle.BookManager)iin);
}
    return new com.app.typicallifecycle.BookManager.Stub.Proxy(obj);
}

还有一个asbinder方法用来获取当前binder
小结一下:其实上面的内容,不使用AIDL文件也可以实现,因为AIDL就是为了方便生成接口文件,如本例中的Manager.java,不过为了方便高效来说AIDL肯定更方便,手动写那么多代码也是挺累的

最后一个小点
由于跨进程通信,如果服务端出现问题终止了,客户端却不知到服务端的情况,有可能继续发起请求,但是这样有可能会影响客户端的运行,所以引入死亡代理这个概念。通过linkToDeathunlinkToDeath,相关介绍可以看API上面的介绍linkToDeath是注册接收一个服务死亡时通知,unlinkToDeath是移除前面注册的死亡通知

绑定好远程服务,开启死亡代理service.linkToDeath(deathRecipient, 0);

    private ServiceConnection myserviceconnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //连接上服务的回调函数
            Log.e("xxxxx", name.getClassName());

            bookManager = BookManager.Stub.asInterface(service);
            try {
                service.linkToDeath(deathRecipient, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            Book book1 = new Book(007, "android艺术探索");
            try {
                bookManager.addBooks(book1);
                Log.e("xxxxx", "上传完第一本书:艺术探索");
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            try {
                 Book book;
                book = bookManager.getBook();
                Log.e("xxxxx", "下载完书籍:"+book);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

这是一个Ibinder下的接口

        //死亡代理
        private IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
            @Override
            //如果服务端进程死掉,回调此方法
            public void binderDied() {
                Log.e("xxxxx","接受到死亡通知");
                //断开连接的回调函数
                if (bookManager==null)
                    return;
                //移除之前的死亡通知
                bookManager.asBinder().unlinkToDeath(deathRecipient, 0);
                //重新绑定服务
                bookManager=null;
                bindService(new Intent("AIDLservice"), myserviceconnection, BIND_AUTO_CREATE);
            }
        };

杀死进程名为app.service.remote的进程

                ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
                List<ActivityManager.RunningAppProcessInfo> processinfolist = activityManager.getRunningAppProcesses();
                for (int i=0; i<processinfolist.size(); i++){
                    String processName = processinfolist.get(i).processName;
                    if (processName.equals("app.service.remote")) {
                        int pid = processinfolist.get(i).pid;
                        Process.killProcess(pid);
                    }