AIDL:一种IPC跨进程调用远程服务方式,如果是同一个进程之内,没必要使用AIDL;
如下例子一步步揭开AIDL谜题
首先第一步创建一个model实体类,并且需要Parcelable:
public class Book implements Parcelable {
private String name;
public Book(Parcel in) {
this.name = in.readString();
}
public Book(String name) {
this.name = name;
}
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 int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.name);
}
}
然后在实体同包名下创建一个Book.aidl
// Book.aidl
package com.mi.learn.aidllib;
parcelable Book;
之后在创建一个远程调用的IBookManaer.aidl文件:
// IBookManager.aidl
package com.mi.learn.aidllib;
// Declare any non-default types here with import statements
import com.mi.learn.aidllib.Book;
interface IBookManager {
List<Book> getListBook();
void addBook(in Book book);
}
编译后会生成IBookManager.java文件
package com.mi.learn.aidllib;
public interface IBookManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.mi.learn.aidllib.IBookManager {
private static final java.lang.String DESCRIPTOR = "com.mi.learn.aidllib.IBookManager";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.mi.learn.aidllib.IBookManager interface,
* generating a proxy if needed.
*/
public static com.mi.learn.aidllib.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.mi.learn.aidllib.IBookManager))) {
return ((com.mi.learn.aidllib.IBookManager) iin);
}
return new com.mi.learn.aidllib.IBookManager.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@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_getListBook: {
data.enforceInterface(descriptor);
java.util.List<com.mi.learn.aidllib.Book> _result = this.getListBook();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(descriptor);
com.mi.learn.aidllib.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.mi.learn.aidllib.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.mi.learn.aidllib.IBookManager {
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.util.List<com.mi.learn.aidllib.Book> getListBook() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.mi.learn.aidllib.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getListBook, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.mi.learn.aidllib.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(com.mi.learn.aidllib.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_addBook, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getListBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
public java.util.List<com.mi.learn.aidllib.Book> getListBook() throws android.os.RemoteException;
public void addBook(com.mi.learn.aidllib.Book book) throws android.os.RemoteException;
可以看出来,这个IBookManager.java文件几个重要点:
DESCRIPTOR:唯一标识;
IBookManager#asInterface:判断同一进程,返回stub,跨进程,返回Stub.Proxy;
IBookManager#asBinder:返回IBookManager自身;
IBookManager#onTransact:处理各种方法;
IBookManager#Proxy#asBinder:返回是一个mRemote,即IBookManager#asInterface方法传过去的Ibinder;
IBookManager#Proxy#getListBook:代理类的提供方法
IBookManager#Proxy#addBook:代理类的提供方法
之后创建一个服务端BookManagerService:
public class BookManagerService extends Service
{
private static final String TAG = "AIDL-Server";
private List<Book> list = new ArrayList<>();
private Binder mBinder = new IBookManager.Stub() {
@Override
public List<Book> getListBook() throws RemoteException {
Log.d(TAG,"getListBook :" + Thread.currentThread().getName());
return list;
}
@Override
public void addBook(Book book) throws RemoteException {
Log.d(TAG,"addBook :" + Thread.currentThread().getName());
list.add(book);
}
};
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG,"onBind");
return mBinder;
}
}
//AndroidManifest.xml指定下process
<service android:name=".BookManagerService"
android:process=":remote">
<intent-filter>
<action android:name="com.mi.learn.server.bookmanagerservice"/>
</intent-filter>
</service>
客户端调用代码:
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG,"onServiceConnected :" + Thread.currentThread().getName());
bookManager = IBookManager.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG,"onServiceDisconnected :" + Thread.currentThread().getName());
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
Intent intent = new Intent("com.mi.learn.server.bookmanagerservice").setPackage("com.mi.learn.client");
bindService(intent,serviceConnection,BIND_AUTO_CREATE);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.addBook:
try {
bookManager.addBook(new Book("this is a book"));
} catch (RemoteException e) {
e.printStackTrace();
}
break;
case R.id.getBook:
try {
tv.setText(bookManager.getListBook().toString());
} catch (RemoteException e) {
e.printStackTrace();
}
break;
}
}
至此,最简单不稳定的AIDL实现了;
说不稳定有一个地方:
1.服务端可能被多个客户端同时访问,服务端需要线程同步;
2.客户端没有处理服务端进程突然死亡情况;
3.客户端很多服务处理在主线程,会造成ANR;
下面来说如何一步步优化:
1.服务端可能被多个客户端同时访问,服务端需要线程同步;
ArrayList集合时线程不同步的,那么如何做到线程同步呢,在不加锁情况下换成CopyOnWriteArrayList
2.客户端没有处理服务端进程突然死亡情况;
可以使用DeathRecipient机制;
Binder服务端挂掉时候会发一个通知,然后调用DeathRecipient#binderDied,可以在此处死亡时候重新bindService;
3.客户端很多服务处理在主线程,会造成ANR;
点击事件调用bookManager.addBook和bookManager.getListBook都是在主线程进程的,
在点击时候,服务端如果耗时操作,那么这里会卡住,严重会出现ANR;
ServiceConnection回调的onServiceConnected和onServiceDisconnected也是运行在主线程的;而服务端的BookManagerService#IBookManager.Stub的getListBook和addBook在IPC时候处于binder线程池中运行;因此可以创建线程处理,用handler更新即可;
看下改进后的代码
//使用WeakReference防止内存泄露
private MyHandler handler = new MyHandler(this);
private class MyHandler extends Handler{
private WeakReference<Activity> weakReference;
private MyHandler(Activity activity){
weakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
Activity activity = weakReference.get();
if (activity == null){
return;
}
switch (msg.what){
switch (msg.what){
case ADD_BOOK_MESSAGE:
Toast.makeText(activity,"add book success",Toast.LENGTH_SHORT).show();
break;
case GET_BOOK_MESSAGE:
case NOTIFY_BOOK_MESSAGE:
List<Book>list= (List<Book>) msg.obj;
tv.setText(list.toString());
break;
default:
break;
}
}
}
//死亡回调,重新bindService
private IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (bookManager!=null){
bookManager.asBinder().unlinkToDeath(deathRecipient,0);
bookManager = null;
bindService();
}
}
};
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG,"onServiceConnected :" + Thread.currentThread().getName());
Log.d(TAG,"onServiceConnected IBinder :" + service.toString());
//拿到Stub.Proxy代理类
bookManager = IBookManager.Stub.asInterface(service);
try {
bookManager.asBinder().linkToDeath(deathRecipient,0);
Log.i(TAG,"callback address"+callBack.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG,"onServiceDisconnected :" + Thread.currentThread().getName());
}
};
//点击事件运行在子线程,然后通过handler到UI线程
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.addBook:
new Thread(new Runnable() {
@Override
public void run() {
try {
if (bookManager!=null){
bookManager.addBook(new Book("this is a book"));
}
} catch (RemoteException e) {
e.printStackTrace();
}
handler.sendMessage(Message.obtain(handler,ADD_BOOK_MESSAGE));
}
}).start();
break;
case R.id.getBook:
new Thread(new Runnable() {
@Override
public void run() {
try {
if (bookManager!=null){
handler.sendMessage(Message.obtain(handler,GET_BOOK_MESSAGE,bookManager.getListBook()));
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
}).start();
break;
}
}
public class BookManagerService extends Service
{
private static final String TAG = "AIDL-Server";
private CopyOnWriteArrayList<Book> list = new CopyOnWriteArrayList<>();
private Binder mBinder = new IBookManager.Stub() {
@Override
public List<Book> getListBook() throws RemoteException {
//延迟5S,模拟耗时
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG,"getListBook :" + Thread.currentThread().getName());
return list;
}
@Override
public void addBook(Book book) throws RemoteException {
//延迟5S,模拟耗时
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG,"addBook :" + Thread.currentThread().getName());
list.add(book);
}
有时候服务端会有数据更新,那如何通知到客户端实现一种观察者模式呢?
这里就需要在客户端注册一个callback类型的binder到服务端,此时服务端持有callback类型binder的代理类也就是BinderProxy,客户端相当于这个CallBack Binder的服务端,服务端相当于这个CallBack Binder的客户端;
如下步骤,创建一个IBookCallBack.aidl
interface IBookCallBack {
void notifyClient(inout List<Book> list);
}
修改IBookManager.aidl:
interface IBookManager {
List<Book> getListBook();
void addBook(in Book book);
void register(in IBookCallBack callback);
void unregister(in IBookCallBack callback);
}
客户端此时相当于服务端,而服务端相当于客户端,客户端代码:
//需要注意这里可能回调过来在binder线程池中
private Binder callBack = new IBookCallBack.Stub() {
@Override
public void notifyClient(final List<Book> list) throws RemoteException {
Log.d(TAG,"notifyClient :" + Thread.currentThread().getName());
new Thread(new Runnable() {
@Override
public void run() {
handler.sendMessage(Message.obtain(handler,NOTIFY_BOOK_MESSAGE,list));
}
}).start();
}
};
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG,"onServiceConnected :" + Thread.currentThread().getName());
Log.d(TAG,"onServiceConnected IBinder :" + service.toString());
bookManager = IBookManager.Stub.asInterface(service);
try {
bookManager.asBinder().linkToDeath(deathRecipient,0);
Log.i(TAG,"callback address"+callBack.toString());
//注册
bookManager.register((IBookCallBack) callBack);
} catch (RemoteException e) {
e.printStackTrace();
}
}
protected void onDestroy() {
super.onDestroy();
if (bookManager!=null && bookManager.asBinder().isBinderAlive()){
try {
//解注册
bookManager.unregister((IBookCallBack) callBack);
} catch (RemoteException e) {
e.printStackTrace();
}
bookManager.asBinder().unlinkToDeath(deathRecipient,0);
bookManager = null;
}
unbindService(serviceConnection);
handler.removeCallbacksAndMessages(null);
}
服务端BookManagerService代码:
public class BookManagerService extends Service
{
private static final String TAG = "AIDL-Server";
private CopyOnWriteArrayList<Book> list = new CopyOnWriteArrayList<>();
private final RemoteCallbackList<IBookCallBack> remoteCallBackList = new RemoteCallbackList<IBookCallBack>();
private Binder mBinder = new IBookManager.Stub() {
@Override
public List<Book> getListBook() throws RemoteException {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG,"getListBook :" + Thread.currentThread().getName());
return list;
}
@Override
public void addBook(Book book) throws RemoteException {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG,"addBook :" + Thread.currentThread().getName());
list.add(book);
synchronized (BookManagerService.this){
int size = remoteCallBackList.beginBroadcast();
for (int i = 0; i< size; i++){
IBookCallBack callBack = remoteCallBackList.getBroadcastItem(i);
try {
callBack.notifyClient(list);
} catch (RemoteException e) {
e.printStackTrace();
}
}
remoteCallBackList.finishBroadcast();
}
}
@Override
public void register(IBookCallBack callback) throws RemoteException {
Log.i(TAG,"callback address"+callback.toString());
synchronized (BookManagerService.this){
remoteCallBackList.register(callback);
int size = remoteCallBackList.beginBroadcast();
Log.i(TAG,"after register size:"+size);
remoteCallBackList.finishBroadcast();
}
}
@Override
public void unregister(IBookCallBack callback) throws RemoteException {
synchronized (BookManagerService.this){
remoteCallBackList.unregister(callback);
int size = remoteCallBackList.beginBroadcast();
Log.i(TAG,"after unregister size:"+size);
remoteCallBackList.finishBroadcast();
}
}
};
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG,"onBind");
isServiceOk.set(true);
new addBookThread().start();
return mBinder;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG,"onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG,"onUnbind");
isServiceOk.set(false);
return super.onUnbind(intent);
}
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG,"onCreate");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG,"onDestroy");
}
private AtomicBoolean isServiceOk = new AtomicBoolean(false);
private class addBookThread extends Thread{
@Override
public void run() {
while (isServiceOk.get()){
list.add(new Book("this is increase book"));
synchronized (BookManagerService.this){
int size = remoteCallBackList.beginBroadcast();
for (int i = 0; i< size; i++){
IBookCallBack callBack = remoteCallBackList.getBroadcastItem(i);
try {
Log.d(TAG,"server notifyClient:"+Thread.currentThread().getName());
Log.d(TAG,"server notifyClient:"+callBack.toString());
callBack.notifyClient(list);
} catch (RemoteException e) {
e.printStackTrace();
}
}
remoteCallBackList.finishBroadcast();
}
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
在BookManagerService#onCreate创建一个线程,每隔10S添加一本书,然后通过notifyClient回调到客户端,此过程就是服务端持有的是IBookCallBack.Stub.Proxy,而客户端的IBookCallBack.Stub是真正的服务处理;
这里使用RemoteCallbackList<IBookCallBack> remoteCallBackList ;这个类里面实现了死亡回调DeathRecipient过程,因此如果客户端挂了,那么这里也就不用回调了;需要注意remoteCallBackList需要线程同步;
如果服务端BookManagerService经过10S增加一本书到客户端notifyClient显示,这里回调是在binder线程池中,而如上调用客户端通过bookManager.addBook(book),在服务端addBook处理在回调到客户端的notifyClient时候,是在UI线程中的,这地方需要注意下,因此回调不一定就在binder线程池中;
Binder线程池总结
总结如下图所示
(1)场景1,客户端主线程点击Click事件,调用服务端方法addBook,
客户端在主线程,服务端此时应该是在 Binder线程池中;
graph TB
subgraph client
Client-MainThread
end
subgraph Server
Server-Stub1
end
Client-MainThread-->Server-Stub1
(2)服务端Server通过new Thread方式创建线程,在线程方法中调用客户端Client Stub1服务,此时服务端在new Thread方法上,客户端在Binder线程池中;上面服务端开一个线程,每过10S添加一本Book,然后不断通知客户端有数据更新,此操作大致模型如下:
graph TB
subgraph client
Client-MainThread
Client-Stub1
end
subgraph Server
Server-newthread
end
Server-newthread-->Client-Stub1
(3)如果客户端调用服务端AddBook,然后服务端Add完毕后,在调用客户端的方法Stub1,之后客户端在调用服务端Stub2,于是模型如下:
graph TB
subgraph client
Client-MainThread
Client-Stub1
end
subgraph Server
Server-Stub1
Server-Stub2
end
Client-MainThread-->Server-Stub1
Server-Stub1 --> Client-Stub1
Client-Stub1 --> Server-Stub2
客户端主线程请求服务的Stub1服务时候,客户端运行主线并且阻塞,服务端运行在Binder线程池中,假设现在叫binder_2333线程中;
如果server-stub1需要调用client-stub1服务时候,此时client-stub1运行在主线程中;
如果Client-Stub1还需要调用Server-Stub2服务,此时Server-Stub2会运行在binder_2333这个线程池中;
这其中原因是因为binder线程池中有一个线程复用的原则,具体这里不分析;
OneWay和非OneWay
还有需要注意AIDL中oneway和非oneway方式区别;
非oneway如上分析,客户端主线程的bookmanager.add调用时阻塞的,如果服务端的binder.add没处理完,客户端一直阻塞,如果没处理好会ANR,因此客户端在调用服务端时候时waitForResponse;
oneway就是客户端时候不会阻塞;
In/Out/InOut
还有AIDL的输入参数in,out,inout区别,如下
void addBook(in Book book);
in时客户端将数据拷贝到服务端,如果服务端修传入的Book值,客户端传入的Book时不会发生改变的
out时,客户端没有将数据拷贝到服务端,此时时new Book的,但是处理完后,服务端会将数据拷贝到客户端,因此调用完后,客户端时一个 new Book,当然我们服务端修改Book属性,客户端就会拿到服务端修改的值;
inout就是客户端会将Book拷贝到服务端,服务端修改后也会将数据传给客户端;
Binder连接池
如果服务端Binder很多,可以考虑是用Binder连接池,首先建立如下AIDL:
// IBinderPool.aidl
package com.mi.learn.binder;
// Declare any non-default types here with import statements
interface IBinderPool {
IBinder queryBinder(in int code);
}
通过queryBinder方法参数code不同服务端返回不同的binder,如下服务端代码:
private IBinder binderPool = new IBinderPool.Stub() {
@Override
public IBinder queryBinder(int code) throws RemoteException {
switch (code){
case BOOK_BINDER:
return bookBinder;
case PERSON_BINDER:
return personBinder;
}
return null;
}
};
进一步,可以封装一个专门针对这种服务的工具类如下:
package com.mi.learn.binder;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
public class BinderPoolUtils {
private IBinderPool mBinderPool;
private Context mContext;
private Intent mIntent;
private BinderPoolLifeCycle mLifeCycle;
public BinderPoolUtils(Context mContext, Intent mIntent, BinderPoolLifeCycle mLifeCycle) {
this.mContext = mContext;
this.mIntent = mIntent;
this.mLifeCycle = mLifeCycle;
}
public interface BinderPoolLifeCycle{
void onServiceConnected(IBinderPool binderPool);
void onServiceDisconnected(ComponentName name);
void binderDied();
}
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBinderPool = IBinderPool.Stub.asInterface(service);
try {
mBinderPool.asBinder().linkToDeath(deathRecipient,0);
} catch (RemoteException e) {
e.printStackTrace();
}
mLifeCycle.onServiceConnected(mBinderPool);
}
@Override
public void onServiceDisconnected(ComponentName name) {
mLifeCycle.onServiceDisconnected(name);
}
};
private IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (mBinderPool != null){
mBinderPool.asBinder().unlinkToDeath(deathRecipient,0);
mBinderPool = null;
mLifeCycle.binderDied();
bindPoolService();
}
}
};
public void bindPoolService(){
mContext.bindService(mIntent, connection,Context.BIND_AUTO_CREATE);
}
public void unBindPoolService(){
if (mBinderPool != null && mBinderPool.asBinder().isBinderAlive()){
mBinderPool.asBinder().unlinkToDeath(deathRecipient,0);
mBinderPool = null;
}
mContext.unbindService(connection);
}
}
通过接口方式回调Binder的几个关键的方法,客户端主要调用几个关键方法即可:
private void init() {
mBinderPoolUtils = new BinderPoolUtils(this,
new Intent(this,BookManagerService.class),
this);
mBinderPoolUtils.bindPoolService();
}
@Override
public void onServiceConnected(IBinderPool binderPool) {
try {
mBookBinder = IBookBinder.Stub.asInterface(binderPool.queryBinder(BOOK_BINDER));
mBookBinder.register(callBack);
mPersonBinder = IPersonBinder.Stub.asInterface(binderPool.queryBinder(PERSON_BINDER));
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {}
@Override
public void binderDied() {
try {
mBookBinder.unregister(callBack);
} catch (RemoteException e) {
e.printStackTrace();
}
mBookBinder = null;
mPersonBinder = null;
}
@Override
protected void onDestroy() {
super.onDestroy();
mBinderPoolUtils.unBindPoolService();
}
到此AIDL细节东西基本介绍完毕,模拟实践可以更快更好的理解这些原理;
贴上个人写的AIDL实例:https://github.com/1036711153/Learning
网友评论