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();
}
}
};
再演示下效果嘍