在研究DroidPlugin插件和service进程与主进程需要交互时都会用到AIDL来实现进程通信,今天回头整理资料的时候,发现AIDL有些模糊了,所以有些比较重要的知识点还是有必要把它记录下来。
AIDL,全称是Android interface definition language,Android接口描述语言。我们知道,每一个进程都有自己的Dalvik VM实例,都有自己的一块独立的内存,互不影响。那我们要实现进程间通信,该怎么做呢?这个时候AIDL就起作用了,下面就带大家一步一步实现进程通信。
(由于界面排版问题,下面是以图片的形式讲解,在最后面会将源码贴出来)
1.在Android工程中,新建一个AIDL文件,此时as会自动生成AIDL目录,跟java目录并列。如图:
2.编写AIDL接口方法,就以登录功能为例。写完后,一定要build下工程,让系统生成对应的java文件。
3.创建一个服务进程,实现刚写的AIDL接口,提供检查登录输入的用户名和密码是否正确的功能。并在onBind方法中返回AIDL接口对象。
4.在清单文件里注册该service,并以进程形式存在。android:process=”:checkLogin”:表示新开进程
5.现在我们就可以在主进程调用该接口实现进程通信了,先上代码
代码还是很清晰,第一块是创建serviceConnection回调对象,在连接成功后会回调onServiceConnected()方法,然后通过IMyAidl.Stub.asInterface()来获取IMyAidl接口对象。第二块是绑定检查登录服务,绑定时将serviceConnection回调对象传入。第三块是当点击登录按钮时通过IMyAidl对象调用checkLogin方法去判断登录是否成功。此时就是进程通信了。第四块是当activity被销毁时,将此服务解绑。
6.编译生成apk后,运行效果:
进程通信基本上结束了,但是,有些需求是需要进程间传递自定义数据结构,这个怎么实现呢?那我们也来一起试试吧,毕竟这个需求也是很大的。
1.新建一个数据结构LoginBean类,要实现在进程间传递的话就必须实现Parcleable接口,为啥不能用Serializable ? 因为 第一,Parcleable序列化和反序列化的效率均比Serializable接口高;第二,Parcelable接口是Android所特有的,AIDL进行在进程间通信(IPC),都是需要实现Parcelable接口。实现Parcleable接口就需要实现writeToParcel、describeContents函数以及静态的CREATOR变量,请看图:
这个类最好是放在AIDL包下,方便移植,如果说我们需要与别的应用进行通信时,那么在别的应用里也需要放一个AIDL包,而且包名和内容都要一样。
2.在AIDL包下新建一个LoginBean.aidl文件,表示引入了一个序列化对象 LoginBean 供其他的AIDL文件使用,如果不做这一步就会编译不通过;
3.在IMyAidl接口里新增login方法,在接口里引用了LoginBean类。
此时编译肯定不通过。第一,没有导包;就是上面第一个红框。记得LoginBean这个类一定要导包,虽然在同一个包下;第二,as是使用 Gradle 来构建 Android 项目的, 默认是将 java 代码的访问路径设置在 java 包下的,而LoginBean类是在AIDL包下的,所以不认识这个类。我们可以在build文件中配置下就可以解决问题。
4.在服务端实现这个接口方法。
5.在主进程调用这个接口。
6.编译生成apk,运行效果也是一样。
其实AIDL没有想象的那么难,只是有些繁琐和需要多些细心就好了,下面是我总结需要注意的几个点:
在有些文章上看到接口方法里用到了“in”“out”“inout”,它们什么意思呢?
in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。inout不要乱用,用的不好会加大系统开销。
如 boolean login(in LoginBean data); 服务端将收到这个对象的完整数据,但客户端的那个对象不会因为服务端对传参的修改而发生变动。
boolean login(out LoginBean data); 服务端将收到个空对象,但在服务端对接收到的空对象有任何修改之后客户端将会同步变动。
boolean login(inout LoginBean data); 服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。
当进程通信用到自定义数据结构时,如LoginBean.java,此时就必须要生成对应的AIDL文件,不然导致编译不成功。
如:// LoginBean.aidl的内容
package com.dalongtech.textapk;
parcelable LoginBean;//注意parcelable是小写
当第二步做完后,发现还是编译不通过,原因是LoginBean.java在AIDL文件中不认识,解决方法:
在build文件中添加:
sourceSets {
main {
java.srcDirs = [‘src/main/java’, ‘src/main/aidl’]
}
}
可序列化数据结构需要实现Parcelable接口
到此呢,进程通信讲解就真正结束了!如果屏前的你有一定的收获的话,那么请您给笔者点个赞或者留个言吧~
谢谢!
核心代码:
IMyAidl.aidl
package com.dalongtech.textapk;
import com.dalongtech.textapk.LoginBean;
// Declare any non-default types here with import statements
interface IMyAidl {
boolean checkLogin(String name,String psw);
boolean login(in LoginBean data);
}
LoginBean.aidl
package com.dalongtech.textapk;
parcelable LoginBean;//注意parcelable是小写
LoginBean.java
package com.dalongtech.textapk;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Created by sunmoon on 2017/7/6 0006.
*/
public class LoginBean implements Parcelable {
private String loginName;
private String loginPsw;
private int loginSucc;
public LoginBean(){}
protected LoginBean(Parcel in) {
loginName = in.readString();
loginPsw = in.readString();
loginSucc = in.readInt();
}
public static final Creator CREATOR = new Creator() {
@Override
public LoginBean createFromParcel(Parcel in) {
return new LoginBean(in);
}
@Override
public LoginBean[] newArray(int size) {
return new LoginBean[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(loginName);
dest.writeString(loginPsw);
dest.writeInt(loginSucc);
}
public void readFromParcel(Parcel dest) {
//注意,此处的读值顺序应当是和writeToParcel()方法中一致的
loginName = dest.readString();
loginPsw = dest.readString();
loginSucc = dest.readInt();
}
public String getLoginName() {
return loginName;
}
public void setLoginName(String loginName) {
this.loginName = loginName;
}
public String getLoginPsw() {
return loginPsw;
}
public void setLoginPsw(String loginPsw) {
this.loginPsw = loginPsw;
}
public int getLoginSucc() {
return loginSucc;
}
public void setLoginSucc(int loginSucc) {
this.loginSucc = loginSucc;
}
}
CheckLoginService.java
package com.dalongtech.textapk;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
/**
*
* Created by sunmoon on 2017/7/6 0005.
*/
public class CheckLoginService extends Service {
private static final String UserName = "sunmoon";
private static final String UserPsw = "sunmoon001";
private IMyAidl.Stub mBinder = new IMyAidl.Stub() {
@Override
public boolean checkLogin(String name, String psw) throws RemoteException {
return UserName.equals(name) && UserPsw.equals(psw);
}
@Override
public boolean login(LoginBean data) throws RemoteException {
return UserName.equals(data.getLoginName())&&UserPsw.equals(data.getLoginPsw());
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
MainActivity.java
package com.dalongtech.textapk;
import android.app.Service;
import android.content.ComponentName;
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.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private EditText mUserNameEt;
private EditText mUserPswEt;
private IMyAidl myAidl;
//与service连接
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myAidl = IMyAidl.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
myAidl = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.loginAct_toolbar);
toolbar.setTitle("登录页面");
mUserNameEt = (EditText)findViewById(R.id.loginAct_userName);
mUserPswEt = (EditText)findViewById(R.id.loginAct_userPsw);
//绑定检查登录服务
Intent intent = new Intent(this,CheckLoginService.class);
bindService(intent,mConnection, Service.BIND_AUTO_CREATE);
findViewById(R.id.loginAct_loginBtn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
// boolean loginSucc = myAidl.checkLogin(mUserNameEt.getText().toString(),
// mUserPswEt.getText().toString());
LoginBean bean = new LoginBean();
bean.setLoginName(mUserNameEt.getText().toString());
bean.setLoginPsw(mUserPswEt.getText().toString());
boolean loginSucc = myAidl.login(bean);
if(loginSucc) {
startActivity(new Intent(MainActivity.this,SecondActivity.class));
return;
}
} catch (Exception e) {}
Toast.makeText(MainActivity.this,"用户名或密码错误",Toast.LENGTH_LONG).show();
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mConnection);
}
}