AIDL 意思即 Android Interface Definition Language,翻译过来就是Android接口定义语言,是用于定义服务器和客户端通信接口的一种描述语言,可以拿来生成用于IPC的代码。从某种意义上说AIDL其实是一个模板,因为在使用过程中,实际起作用的并不是AIDL文件,而是据此而生成的一个IInterface的实例代码,AIDL其实是为了避免我们重复编写代码而出现的一个模板。
用三方登录举例子,我们自己的应用算是客户端,三方应用比如QQ、微信等是服务端,我们客户端需要跳转到服务端,并让服务端的账号信息授权,然后自动返回我们的客户端,同时拿到服务端的登录授权信息,我们的应用和三方应用是两个独立的进程,普通的通讯方式是没法完成的,因此这里需要用到AIDL。
1 客户端跳转服务端
1.1 AIDL文件编写
Android Studio可以很轻松的编写AIDL文件,在main目录下新建new -> AIDL->AIDL File即可
命名方式一般都是IXXXXAIDLInterface,然后会生成.aidl文件,这里模拟三方登录,就起名ILoginInterface.aidl,需要注意的是,这里的包名和文件名一定要和客户端的aidl文件保持一致
最后是aidl的文件编写,我们首先需要知道我们跟服务端都需要进行进行通讯什么数据,作为登录,首先客户端需要去调用服务端的登录函数,携带的参数可以自己根据业务添加,其次登录完成之后我们还需要拿到客户端返回的登录状态和用户信息。
// ILoginInterface.aidl
package com.itzb.aidldemoserver;
interface ILoginInterface {
//登录
void login();
//登录返回
void loginCallback(boolean loginStatus, String loginName);
}
1.2 客户端Activity
aidl文件编写完成后我们rebuild一下项目,会生成对应的IInterface的实例代码,当然我们可以不对其生成的代码关系,如果需要更深入了解binder机制就得去研究了,本篇不作介绍。客户端的布局文件很简单,就一个跳转登录按钮,点击登录后跳转到服务端去拿数据。我们在登录页面的oncreate中就去绑定客户端aidl远程服务,还有别忘了ondestroy的时候解绑服务。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
bindRemoteService();//开启远程服务
}
//绑定服务端service
private void bindRemoteService() {
Intent intent = new Intent();
intent.setAction("server_ation");//设置服务端应用action(服务唯一标识)
intent.setPackage("com.itzb.aidldemoserver");//设置Server服务端包名
bindService(intent, conn, BIND_AUTO_CREATE);//启动服务
isBindServer = true;//改变标志位
}
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iLoginInterface = ILoginInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onDestroy() {
if (isBindServer) {
unbindService(conn);
}
super.onDestroy();
}
接着当点击跳转登录按钮后,我们调用远程服务的登录方法。
findViewById(R.id.bt_login).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (iLoginInterface != null) {
try {
iLoginInterface.login();
} catch (RemoteException e) {
e.printStackTrace();
}
} else {
Toast.makeText(MainActivity.this, "服务端app未安装", Toast.LENGTH_SHORT).show();
}
}
});
1.3 服务端Service
服务端是另外一个APP,我们新建另一个应用,首先创建aidl文件,这里的aidl应该和客户端的aidl文件一模一样,包括包名和文件层级。准确说应该是客户端应该跟服务端保持一致了,为了讲解逻辑清晰,先编写了客户端的aidl文件,然后rebuild一下,这里不再演示。服务端Service的是被客户端绑定的,绑定成功后,当客户端点击登录按钮调用了aidl中的login方法,服务端自然是需要打开自身的Activity的,代码如下
public class LoginService extends Service {
public LoginService() {
}
@Override
public IBinder onBind(Intent intent) {
return new ILoginInterface.Stub() {
@Override
public void login() throws RemoteException {
Log.d("zb", "服务端收到了: ");
Intent intent = new Intent(LoginService.this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
@Override
public void loginCallback(boolean loginStatus, String loginName) throws RemoteException {
}
};
}
}
我们还需要在清单文件中更改一些service的属性,这样才能正确的匹配到该service。
<service
android:name=".service.LoginService"
android:enabled="true"
android:exported="true"
android:process=":remote_server">
<intent-filter>
<action android:name="server_ation" />
</intent-filter>
</service>
服务端的activity界面就两个edittext,一个账号,一个密码,一个授权登录按钮,页面布局不再讲解,到这里我们单向的从客户端到服务端的通讯以及完成,看下效果喽
2 服务端返回客户端
2.1 服务端Activity
现在我们已经从客户端跳转到了服务端的Activity,接下来就是从服务端如何将登录信息返回给客户端,当然还是通过AIDL来实现了,刚才我们编写的服务端的Service是运行在服务端的进程中的,我们如果调用该Service显然是不能够传输到客户端的,因此我们需要在客户端创建一个Service的,并且在服务端的oncreate应该绑定此服务,只是需要注意这里的intent的action是客户端的action,package也应该是客户端的包名。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_main);
getSupportActionBar().hide();
etName = findViewById(R.id.et_name);
etPwd = findViewById(R.id.et_pwd);
findViewById(R.id.bt_login).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
login();
}
});
bindResultService();//开启远程服务
}
private void bindResultService() {
Intent intent = new Intent();
intent.setAction("result_action");//设置服务端应用action(服务唯一标识)
intent.setPackage("com.itzb.aidldemoclient");//设置Server服务端包名
bindService(intent, conn, BIND_AUTO_CREATE);//启动服务
//改变标志位
isBindServer = true;
}
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iLoginInterface = ILoginInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onDestroy() {
if (isBindServer) {
unbindService(conn);
}
super.onDestroy();
}
接着就是点击登录按钮后,我们这里让线程sleep2秒代码网络延时,然后调用服务的登录回调接口,并finish掉自己
private void login() {
name = etName.getText().toString();
pwd = etPwd.getText().toString();
if (TextUtils.isEmpty(name) || TextUtils.isEmpty(pwd)) {
Toast.makeText(MainActivity.this, "账号或者密码不能为空", Toast.LENGTH_SHORT).show();
return;
}
final ProgressDialog dialog = new ProgressDialog(MainActivity.this);
dialog.setTitle("登录");
dialog.setMessage("登录中......");
dialog.show();
new Thread() {
@Override
public void run() {
super.run();
SystemClock.sleep(2000);
runOnUiThread(new Runnable() {
@Override
public void run() {
try {
dialog.dismiss();
boolean status = false;
if ("123".equals(name) || "456".equals(pwd)) {
status = true;
Log.d("zb", "数据匹配成功");
} else {
status = false;
Log.d("zb", "数据匹配失败");
}
iLoginInterface.loginCallback(status, name);
finish();
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
}.start();
}
2.2 客户端Service
客户端的Service主要就是接受服务端的回调,等回调过来之后其实我们已经算是跨进程通讯完成了,而且是双向的哦,我们这里拿到数据之后简单的发送一条广播告诉Activity吧,实际项目肯定不能用这种方式
public class ResultService extends Service {
public ResultService() {
}
@Override
public IBinder onBind(Intent intent) {
return new ILoginInterface.Stub() {
@Override
public void login() throws RemoteException {
}
@Override
public void loginCallback(boolean loginStatus, String loginName) throws RemoteException {
Log.d("zb", "loginCallback, loginName: " + loginName);
Intent broadcastIntent = new Intent();
broadcastIntent.setAction("result.login");
broadcastIntent.putExtra("loginStatus",loginStatus);
broadcastIntent.putExtra("name", loginName);
sendBroadcast(broadcastIntent);
}
};
}
}
更改清单文件属性
<service
android:name=".servece.ResultService"
android:enabled="true"
android:exported="true"
android:process=":resultService">
<intent-filter>
<action android:name="result_action" />
</intent-filter>
</service>
最后我们在客户端的Activity中注册广播接受者拿到数据,就可以根据业务逻辑该干嘛干嘛了,这里简单用吐司代替,结束这个跨进程通讯吧。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_main);
getSupportActionBar().hide();
......
IntentFilter filter = new IntentFilter("result.login");
registerReceiver(loginReceiver, filter);
}
@Override
protected void onDestroy() {
if (isBindServer) {
unbindService(conn);
}
if (loginReceiver != null) {
unregisterReceiver(loginReceiver);
}
super.onDestroy();
}
private BroadcastReceiver loginReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String name = intent.getStringExtra("name");
Boolean loginStatus = intent.getBooleanExtra("loginStatus", false);
if (loginStatus) {
Toast.makeText(MainActivity.this, "登录成功, name: " + name, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this, "登录失败, name: " + name, Toast.LENGTH_SHORT).show();
}
}
};
再演示下效果喽