Service是什么?
在android中Service是只在后台执行操作且没有页面的组件。Service可以被Service、Activity、ContentProvider启动/绑定,但是不可以被Broadcast启动/绑定。换句话说,Activity、ContentProvider可以绑定到Service并与之交互,甚至进行IPC通讯。
服务根据用途不同而分为两类:启动的服务和绑定的服务。
Service的基本用法。
要实现自己的Service去完成一些后台操作,必须继承自Service,并重写它的onCreate(), onStartCommand(), onDestroy()方法。
public class MyService extends Service{
public static final String TAG = "MyService";
@Override
public void onCreate(){
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId){
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy(){
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent){
return null;
}
}
复制代码
然后在androidManifest.xml中进行注册
<Service android:name="packageName.MyService"/>
复制代码
这样一个完整的Service就完成了,现在我们只需要启动它就可以了。启动Service的方法和启动Activity的方法类似:
Intent intent = new Intent(this, MyService.class);
startService(intent);
复制代码
既然有启动Service,那么肯定有停止Service,停止Service的方法为:
Intent intent = new Intent(this, MyService.class);
stopService(intent);
复制代码
注意:无论启动多少次Service,只要Service还没有被stop,那么Service#onCreate方法始终只执行一次。换言之,如果Service已经被启动了,Service#onCreate就不再执行了,但onStartCommand每次都会执行。
Service虽然被用来处理一些后台任务,但它运行在主线程(MainThread),因此如果直接在onStartCommand()中执行耗时操作,则会阻塞主线程,具体表现为:除非onStartCommand()的耗时操作执行完毕,否则界面上无法进行任何操作。所以如果要在Service中执行耗时操作(例如网络请求,IO流处理),则必须创建Thread来完成这些耗时操作。那么有的朋友可能会问,既然后台的耗时操作要使用Thread来执行,那么又为什么使用Service呢?直接使用Thread就可以了呀!其实使用Service的原因在于android系统将Service的优先级设置的很高,也就是说,当系统因为内存不足而要回收某些任务时,Thread会比Service先被销毁来释放内存,而且Thread的存活与否与启动它的宿主(例如Activity)的销毁与否无关,那么如果宿主被销毁了,我们就无法控制Thread的存活了,但是Service则不同,我们可以随时随地的控制Service的存活。
其实,android还提供了一个叫做IntentService的组件来实现耗时操作的Service,也就是说,如果你使用IntentService替代Service,那么就不需要new一个Thread来执行耗时操作了,IntentService本身就提供了这种机制。
那么,现在我们来考虑一下:首先在Activity中启动Service,如果之后该Activity被finish掉了,那么此时Service会被destroy吗?答案是NO。想想看,既然Service是用来执行后台操作的,那么它肯定要独立于启动它的组件,否则Service就无法一直在后台执行了。但是,后面我们要说的绑定Service,则不是这样,如果绑定Service的客户端被销毁了,则该Service也被销毁,具体我们后边再来详细说明。
Service#onStartCommand()的返回值
在上一节我们注意到在Service#onStartCommand中返回了一个int值,那么这个值有什么作用呢?
Service#onStartCommand()可以返回四个值:START_STICKY, START_NOT_STICKY, START_REDELIVER_INTENT, START_STICKY_COMPATIBILITY.
START_STICKY:返回该值则表示如果Service所在的进程被系统kill之后,将该Service置于started(已启动)状态,但是不会将第一次启动Service时的intent值恢复。因为该Service处于started状态,所以会保证重启Service之后再次执行onStartCommand()方法,此时如果没有intent传递过来,则将以null object的形式去调用。
START_NOT_STICKY:返回该值则表示如果Service所在的进程被系统kill之后,则系统不会主动重启该Service。
START_REDELIVERY_INTENT:见名知意,返回该值则表示如果Service所在的进程被系统kill之后,将该Service置于started(已启动)状态并将之前的intent值恢复。
START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,而且不保证Service所在的进程被kill之后,Service会被重启。
IntentService的基本用法及原理
正如上面所说的,IntentService是一种特殊的service,可以直接在里面执行耗时操作。
IntentService的基本用法
首先我们肯定要继承IntentService,然后实现onHandleIntent(Intent intent)方法。
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
//do something consuming time.
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
复制代码
然后在AndroidManifest中注册:
<service
android:name=".service.MyIntentService"
android:exported="false"/>
复制代码
之后我们像启动/停止Service一样去启动/停止IntentService即可。之后我们就可以在IntentService#onHandleIntent中执行耗时操作。
我们看到在注册service时,有时会使用android:exported这个属性,这个属性用来说明该Service是否可以被外部应用使用。默认为false。
IntentService原理解析
在android中,通常使用Handler来实现线程切换及协作,使用HandlerThread来创建一个带有Looper的线程。那么我们来看一下IntentService的源码,看看它是不是使用了Handler和HandlerThread来实现的。
public abstract class IntentService extends Service {
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
private String mName;
private boolean mRedelivery;
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);//停止自己
}
}
/**
* Creates an IntentService. Invoked by your subclass's constructor.
*
* @param name Used to name the worker thread, important only for debugging.
*/
public IntentService(String name) {
super();
mName = name;
}
/**
* Sets intent redelivery preferences. Usually called from the constructor
* with your preferred semantics.
*
* <p>If enabled is true,
* {@link #onStartCommand(Intent, int, int)} will return
* {@link Service#START_REDELIVER_INTENT}, so if this process dies before
* {@link #onHandleIntent(Intent)} returns, the process will be restarted
* and the intent redelivered. If multiple Intents have been sent, only
* the most recent one is guaranteed to be redelivered.
*
* <p>If enabled is false (the default),
* {@link #onStartCommand(Intent, int, int)} will return
* {@link Service#START_NOT_STICKY}, and if the process dies, the Intent
* dies along with it.
*/
public void setIntentRedelivery(boolean enabled) {
mRedelivery = enabled;
}
@Override
public void onCreate() {
// TODO: It would be nice to have an option to hold a partial wakelock
// during processing, and to have a static startService(Context, Intent)
// method that would launch the service & hand off a wakelock.
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public void onStart(Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
/**
* You should not override this method for your IntentService. Instead,
* override {@link #onHandleIntent}, which the system calls when the IntentService
* receives a start request.
* @see android.app.Service#onStartCommand
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
@Override
public void onDestroy() {
mServiceLooper.quit();
}
/**
* Unless you provide binding for your service, you don't need to implement this
* method, because the default implementation returns null.
* @see android.app.Service#onBind
*/
@Override
public IBinder onBind(Intent intent) {
return null;
}
/**
* This method is invoked on the worker thread with a request to process.
* Only one Intent is processed at a time, but the processing happens on a
* worker thread that runs independently from other application logic.
* So, if this code takes a long time, it will hold up other requests to
* the same IntentService, but it will not hold up anything else.
* When all requests have been handled, the IntentService stops itself,
* so you should not call {@link #stopSelf}.
*
* @param intent The value passed to {@link
* android.content.Context#startService(Intent)}.
*/
@WorkerThread
protected abstract void onHandleIntent(Intent intent);
}
复制代码
从源码我们可以看出,IntentService正是使用了Handler和HandlerThread来实现的,具体实现流程如下: 首先,我们要启动IntentService,那么则肯定会先执行onCreate方法,我们来看看其源码:
@Override
public void onCreate() {
// TODO: It would be nice to have an option to hold a partial wakelock
// during processing, and to have a static startService(Context, Intent)
// method that would launch the service & hand off a wakelock.
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
复制代码
在onCreate中,首先使用HandlerThread来创建一个带有Looper的线程,指定该线程的名字叫做mName,然后启动线程,接着得到该线程的Looper对象,得到Looper之后再去初始化Handler。至此,就完成了线程创建和线程切换的准备工作。
接着会去调用IntentService#onStartCommand()【先忽略返回值吧?】,而onStartCommand又调用了onStart(),那么我们来看看onStart()方法的具体实现:
@Override
public void onStart(Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
复制代码
从源码可以看出,在onStart中向Handler发送了一个Message,handler在接收到该Message之后会回调Handler#handlerMessage(Msg)方法,我们再来看看ServiceHandler的实现:
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);//停止自己
}
}
复制代码
可以看到,在handleMessage方法中执行了耗时操作onHandleIntent,执行完耗时操作之后就停止自己。
至此,IntentService的实现原理已经非常清楚了:使用了HandlerThread来创建线程执行耗时操作,使用Handler来发送消息。
注意: IntentServie执行完耗时操作之后会自动停止
在此,还有一个隐藏的知识点需要说明:我们已经知道,多次启动Service之后,Service#onCreate()只会执行一次,Service#onStartCommand()则会执行多次,那么在IntentService中,handler就会多次发送消息并执行耗时操作。由于Handler的消息机制使用了队列,那么在IntentService中,多次启动Service之后,这些耗时操作也会排队执行。
在此有的朋友就会问了:在IntentService中,执行完耗时操作之后会stopSelf,那么此时有多个任务排队等待处理,这岂不是自相矛盾吗?事实上不是如此,接下来我们一一说明:
我们可以看到在Service#onStartCommand(Intent intent, int flags, int startId)和stopSelf(int startId)中有一个参数startId,该参数标识每次启动Service的请求。startId初始值为1,如果在启动Service时,该Service已经被启动且未执行完毕,那么startId递增;如果启动Service时,该Service之前未被启动或已经自停止,则startId为1。startId标识了启动Service这个请求和对应的intent。所以每次stopSelf(startId)时,会关闭当前startId对应的Service,和队列中其他的Service无关。
Bound Service
前面我们使用启动Service的方式使得Service来完成一些后台任务,但是启动Service的客户端和Service之间并没有做任何交互,那么二者如果要进行交互,则必须使用绑定Service的方式。
我们知道四大组件都可以通过设置android:process=":remote"属性让其本身处于另一个进程,所以当我们绑定Service时,就有本地绑定和远程绑定之分。很明显本地绑定是指Service和客户端处于同一个进程,远程绑定则是Service和客户端处于不同的进程。其实使用startService(intent)启动Service时,也有本地启动和远程启动之分,但是通过startService(intent)启动的Service和客户端之间并没有做任何交互,而远程服务通常提供一些系统服务,需要客户端和Service之间进行交互,因此启动Service时无需使用远程模式。
本地绑定
首先我们来看一下本地绑定的基本用法。在最开始的MyService中,有一个onBind()方法,我们让它返回null
@Override
public IBinder onBind(Intent intent){
return null;
}
复制代码
其实onBind方法就是实现绑定服务的关键方法,要实现绑定服务必须要返回一个IBinder对象。
public class MyService extends Service{
public static final String TAG = "MyService";
@Override
public void onCreate(){
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId){
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy(){
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent){
return new MyBinder();
}
public class MyBinder extends Binder {
public void doSomething(){
//do something
}
}
}
复制代码
虽然我们返回了一个IBinder对象,但是我们如何在客户端来接受这个IBinder对象呢?系统为我们实现了ServiceConnection这个类来实现客户端与服务的连接。
private ServiceConnection conn = new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName name, IBinder service){
MyBinder mb = (MyService.MyBinder)service;
mb.doSomething();
}
/**
* 当服务连接异常终止后调用
*/
@Override
public void onServiceDisconnected(ComponentName name){
}
}
....
/**
* 自定义方法,来实现绑定Service.
*/
private void bindService(){
Intent bindIntent = new Intent(this, MyService.class);
bindService(bindIntent, conn, BIND_AUTO_CREATE);
}
/**
* 自定义方法,来实现解绑Service.
*/
private void unbindService(){
unbindService(conn);
}
复制代码
从这段代码我们可以看出,通过使用ServiceConnection来建立客户端与Service的连接。当连接成功之后会回调ServiceConnection#onServiceConnected(ComponentName name, IBinder service)方法,将IBinder对象传递过来,这样我们就可以像操作自己的方法一样去操作Service中Binder对象的方法。
注意:Service只能unbind一次,否则会报错: java.lang.IllegalArgumentException: Service not registered
远程绑定
现在我们来想想,如果我们直接将MyService改为远程服务,会有什么效果呢?不妨来做一个测试:直接设置MyService的android:process=":remote"。设置完之后,当执行bindService时,报以下错误:
AndroidRuntime: FATAL EXCEPTION: main Process: cn.codemperor, PID: 10998
java.lang.ClassCastException: android.os.BinderProxy cannot be cast to cn.codemperor.service.MyService$MyBinder
复制代码
咦?很奇怪!怎么会有android.os.BinderProxy这个东东呢?我们在任何地方都没有声明或使用它呀,它是怎么来的呢?是怎样和Service关联起来的呢?
要解决这个问题,必须先知道,远程绑定或者说访问远程服务都是IPC的,什么是IPC呢?IPC就是Inter Process Communication,即进程间通讯。在android中实现继承间通讯的方式是AIDL(Android Interface Define Language),即android接口定义语言。接下来我们先详细了解一下AIDL。
AIDL基本概念及用途
首先我们应该明白AIDL的作用是:实现p1进程与p2进程之间的通信。也就是说AIDL提供了一种机制:只要定义了AIDL文件,并在AIDL文件中规定业务逻辑,然后系统就会自动根据业务逻辑实现p1与p2之间的通信。具体表现如下:
AIDL文件-->定义业务逻辑
|
系统生成实现进程间通信的类文件-->生成Stub接口
|
实现Stub接口-->实现业务逻辑
复制代码
这样一看,我们局很明确的知道AIDL实现进程间通讯的机制。其中Stub是Binder的子类。
那么接下来我们通过一个实例来具体说明一下远程绑定的实现
远程服务实例--数据访问
首先,我们新建IDataAccess.aidl文件,并在启动数据访问方法accessData。
**注意:**在aidl文件中,不能出现private, public, protect修饰符。在AIDL文件中仅能支持基本类型、Map、List、自定义类型。因为自定义类型和其他类型的用法有所区别,我们先来看看非自定义类型的用法。
package cn.codempero.summary;
// Declare any non-default types here with import statements
interface IDataAccess {
boolean accessData();
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
复制代码
然后系统会在/build/generated/source/aidl/debug/packageName/文件夹下自动生成IDataAccess.java文件,这个接口就实现了IPC。我们来看一下它的源代码
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: D:\\LAB\\Development\\Summary\\app\\src\\main\\aidl\\cn\codemperor\summary\\IDataAccess.aidl
*/
package cn.codemperor.summary;
// Declare any non-default types here with import statements
public interface IDataAccess extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements cn.codempero.summary.IDataAccess
{
private static final java.lang.String DESCRIPTOR = "cn.codempero.summary.IDataAccess";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an cn.codempero.summary.IDataAccess interface,
* generating a proxy if needed.
*/
public static cn.codempero.summary.IDataAccess asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof cn.codempero.summary.IDataAccess))) {
return ((cn.codempero.summary.IDataAccess)iin);
}
return new cn.codempero.summary.IDataAccess.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code)
{.
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_accessData:
{
data.enforceInterface(DESCRIPTOR);
boolean _result = this.accessData();
reply.writeNoException();
reply.writeInt(((_result)?(1):(0)));
return true;
}
case TRANSACTION_basicTypes:
{
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
long _arg1;
_arg1 = data.readLong();
boolean _arg2;
_arg2 = (0!=data.readInt());
float _arg3;
_arg3 = data.readFloat();
double _arg4;
_arg4 = data.readDouble();
java.lang.String _arg5;
_arg5 = data.readString();
this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements cn.codempero.summary.IDataAccess
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public boolean accessData() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
boolean _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_accessData, _data, _reply, 0);
_reply.readException();
_result = (0!=_reply.readInt());
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
@Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(anInt);
_data.writeLong(aLong);
_data.writeInt(((aBoolean)?(1):(0)));
_data.writeFloat(aFloat);
_data.writeDouble(aDouble);
_data.writeString(aString);
mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_accessData = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public boolean accessData() throws android.os.RemoteException;
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;
}
复制代码
我们可以看到,在IDataAccess接口里有一个叫做Stub的类,它实现了IDataAccess接口并继承自Binder,那么该类就是用来实现IPC通讯的本地Stub类。
那么现在我们还需要创建一个远程服务来实现具体的业务逻辑accessData。
public class MyRemoteService extends Service {
private static final String TAG = "MyRemoteService";
public MyRemoteService(){}
public IBinder onBind(Intent intent){
}
public class DataAccessBinder extends IDataAccess.Stub {
@Override
public boolean accessData(){
Log.i(TAG, "You can access Me!");
return true;
}
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString){}
}
}
复制代码
接下来我们就需要将MyRemoteService注册在androidManifest.xml中
<service
android:name="cn.codemperor.service.MyRemoteService"
android:exported="true"
android:enable="true"
android:process=":remote"
/>
复制代码
那么现在我们只需要像绑定本地服务一样绑定远程服务即可(真的可以吗?回想一下讲解远程绑定是的第一个异常
private ServiceConnection conn = new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName name, IBinder service){
MyRemoteService.MyBinder mb = (MyRemoteService.MyBinder)service;
try {
mb.endCall();
} catch (RemoteException e) {
e.printStackTrace();
}
}
/**
* 当服务连接异常终止后调用
*/
@Override
public void onServiceDisconnected(ComponentName name){
}
}
....
/**
* 自定义方法,来实现绑定Service.
*/
private void bindService(){
Intent bindIntent = new Intent(this, MyService.class);
bindService(bindIntent, conn, BIND_AUTO_CREATE);
}
/**
* 自定义方法,来实现解绑Service.
*/
private void unbindService(){
unbindService(conn);
}
复制代码
卧槽!卧槽!卧槽!怎么会奔溃了呢?什么玩意儿?客官别急,请先尝试一下下面的方法:
private ServiceConnection conn = new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName name, IBinder service){
IDataAccess iDataAccess = IDataAccess.Stub.asInterface(service);
try {
iDataAccess.accessData();
} catch (RemoteException e) {
e.printStackTrace();
}
}
/**
* 当服务连接异常终止后调用
*/
@Override
public void onServiceDisconnected(ComponentName name){
}
}
复制代码
哎呀!卧槽!对了!
我们来分析一下IDataAccess.Stub.asInterface(service)究竟做了什么
public static cn.codempero.summary.IDataAccess asInterface(android.os.IBinder obj){
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof cn.codempero.summary.IDataAccess))) {
return ((cn.codempero.summary.IDataAccess)iin);
}
return new cn.codempero.summary.IDataAccess.Stub.Proxy(obj);
}
复制代码
看了代码之后,仔细一想,对哟,通过AIDL访问的可是远程服务哎,是不处于同一个进程的呀!那么传递过来的IBinder对象不可能是本进程的IDataAccess呀,所以必须使用代理来访问。
说了这么多,远程Service使用起来如此复杂,好像还不知道为什么要用远程Service呢?
远程Service通常用来提供一些进程间共享的服务,通俗一点来说就是为手机上的App提供一个公共的服务,每个App都可以访问这个服务。
呵呵呵,那怎么在其他的App中访问远程服务呢?毕竟不同的App有不同的UID呀!
在其他应用程序中访问远程服务
最终我们的目的是:所有具有权限的应用程序都可以访问远程服务。那么只需要遵循以下流程即可访问到远程服务:
1. 新建名为ClientTest项目
2. 将IDataAccess.aidl同包路径一起拷贝过来(该路径需要和ClientTest路径同级)
|——src
| └── main
| | | └── java
| | | └── cn.codemperor.clienttest
| | | └── cn.codemepror.summary
| | | | └── IDataAccess.aidl
复制代码
3. 使用隐式Intent来访问远程服务
但是,呵呵呵,android5.0及以上可是禁止通过隐式intent的方式来访问Service的呀。所以呢?我们来了解一下shareUserId吧!
在说shareUserId之前,我们先来说一下Android中的UID和PID吧!
我们已经知道,一个应用程序可以有多个进程,每个进程都有自己的PID,那么android中是如何来唯一标识应用程序的呢?linux是支持多用户多进程的,所以设计师采用UID来唯一标识一个用户,但是Android中只支持单用户,所以Google就将UID来唯一标识一个应用程序了,应用程序之间也可以通过UID来实现数据共享了。呵呵哒,说了这么多,具体应该怎么实现呢?so easy!只需要让两个应用程序拥有相同的shareUserId即可。
应用程序A
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.codemperor.aApplication"
      android:versionCode="1"
      android:versionName="1.0"
