前言
我们在说AIDL
使用之前先说下Service
.Service
是Android
四大组件之一,它是不依赖于用户界面的,所以我们常常用于进行一些耗时的操作,比如:下载数据,更新等操作,我们下面就先简单走一遍这个Service
Service
启动
启动方式有两种:startService
和bindService
startService
:主要用于启动一个服务执行后台任务,不进行通信。
停止服务使用stopService
;bindService
:该方法启动的服务可以进行通信。
停止服务使用unbindService
;
这里说明下:Service
和Thread
没有任何关系
-
Thread
:Thread
是程序执行的最小单元,它是分配CPU的基本单位。可以用Thread
来执行一些异步的操作。 -
Service
:Service
是android
的一种机制,当它运行的时候如果是Local Service
,那么对应的Service
是运行在主进程的main
线程上的。如:onCreate
,onStart
这些函数在被系统调用的时候都是在主进程的main
线程上运行的。如果是RemoteService
,那么对应的Service
则是运行在独立进程的main
线程上。因此请不要把Service
理解成线程,它跟线程半毛钱的关系都没有!同样如果在Service
中执行耗时操作,一样是需要开起线程,否则会引起ANR
首先我们先新建一个Service
类,重写一下里面的方法
/**
*
* @author HuangFusheng
* @date 2019-10-23
* description 本地服务
*/
public class LocalService extends Service {
private static final String TAG = "LocalService";
public LocalService() {
}
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate: --->");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand: --->");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: --->");
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
}
Service
属于四大组件之一,所以还需要在清单文件中注册下
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hfs.aidlsample">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<service
android:name=".LocalService"
android:enabled="true"
android:exported="true"></service>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Ok,然后我们在MainActivity
中写两个方法启动服务和停止服务
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="startService"
android:text="开启服务" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="stopService"
android:text="停止服务" />
</LinearLayout>
我们先点击开启服务,看下log
10-24 20:35:46.324 1488-1488/? D/LocalService: onCreate: --->
10-24 20:35:46.324 1488-1488/? D/LocalService: onStartCommand: --->
我们多点击开启服务试试:
10-24 20:35:46.324 1488-1488/? D/LocalService: onCreate: --->
10-24 20:35:46.324 1488-1488/? D/LocalService: onStartCommand: --->
10-24 20:37:41.924 1488-1488/? D/LocalService: onStartCommand: --->
10-24 20:37:42.590 1488-1488/? D/LocalService: onStartCommand: --->
10-24 20:37:42.756 1488-1488/? D/LocalService: onStartCommand: --->
10-24 20:37:42.956 1488-1488/? D/LocalService: onStartCommand: --->
10-24 20:37:43.057 1488-1488/? D/LocalService: onStartCommand: --->
10-24 20:37:43.224 1488-1488/? D/LocalService: onStartCommand: --->
我们看到,后面即使多点几下这个开启服务,但是也只会调onStartCommand
方法,onCreate
方法并不会重复调用,因为我们之后每次点击的时候,由于该Service
已经存在,所以并不会重新创建,所以onCreate
方法只会调用一次。
我们点击停止服务,看下log
10-24 20:39:17.624 1488-1488/? D/LocalService: onDestroy: --->
服务此时已经销毁了
我们第一种启动方式已经完成了,这种启动方式呢我们不能和这个Service
进行一些交互,我们只能控制它的启动和销毁,这时我们就得说下Service
的另外一种启动方式了,我们先在MainActivity
中写好绑定服务和解绑服务这两个方法,供我们下面使用
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="startService"
android:text="开启服务" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="stopService"
android:text="停止服务" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="bindService"
android:text="绑定服务" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="unBindService"
android:text="解绑服务" />
</LinearLayout>
我们首先看下bindService
这个方法
public boolean bindService(Intent service, ServiceConnection conn, int flags) {
...
}
可以看见这个方法需要三个参数:
- 第一个,
Intent
传的值 - 第二个就是
service
连接对象,类似操作数据库时的链接对象 - 第三个就是我们的
flags
这样的一个参数,一般是BIND_AUTO_CREATE
然后我们在看下unbindService
这个方法
public void unbindService(ServiceConnection conn) {
...
}
主要就是需要一个ServiceConnection
对象,这个和我们bindService
保持一致就行,所以接下来我们先建一个ServiceConnection
类
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
这里会回调两个方法,这两个方法分别会在Activity
与Service
建立关联和解除关联的时候调用
刚才使用startService
方式启动Service
的时候,我们Service
中有个onBind
方法我们并没有去管它,但是用bindService
这种方式启动我们就需要重写这个方法了,它返回的是一个IBinder
,这个是一个接口,它有一个实现类Binder
,所以我们写一个类继承这个Binder
,同时写一个getName
方法一个回调给Activity
@Override
public IBinder onBind(Intent intent) {
return new SimpleBind();
}
public class SimpleBind extends Binder {
public String getName() {
return "我是: Greathfs";
}
}
那么Activity
中怎么调用这个方法呢,这时就用到了咱们之前写好的mServiceConnection
中的onServiceConnected
,这个方法会返回一个IBinder
对象,这个对象就是我们之前在Service
中写的SimpleBind
,所以我们只可以强转为SimpleBind
对象,也就可以调用getName
方法了
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Log.d(TAG, "onServiceConnected: --->");
LocalService.SimpleBind bind = (LocalService.SimpleBind) iBinder;
Log.d(TAG, "onServiceConnected: " + bind.getName());
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
Log.d(TAG, "onServiceDisconnected: --->");
}
};
OK,我们来运行下我们代码,先执行bindService
,看下log
10-24 21:13:36.115 2087-2087/com.hfs.aidlsample D/LocalService: onCreate: --->
10-24 21:13:36.124 2087-2087/com.hfs.aidlsample D/LocalService: onServiceConnected: --->
10-24 21:13:36.124 2087-2087/com.hfs.aidlsample D/LocalService: onServiceConnected: 我是: Greathfs
我们这里发现并没有执行onStartCommand
方法,我们在多点击几次看看
10-24 21:13:36.115 2087-2087/com.hfs.aidlsample D/LocalService: onCreate: --->
10-24 21:13:36.124 2087-2087/com.hfs.aidlsample D/LocalService: onServiceConnected: --->
10-24 21:13:36.124 2087-2087/com.hfs.aidlsample D/LocalService: onServiceConnected: 我是: Greathfs
发现并没有什么变化
我们调用unbindService
方法,看下log
10-24 21:15:51.198 2087-2087/com.hfs.aidlsample D/LocalService: onDestroy: --->
服务销毁,这里有些同学肯定会问为啥onServiceDisconnected
没调用呢?
因为onServiceDisconnected()
方法在连接正常关闭的情况下是不会被调用的, 该方法只在Service
被破坏了或者被杀死的时候调用.例如, 系统资源不足, 要关闭一些Services,
刚好连接绑定的 Service
是被关闭者之一, 这个时候onServiceDisconnected()
就会被调用。
远程服务 AIDL
AIDL(Android Interface Definition Language)
是Android
接口定义语言的意思,它可以用于让某个Service
与多个应用程序组件之间进行跨进程通信,从而可以实现多个应用程序共享同一个Service
的功能。
实际上实现跨进程之间通信的有很多,比如广播
,Content Provider
,但是AIDL
的优势在于速度快(系统底层直接是共享内存),性能稳,效率高,一般进程间通信就用它。
跨进程肯定有两个端,我们这里模拟一下,在工程中再建一个模块server
,整体的项目结构就是下面这样
服务端
我们在服务端下建立一个SimpleAIDLService.aidl
文件,直接使用AS建即可,自动编译
然后,我们在SimpleAIDLService
下增加一个获取名字的方法。
interface SimpleAIDLService {
String getName();
}
然后build
一下,然后会在build - >generated ->source ->aidl->debug
下会生成一个aidl
文件,那说明AIDL
文件已经编译成功。
这个文件创建好之后,我们还需要创建一个Service
进行通信,和上面我们讲Service
启动方式的第二种很像,主要就是onBind
方法那里有些不同
public class MyService extends Service {
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
return new MyBind();
}
public class MyBind extends com.hfs.server.SimpleAIDLService.Stub {
@Override
public String getName() throws RemoteException {
return "我是: AIDL里的Greathfs";
}
}
}
代码看起来是不是很熟悉,唯一不一样的就是原来在本地服务的时候内部类继承的是Binder
,而现在继承的是SimpleAIDLService.Stub
,继承的是我们刚刚建立的aidl
文件,然后实现我们刚刚的定义的getName()
方法
这样服务端的代码基本上就写完了,接下来我们写一下客户端
客户端
首先将刚刚在服务端创建的SimpleAIDLService
原封不动的复制到客户端来。(注意:路径要一模一样),直接复制文件夹
这里为了区分,我们新建一个AIDLActivity
public class AIDLActivity extends AppCompatActivity {
private static final String TAG = "AIDLActivity";
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
SimpleAIDLService simpleAIDLService = SimpleAIDLService.Stub.asInterface(iBinder);
try {
String name = simpleAIDLService.getName();
Log.d(TAG, "onServiceConnected: " + name);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_aidl);
}
public void bindService(View view) {
Intent intent = new Intent();
intent.setAction("com.hfs.server.MyService");
//从 Android 5.0开始 隐式Intent绑定服务的方式已不能使用,所以这里需要设置Service所在服务端的包名
intent.setPackage("com.hfs.server");
bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
}
public void unBindService(View view) {
unbindService(mServiceConnection);
}
}
这里基本上和连接本地服务的代码差不多,只需要注意两个地方,
一个是绑定服务的时候,因为从 Android 5.0开始 隐式Intent
绑定服务的方式已不能使用,所以这里需要设置Service
所在服务端的包名
另一个需要注意的就是获取SimpleAIDLService
对象是通过SimpleAIDLService.Stub.asInterface(iBinder);
获取的,同时基本上只能传递Java的
基本数据类型、字符串、List或Map等。如果想传递一个自定义的类就必须要让这个类去实现Parcelable
接口,并且要给这个类也定义一个同名的AIDL文件
接下来我们运行下代码,看看我们的log能不能打印出来,我们分别运行app
模块和server
模块
10-24 22:09:44.957 2426-2426/com.hfs.aidlsample D/AIDLActivity: onServiceConnected: 我是: AIDL里的Greathfs
可以看到,日志已经出来了,这样的话整体流程也就全部走完了