Android Studio - AIDL服务学习笔记
一、为什么要有AIDL?
无论学什么东西,最先得弄明白为什么要有这个东西,不要说存在即是合理,存在肯定合理,但是你还是没有明白。对于AIDL有一些人的浅显概念就是,AIDL可以跨进程访问其他应用程序,和其他应用程序通讯,那我告诉你,很多技术都可以访问,如广播(应用A在AndroidManifest.xml 中注册指定Action的广播)应用B发送指定Action的广播,A就能收到信息,这样也能看成不同应用之间完成了通讯(但是这种通讯是单向的);还如 ContentProvider,通过URI接口暴露数据给其他应用访问;但是这种都算不上是应用之间的通讯。可能最让人迷惑的是Android推出来了 Messager,它就是完成应用之间的通讯的。那么为什么还要有AIDL呢,官方文档介绍AIDL中有这么一句话:
Note: Using AIDL is necessary only if you allow clients from different applications to access your service for IPC and want to handle multithreading in your service. If you do not need to perform concurrent IPC across different applications, you should create your interface by implementing a Binder or, if you want to perform IPC, but do not need to handle multithreading, implement your interface using a Messenger. Regardless, be sure that you understand Bound Services before implementing an AIDL.
第一句最重要,“只有当你允许来自不同的客户端访问你的服务并且需要处理多线程问题时你才必须使用AIDL”,其他情况下你都可以选择其他方法,如使用Messager,也能跨进程通讯。可见AIDL是处理多线程、多客户端并发访问的。而Messager是单线程处理。还是官方文档说的明白,一句话就可以理解为什么要有AIDL。那么是不是这样的写个AIDL试试。
备注:1、为什么要有AIDL?引用自“彻底明白Android中AIDL及其使用”
二、如何使用AIDL?
AIDL(Android Interface Definition Language)是一种接口定义语言,编译器通过***.aidl文件的描述信息生成符合通信协议的Java代码,我们无需自己去写这段繁杂的代码,只需要在需要的时候调用即可,通过这种方式我们就可以完成进程间的通信工作。
接下来使用一个模拟下载数据的实例来说明AIDL的基本使用:
一、创建服务端Module步骤如下:
1、首先在AS创建一个服务端的Module: service_aidl,并在此Module里面创建一个AIDL文件(默认文件名即可),如下图所示:
2 、在IMyAidlInterface.aidl文件里定义需要使用的方法:
package com.example.service_aidl; interface IMyAidlInterface { //系统默认创建 void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString); //得到下载进度值 int getProgressValue(); }
3、在AS里面可能需要同步一下代码或者Clean Projecty一次,编译器才会自动生成对应的IMyAidlInterface.java接口文件:
4、定义一个Service的子类,将接口暴露给客户端:
Service子类重写你所需要的方法就行了,不需要重写所有的方法。此处需要特别注意的是onBind方法返回的是一个IMyAidlInterface.Stub的实现类对象,跟踪文件可以知道它是继承了Binder的一个子类。
我在此处定义了一个pro成员变量用于模拟下载进度。
package com.example.aidlservice;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;
import com.example.service_aidl.IMyAidlInterface;
public class AidlService extends Service{
//创建一个变量用于模拟下载进度
private int pro;
//创建IMyAidlInterface.Stub的实现类对象,其继承了Binder
private IMyAidlInterface.Stub AidlBinder = new IMyAidlInterface.Stub() {
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString)
throws RemoteException {
}
@Override
public int getProgressValue() throws RemoteException {
return AidlService.this.pro;
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.d("MyLog","AidlService...onBind");
return AidlBinder;
}
@Override
public boolean onUnbind(Intent intent) {
Log.d("MyLog","AidlService...onUnbind");
return super.onUnbind(intent);
}
@Override
public void onCreate() {
Log.d("MyLog","AidlService...onCreate");
super.onCreate();
new Thread(){
@Override
public void run() {
super.run();
for( ; ;){
AidlService.this.pro++;
Log.d("MyLog","AidlService.this.pro:"+ AidlService.this.pro);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(AidlService.this.pro >= 100){
break;
}
}
}
}.start();
}
@Override
public void onDestroy() {
Log.d("MyLog","AidlService...onDestroy");
super.onDestroy();
}
}
5、在AndroidManifest.xml里面配置Service
<?xml version="1.0" encoding="utf-8"?>Activity和Activity的布局文件默认即可,至此我们的服务端Module就写好了,接下来我们完成客户端的Module:client_aidl。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.service_aidl">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<service android:name="com.example.aidlservice.AidlService">
<intent-filter>
<action android:name="com.example.aidlservice.AidlService"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
</application>
</manifest>
二、创建客户端Module步骤如下:
1、首先在AS创建一个客户端的Module: client_aidl,并在此Module里面创建一个AIDL文件,此处需要特别注意AIDL的文件名和包名以及***.aidl里面内容都必须和服务端一致,换句话说也就是两端的AIDL部分代码必须保持一致,如下图所示:
此处将创建的两个Module截图作为对比和参考:
客户端:
服务端:
2、同样需要在AS里面可能需要同步一下代码或者Clean Projecty一次,编译器才会自动生成对应的IMyAidlInterface.java接口文件:
3、在客户端绑定Service,这里与本地绑定Service不同的是这里的ServiceConnection并不能直接获取到服务端onBind所返回的对象,它只能返回onBind所返回的对象的代理。所以此处需要通过下面的方法来获取代理对象:
IMyAidlInterface stub = IMyAidlInterface.Stub.asInterface(service);
此客户端有一个作为下载点击的Button,一个TextView(默认隐藏)和一个ProgressBar来显示进度(默认隐藏),下面是Activity代码和布局文件代码:
package com.example.client_aidl;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.example.service_aidl.IMyAidlInterface;
public class MainActivity extends AppCompatActivity {
//ProgressBar组件
ProgressBar progressBar;
//TextView组件
TextView textView;
//IMyAidlInterface对象
IMyAidlInterface stub = null;
//服务绑定成功标志
boolean bindFlag;
//下载数据标记
boolean downloadFlag;
//创建ServiceConnection对象
private ServiceConnection conn = new ServiceConnection() {
//服务绑定成功后调用
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//从第二个参数取出服务端返回的对象代理
MainActivity.this.stub = IMyAidlInterface.Stub.asInterface(service);
bindFlag = true;
Log.i("MyLog", "stub...: 绑定服务成功");
doDownloadData();
}
//服务非正常断开由系统调用 普通断开不会调用
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getALLUserView();
}
//得到所有组件
private void getALLUserView() {
progressBar = (ProgressBar) this.findViewById(R.id.progressBar);
textView = (TextView) this.findViewById(R.id.downloadText);
}
//下载按钮点击事件
public void downloadButton(View view) {
if (!bindFlag && !downloadFlag) {
//绑定服务
doBindService();
//下载数据
//doDownloadData();
downloadFlag = true;
}
}
//绑定服务
private void doBindService() {
//创建意图,并隐式绑定服务
Intent intent = new Intent();
//service_aidl 服务所在Activity包路径 5.0后需指定
intent.setPackage("com.example.service_aidl");
intent.setAction("com.example.aidlservice.AidlService");
this.bindService(intent, this.conn, Service.BIND_AUTO_CREATE);
}
//下载数据
private void doDownloadData() {
Log.i("MyLog", "stub...: doDownloadData");
if (stub != null) {
Log.i("MyLog", "stub...: " + stub.toString());
new MyAsyncTask().execute(MainActivity.this.stub);
}
}
//解绑服务
private void doUnBindService() {
this.unbindService(this.conn);
bindFlag = false;
}
@Override
protected void onDestroy() {
super.onDestroy();
//解绑服务
doUnBindService();
}
//创建AsyncTask对象
public class MyAsyncTask extends AsyncTask<IMyAidlInterface, Integer, String> {
@Override
protected void onPreExecute() {
super.onPreExecute();
//设置显示
progressBar.setVisibility(View.VISIBLE);
textView.setVisibility(View.VISIBLE);
}
//接收服务端下载数据
@Override
protected String doInBackground(IMyAidlInterface... params) {
int pro = 0;
for (; ; ) {
try {
Thread.sleep(500);
//得到进度值
if (params[0] != null) {
pro = params[0].getProgressValue();
this.publishProgress(pro);
}
if (pro == 100) {
//解绑服务
doUnBindService();
downloadFlag = false;
break;
}
} catch (RemoteException | InterruptedException e) {
e.printStackTrace();
}
}
return "下载成功!";
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
int tmp = values[0];
progressBar.setProgress(tmp);
textView.setText("下载进度: " + tmp + "%");
}
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
Toast.makeText(MainActivity.this, s, Toast.LENGTH_SHORT).show();
}
@Override
protected void onCancelled() {
super.onCancelled();
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.client_aidl.MainActivity">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#ff0"
android:onClick="downloadButton"
android:text="@string/downloadButton"
android:textSize="18sp"/>
<TextView
android:id="@+id/downloadText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="100dp"
android:visibility="invisible"/>
<ProgressBar
android:id="@+id/progressBar"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:max="100"
android:progress="0"
android:visibility="invisible"/>
</LinearLayout>
最后先将服务端安装到虚拟机,在将客户端安装到虚拟机。在客户端点击下载得到下面是执行效果:
从代码可以看出,使用AIDL线程间通信只是在绑定Service的回调部分有点差别,其他都和本地绑定基本差不多,代码注释比较清楚,就不再赘述了。
新手学习笔记,不足之处望指出,有什么疑问欢迎大家一起讨论。