android:sharedUserId="com.codemperor.share"/>
复制代码
应用程序B
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.codemperor.bApplication"
     android:versionCode="1"
     android:versionName="1.0"
android:sharedUserId="com.codemperor.share"/>
复制代码
哈哈哈,现在我们就可以使用显示intent的方式来绑定远程Service了(当然也可以通过同样的方式启动Activity,本地Service,BroadcastReceiver)。但是机智的朋友就会问了:如果我知道了你的shareUserId,那么我就可以随意访问你的应用程序咯?哈哈,Google怎会如此lowB,开发者都知道在打包App时,都会有自己的签名文件,而且该签名文件可是机密哟。不同签名的App可是不能实现数据共享的。
在远程服务中使用自定义类型
前面我们提到,在远程服务中,仅支持几种类型的数据,虽然支持自定义类型,但用法有所不同。
首先我们定义一个Cat类型。据官方文档说明,要再远程服务中使用自定义类型必须Parcelable。
package cn.codemperor.summary.bean;
public class Cat implement Parcelable{
public static final Parcelable.Creator<Cat> CREATOR = new
Parcelable.Creator<Cat>() {
public Cat createFromParcel(Parcel in) {
return new Cat(in);
}
public Cat[] newArray(int size) {
return new Cat[size];
}
};
String name;
String host;
private Cat(Parcel in) {
name = in.readString();
host = in.readString();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeString(host);
}
}
复制代码
然后在Cat所在的包下创建一个和自定义类型Cat同名的aidl文件,里面的内容固定为:
// Cat.aidl
package cn.codemperor.summary.bean;
// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Cat;
复制代码
注意:Cat.aidl和Cat.java的包名必须一致,否则会查找不到。
那么,现在我们就可以使用Cat类型了
//导入Cat类,必须写
import cn.codemperor.summary.bean.Cat;
interface IDataAccess {
boolean accessData();
Cat getCat();
/**
* 如果要在参数中使用自定义类型,必须添加in修饰符
**/
void setCat(in Cat cat);
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
复制代码
最后在DataAccessBinder中重写getCat(), setCat(in Cat cat)即可。
哈哈,前方高能,前方高能,前方高能 经过测试,使用getCat()返回的Cat对象不为空,但是cat.name, cat.host为null。
其他进程间通讯的方式
Messager
该部分较为简单,点我查看用法。
一些Service保活的方法
前台Service
具体参见这里,作者写的浅显易懂。
onStartCommand的返回值
不用再说了吧,哈哈哈哈哈哈
一些小知识点
android:process的值
(1). android:process="com.xxx.xxxx.remote" 完整的命名方式,属于全局进程,其它应用通过ShareUID方式可以和它跑在同一个进程中。
(2). android:process=":remote",进程以“:”开头的进程属于当前应用的私有进程,其它应用的组件不可以和它跑在同一个进程中,“:”命名的进程会在当前的进程名之前附加上包名如:“com.xxx.xxxx:remote”。