本文为原创文章,如需转载请注明出处,谢谢!
概述
本篇仅给刚刚接触 Binder 的同学入门,各位大佬可以自行忽略~
对于 Android 初学者来说,IPC 机制无疑是一块难啃的骨头,抽象难懂,而且经常让人深入到无法自拔,经过 Binder 对我 3 天的洗礼,我决定由浅入深地总结一下 Binder 相关的知识。
由于我没学习过 C++,在看其他大神对 framework 层 binder 的工作原理的分析时,就像是在读天书,完全无法理解,这也让我恍然大悟,可能我太过于深入的研究底层以至于应用层的东西我可能都还没理解透彻,导致理解的云里雾里。
所以,我打算第一篇文章就记录以下内容:
1.AIDL 怎么用
2.分析 AIDL 生成的代码
3.自己动手撸一个 AIDL
前提
首先还是要聊聊跨进程的意义何在,我们为什么要选择多进程去开发程序?多进程的优缺点又是什么?接下来,逐一解决这几个问题。
- 为什么要选择多进程去开发程序?
假如程序的某些模块比较特殊,非常耗用内存,这就需要运行在单独的进程中,以缓解主进程的压力。或者有些即时通讯的应用需要一个常驻后台,再或者用一个单独的进程作为守护进程,防止主进程被杀死等等,多进程的使用场景还是非常广泛的。
- 多进程的优缺点?
先说说优点,刚在上一个问题其实已经说到,缓解了主进程的内存压力,我们知道在 Android 中一个应用可以有多个进程,而一个进程就分配一个虚拟机,所以单独一个进程可以分配一个虚拟机的完整内存。
至于缺点就是增大了系统的开销,可能导致耗电量增大,而最主要的缺点就是我们无法在操作同一个内存区域了,于是一些问题就出现了:
(1)静态成员和单例模式完全失效
(2)线程同步机制完全失效
(3)SharedPreferences 的可靠性下降 (并发读写的问题)
(4)Application 会创建多次
所以这就需要 IPC 机制登场了!开始正文部分!
一、先会用 AIDL
看了很多文章,都用的「Android 开发艺术探索」中书的例子,在这咱们换一个俗点儿的,就用存取钱,假如一个进程代表银行,一个进程代表你,你要去银行存取钱就要用到 AIDL 了。好了,废话不说了,直接上代码吧。
1.Money 类
首先先写一个 class Money,实现 Parcelable 接口(序列化相关的知识就不在此文说了,如果不懂请自行 google)
package com.juphoon.ipcdemo;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Created by Android_ZzT on 17/7/20.
*/
public class Money implements Parcelable {
private int id;
private int value;
public Money() {}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public static final Parcelable.Creator CREATOR = new Creator() {
@Override
public Money createFromParcel(Parcel source) {//反序列化过程,从包裹还原对象
return new Money(source);
}
@Override
public Money[] newArray(int size) {
return new Money[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) { //序列化过程,将数据写入包裹
//写入的顺序需要和反序列化时对应
dest.writeInt(id);
dest.writeInt(value);
}
private Money(Parcel source) {
id = source.readInt();
value = source.readInt();
}
}
比较简单,要说明的都在注释了。
2.IBankService.aidl
在 Android Studio 中,在跟目录下右键-->new-->AIDL-->AIDL File ,输入IBankService 即可生成所需文件。
// IBankService.aidl
package com.juphoon.ipcdemo;
// Declare any non-default types here with import statements
import your_package_name.Money; //这里一定要导入 Money
interface IBankService {
void inputMoney(in Money money);
List getMoney();
}
对于不理解AIDL 语法的可参考这篇文章,详细分析了 in out inout,我就不在此赘述了。
http://www.jianshu.com/p/ddbb40c7a251
3.Money.aidl
同样地方法生成 Money.aidl
// Money.aidl
package your_package_name; //注意这里的包名要和 Money 类的包保持一致
parcelable Money; //只需声明这一行就可以
在使用 AIDL 时,如果需要定义自己的类进行序列化,即实现 Parcelable 接口,都需要在 aidl 目录下再声明一下,否则编译时会不通过。
4.Make Project
重新构建项目,编译过后,就会在 generated/source/aidl 目录下生成对应的 java 文件。
5.客户端进程(MainActivity)
过程也很简单,先绑定 Service,绑定成功后获取服务对象,然后调用服务。代码如下:
public class MainActivity extends AppCompatActivity {
private boolean mIsBind;
private IBankService bankService;
private static int moneyId = 1;
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d("zzt", "onServiceConnected: ComponentName " + name);
Log.d("zzt", "onServiceConnected: Binder " + service);
mIsBind = true;
bankService = IBankService.Stub.asInterface(service);
try {
List myMoney = bankService.getMoney();
Log.d("zzt", "Client getMoney: " + myMoney.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d("zzt", "onServiceDisconnected: " + name);
mIsBind = false;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onBind(View view) {
if (!mIsBind) {
Intent intent = new Intent(this, BankService.class);
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
}
public void onUnBind(View view) {
if (mIsBind) {
unbindService(mServiceConnection);
mIsBind = false;
}
}
public void onInputMoney(View view) {
try {
Money money = new Money();
money.setId(moneyId);
money.setValue(100);
moneyId++;
bankService.inputMoney(money);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void onGetMoney(View view) {
try {
List myMoney = bankService.getMoney();
Log.d("zzt", "Client getMoney: " + myMoney.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
代码调用过程如下:
1.ServiceConnection
2.bindService
3.在 onServiceConnected 中获得服务对象(IBankService),其中IBankService.Stub.asInterface(service)
这行代码我们先不管是什么意思,只需要知道通过这个方法可以得到远程服务的对象即可。
4.调用服务
6.Service 进程(BankService),服务端代码
服务端,我们先看代码:
public class BankService extends Service {
private List myMoney = new ArrayList<>();
private IBankService.Stub bankService = new IBankService.Stub() {
@Override
public List getMoney() throws RemoteException {
synchronized (this) {
Log.d("zzt", "BankService getMoney: "+ myMoney);
if (myMoney != null) {
return myMoney;
}
return new ArrayList<>();
}
}
@Override
public void inputMoney(Money money) throws RemoteException {
synchronized (this) {
Log.d("zzt", "BankkService inputMoney: "+ money.toString());
if (myMoney != null) {
if (!myMoney.contains(money)) {
myMoney.add(money);
}
}
}
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.d("zzt","Service onBind");
Log.d("zzt", "onBind: Binder " + bankService);
return bankService;
}
@Override
public void onCreate() {
super.onCreate();
Log.d("zzt","Service onCreate");
Money money = new Money();
money.setId(0);
money.setValue(10000);
myMoney.add(money);
}
}
编码过程如下:
1.最开始我们声明了 IBankService.Stub 类的实例,这里我们先不解释这个类到底做了些什么,只需要知道这是 AIDL 工具帮我们生成的一个抽象类,并且需要使用它的子类来实现我们定义的接口(IBankService)方法,即实现我们想让服务进程真正想为我们提供什么样的服务,我们实现的服务就是维护了一个 List 负责管理钱,然后提供一个能存钱的方法。
2.实现 onBind 方法,此方法需要返回一个 IBinder 对象,直接返回我们实现的 Stub 实例即可,由此可见 Stub 是 IBinder 的子类,具体是什么我们也先不分析,后面小节再做解释,只需明白我们返回的 IBinder 对象将会传给客户端的onServiceConnected(ComponentName name, IBinder service)
方法,供客户端调用。
3.onCreate() 中做一些初始化工作。
此外,还有重要的一点,跨进程跨进程,我们需要跨不同的进程,所以千万别忘了在 Manifest 里将 BankService 声明并指定在其他进程工作,代码如下:
这里还要简单说一下 :remote
,这种方式指定的进程是私有进程,进程名为当前进程+:remote,即com.packagename.ipcdemo:remote
,私有进程不允许其他应用的组件和它跑在一个进程。如果用com.packagename.ipcdemo.remote
这种自命名的方式,就是指定了一个全局进程,其他应用的组件可以通过 ShareUID 方式和它跑在同一个进程。
至此,所有代码已经编写完毕,一个简单的跨进程 demo 就完成了,各位可以自己敲一遍,然后运行一下,看看打印的信息,熟悉一下方法的调用顺序,初步体验一下看不见摸不着虚无缥缈的跨进程操作。这个过程你可能完全体验不到任何感觉,因为 Binder 机制被高度封装了,但是不要着急,我们一点一点深入,在下一节可能就会不知不觉的进入了 Binder 世界,然后慢慢体会吧!
写着写着发现篇幅太多了,所以 AIDL 的分析相关内容放在下一篇。
我也是个初学者,可能有些细节描述的不准确
如写的有问题,可在中给我留言!感谢!