26
2017
09

Android AIDL简单实现

每一个APP都独立运行在自己的进程中,拥有独立的地址空间。因而进程之间的资源是不能共享的,所以每个系统都有自己的IPC(Inter-Process Communication,进程间通信)机制。
Android是基于Linux内核的移动操作系统,但它并没有继承Linux的IPC机制,而是有着自己的一套IPC机制,这个IPC机制就是使用AIDL来实现的,而android中的Binder就是Android中最具特色的IPC方式,AIDL就是通过Binder实现的。

那么,一个APP,什么情况下会开启多进程呢?

  1. 应用由于自身需要采用多进程模式来实现。比如播放器之类,由于service与Activity一样,都是运行在UI主线程中的,如果播放器后台播放功能仅仅在service中运行会影响主线程的响应速度,很可能会造成ANR,一般情况下不会这么写;如果仅仅在子线程中运行,一旦开启该线程的Activity被杀死后,线程也被杀死,无法实现后台运行效果,更加不合理。而如果在另外一个进程中使用service后台运行,就显得十分恰当了。
  2. 由于android对单个应用所使用的最大内存做了限制,为了加大一个应用可使用的内存,所以通过多进程来获取多份内存空间。

开启多线程的方法

android开启多线程的方法只有这一种:在manifest.xml文件中,为android四大组件配置android:process属性,如:

 <service  android:name=".services.TestAIDLService" android:process=":remote">
    <intent-filter>
       <action android:name="android.intent.action.TestAIDLService"/>
       <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</service>

开启新的进程之后,会在APP的进程列表中看到多了一个:remote的进程:

这里写图片描述

从Manifest以及进程名称中,我们可以看到,新开的进程的名称与APP主进程的名称前半部分相同,但多了:remote。由此可以知道新进程的名称已”:”开头,且属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中;而新进程名称又可以不带这个“:”,表示全局进程,其他应用可以通过某些方式和它跑在同一个进程中。当然,这个“remote”名字可以随意取。

接下来,就一步步看下android是如何实现自己的IPC的。

我创建两个APP来实现跨进程(APP)的通信。

第一个APP,我直接在我用来学习的工程中创建了,我们定义这个APP为服务端:
这里写图片描述
因为这个是服务端,所以提供跨进程的服务就设计在这个APP中;

第二个APP,我新建一个测试工程,定义这个APP为client:
这里写图片描述

从两张图中,可以看到,我们有相同包名,相同类名的,两个后缀名为aidl的文件,这个文件就是我们实现Android IPC的关键。

首先,我们在java的同级目录下创建如图的aidl目录,并在该目录下新建一个AIDL文件:

// IMyAidlInterface.aidl
 package com.shangxiaom.commonlist;

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

 interface IMyAidlInterface {
     String getTestStr(String extra);

     void testAidl();

     double getTestNum();
 }

创建该接口之后,build一下工程,studio会自动帮我们创建对应的Stub(下边会介绍)。

接下来,我们提供服务:

public class TestAIDLService extends Service {

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

    IMyAidlInterface.Stub mBinder = new IMyAidlInterface.Stub() {
        @Override
        public String getTestStr(String extra) throws RemoteException {
            return "这是测试RPC代码!" + extra;
        }

        @Override
        public void testAidl() throws RemoteException {

        }

        @Override
        public double getTestNum() throws RemoteException {
            return 1;
        }
    };
}

从服务的onBind方法,我们返回了一个IMyAidlInterface.Stub对象,打开Stub的源码:

public interface IMyAidlInterface extends android.os.IInterface {
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.shangxiaom.commonlist.IMyAidlInterface {
private static final java.lang.String DESCRIPTOR = "com.shangxiaom.commonlist.IMyAidlInterface";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/** * Cast an IBinder object into an com.shangxiaom.commonlist.IMyAidlInterface interface, * generating a proxy if needed. */
public static com.shangxiaom.commonlist.IMyAidlInterface asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.shangxiaom.commonlist.IMyAidlInterface))) {
return ((com.shangxiaom.commonlist.IMyAidlInterface)iin);
}
return new com.shangxiaom.commonlist.IMyAidlInterface.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}

从源码中看出,Stub属于IMyAidlInterface的内部静态类,并实现了我们定义的AIDL接口:
这里写图片描述

可以看到Stub中的常量,其中两个int常量是用来标识我们在接口中定义的方法的,DESCRIPTOR常量是 Binder的唯一标识。
asInterface 方法用于将服务端的Binder对象转换为客户端所需要的接口对象,该过程区分进程,如果进程一样,就返回服务端Stub对象本身,否则呢就返回封装后的Stub.Proxy对象。
adBinder()方法用于在onBind方法中返回Binder对象,但一般我们直接用Stub对象即可,因为它本身继承自Binder。
onTransact 方法是运行在服务端的Binder线程中的,当客户端发起远程请求后,在底层封装后会交由此方法来处理。通过code来区分客户端请求的方法,注意一点的是,如果该方法返回false的换,客户端的请求就会失败。一般可以用来做权限控制。
最后,看一下这个Proxy代理

private static class Proxy implements com.shangxiaom.commonlist.IMyAidlInterface {
            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 java.lang.String getTestStr(java.lang.String extra) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.lang.String _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeString(extra);
                    mRemote.transact(Stub.TRANSACTION_getTestStr, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readString();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void testAidl() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_testAidl, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public double getTestNum() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                double _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getTestNum, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readDouble();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

Proxy类实现了我们定义的IMyAidlInterface接口方法。
代理类中的方法都是运行在客户端,当客户端发起远程请求时,_data会写入参数,然后执行mRemote.transact(Stub.TRANSACTION_getTestStr, _data, _reply, 0);这个方法会发起RPC(远程过程调用)请求,并挂起当前线程,同时服务端的onTransact方法就会被 调起,直到RPC过程返回后,当前线程继续执行,并从_reply取出返回值(如果有的话),并返回结果。

最后在对应的Activity中,启动该服务

 Intent startIntent = new Intent(this, TestAIDLService.class);
 startService(startIntent);

别忘了在manifest.xml中配置服务,以及对应的action

<service  android:name=".services.TestAIDLService" android:process=":remote">
      <intent-filter>
         <action android:name="android.intent.action.TestAIDLService"/>
         <category android:name="android.intent.category.DEFAULT"/>
      </intent-filter>
</service>

接下来,来实现第二个APP:
因为要保证两个APP中,AIDL接口的包名以及方法都相同,所以我们直接把第一个APP中的aidl目录整个拷贝到第二个APP的main目录下;

然后,在MainActivity中,获取AIDL接口对象,并绑定远程的服务:

public class MainActivity extends AppCompatActivity {

    private IMyAidlInterface mIMyAidlInterface;

    private TextView mTextView;

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            mIMyAidlInterface = IMyAidlInterface.Stub.asInterface(iBinder);
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    try {
                        mTextView.setText(mIMyAidlInterface.getTestStr("com.shangxiaom.testaidlclient"));
                    } 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);
        mTextView = (TextView) findViewById(R.id.test_aidl_textview);
        Intent intent = new Intent();
        intent.setAction("android.intent.action.TestAIDLService");
        Intent eintent = new Intent(getTempIntent(this, intent));
        bindService(eintent, mConnection, Context.BIND_AUTO_CREATE);
    }  
}

关于getTempIntent方法,请见

http://blog.csdn.net/shangming150/article/details/78086237

到此就实现了跨进程(APP)之间的通信,先运行第一个APP,启动对应的服务之后,再运行第二个APP,得到如下的结果:
这里写图片描述

完整的服务端代码见github:

https://github.com/shangXiaoM/CAdapter

client代码自己实现吧。。。

上一篇:Activity+弹框,点击home键Activity生命周期 下一篇:git 使用笔记