最近项目搞完了,有点空正好查漏补缺关于服务这一块,一直都没怎么用过,趁着这个时机学习一下至于为啥起这名呢……因为本来就想着稍微看一下Service,结果发现要看的东西越来越多……越来越多……所以就有崩了的意思……本文代码比较多,请边看边敲,碰到不懂去搜一下。
首先梳理一下我想要写的东西:
什么是Service Service是一个可以在后台执行长时间运行操作而不提供用户界面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。服务并不是一个单独的进程,除非有特殊指明,否则Service是作为application的一部分运行在相同的进程中的。服务没有分离于主线程工作,所以当你要在服务中进行耗时操作最好开启一个新的线程来执行这种操作,避免ANR。
启动Service的两种方式 有两种方式启动service,一种是startService,一种是bindService,暂时从service的生命周期上来看一下这两种有何异同,先贴代码: Service:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 package com.xiasuhuei321.client;import android.app.Service;import android.content.Intent;import android.os.Binder;import android.os.IBinder;import android.support.annotation.Nullable;import android.util.Log;public class TestService extends Service { public static final String TAG = "TestService" ; @Nullable @Override public IBinder onBind (Intent intent) { Log.e(TAG, "onBind" ); return null ; } @Override public void onCreate () { Log.e(TAG, "onCreate" ); } @Override public int onStartCommand (Intent intent, int flags, int startId) { Log.e(TAG, "onStartCommand" ); return super .onStartCommand(intent, flags, startId); } @Override public void onRebind (Intent intent) { Log.e(TAG, "onRebind" ); } @Override public void onDestroy () { Log.e(TAG, "onDestroy" ); } @Override public boolean onUnbind (Intent intent) { Log.e(TAG, "onUnbind" ); return super .onUnbind(intent); } private static class MyBinder extends Binder { } }
Activity:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 package com.xiasuhuei321.client;import android.content.ComponentName;import android.content.Intent;import android.content.ServiceConnection;import android.os.IBinder;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.util.Log;import android.view.View;public class MainActivity extends AppCompatActivity implements View .OnClickListener { private Intent startIntent; private Intent bindIntent; public static final String TAG = "MainActivity" ; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.bt_bind_service).setOnClickListener(this ); findViewById(R.id.bt_start_service).setOnClickListener(this ); findViewById(R.id.bt_stop_service).setOnClickListener(this ); findViewById(R.id.bt_unbind_service).setOnClickListener(this ); } @Override public void onClick (View view) { switch (view.getId()) { case R.id.bt_bind_service: bindIntent = new Intent(this , TestService.class); bindService(bindIntent, connection, BIND_AUTO_CREATE); break ; case R.id.bt_start_service: startIntent = new Intent(this , TestService.class); startService(startIntent); break ; case R.id.bt_stop_service: if (startIntent != null ) stopService(startIntent); break ; case R.id.bt_unbind_service: if (bindIntent != null ) unbindService(connection); break ; } } ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected (ComponentName componentName, IBinder iBinder) { Log.e(TAG, "onServiceConnected" ); } @Override public void onServiceDisconnected (ComponentName componentName) { Log.e(TAG, "onServiceDisconnected" ); } }; }
xml文件就不贴了,就是四个按钮,从上到下分别是bindService、startService、unbindService、stopService两两对应,样子如下所示:
最后别忘了在清单文件中注册一下,android四大组件都需要注册的。
使用bindService方法启动时service的生命周期:
点击unbind按钮时service的生命周期:
点击startService
点击stopService
通过以上的生命周期我们会发现,通过bindService和startService启动的服务生命周期会有所不同,其实bindService还有另外的东西,不过因为我在onBind()方法里返回了null所以没有体现出来。那么我返回一个他要的试试看,修改的代码只有TestService和ServiceConnection,只放修改的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 package com.xiasuhuei321.client;import android.app.Service;import android.content.Intent;import android.os.Binder;import android.os.IBinder;import android.support.annotation.Nullable;import android.util.Log;public class TestService extends Service { public static final String TAG = "TestService" ; @Nullable @Override public IBinder onBind (Intent intent) { Log.e(TAG, "onBind" ); return new MyBinder(); } @Override public void onCreate () { Log.e(TAG, "onCreate" ); } @Override public int onStartCommand (Intent intent, int flags, int startId) { Log.e(TAG, "onStartCommand" ); return super .onStartCommand(intent, flags, startId); } @Override public void onRebind (Intent intent) { Log.e(TAG, "onRebind" ); } @Override public void onDestroy () { Log.e(TAG, "onDestroy" ); } @Override public boolean onUnbind (Intent intent) { Log.e(TAG, "onUnbind" ); return super .onUnbind(intent); } public static class MyBinder extends Binder { public void playMusic () { Log.e("MyBinder" , "play music" ); } } } ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected (ComponentName componentName, IBinder iBinder) { Log.e(TAG, "onServiceConnected" ); TestService.MyBinder mb = (TestService.MyBinder) iBinder; mb.playMusic(); } @Override public void onServiceDisconnected (ComponentName componentName) { Log.e(TAG, "onServiceDisconnected" ); } };
那么让咱点下BIND_SERVICE按钮看看有啥反应:
可以看到,通过ServiceConnection中有两个回调方法,其中一个带有IBinder类型的对象,而我强转成我自己继承自Binder的MyBinder也没有错。可以猜测这里的IBinder就是我在onBind()里返回的MyBinder,而我可以在这个类里写一个方法,再通过拿到的IBinder来调用这个方法。
小结:
onCreate方法只在首次创建服务时,系统将调用此方法来执行一次性设置程序(在调用 onStartCommand()或onBind()之前)。如果服务已在运行,则不会调用此方法。点击BIND_SERVIC和START_SERVICE(即bindService和startService都调用),再点击UNBIND_SERVICE或STOP_SERVICE实际上都不会调用onDestroy方法。因为service可以和多个客户端绑定(不过只在第一次和客户端绑定的时候调用onBind方法,随后即可将同一IBinder传递给其他绑定的客户端),除非所有的客户端均取消绑定(如果通过startService也启动了这个服务,那么还得stopService才会停止),否则stopService或stopSelf不会实际停止服务。
在阅读鸿洋大神的一篇AIDL文章时,我尝试了下他的代码,发现使用隐式Intent启动服务会报错,后来搜索发现说是Android 5.0以上只能显示启动服务了,解决方案后文和网上都有。
创建绑定服务的三种方式 创建绑定服务时必须提供IBinder,提供客户端和服务交互的“接口”。这里的接口并非java语言中的interface,可以理解为提供客户端调用的方法之类的。android提供三种方式:
扩展(继承)Binder类
使用Messenger
使用AIDL
第一种扩展Binder类在Android API指南中描述如下:如果服务是供你的自由应用专用,并且在客户端相同的进程中运行(常见情况),则应通过扩展Binder类并从onBind()返回它的一个实例来创建接口。客户端收到Binder后,可利用它直接访问Binder实现中乃至Service中可用的公共方法。
如果服务只是您的自有应用的后台工作线程,则优先采用这种方法。不以这种方式创建接口的唯一原因是,您的服务被其他应用或不同的进程占用。
也就是说如果不是为了跨进程而用其他两种方式你就是在耍流氓~
AIDL 在正式了解AIDL之前,我觉得有必要先弄明白线程和进程的概念。首先看一下他们正式的定义:
从我们平时的开发中可以这么阐述进程与线程:进程是程序的一次运行,线程是其中一段代码的执行。而我们平时写的Android程序一般都是在一个进程中运行的,进程名即包名。Android系统内核是Linux内核,而每一个应用程序都运行在一个虚拟机上,每个应用程序都是一个进程。在Android中一个应用程序也是可以使用多进程的,其方式非常简单,只要在清单文件中指定对应的组件属性:
1 android:process=":remote"
这样就可以了,但是本来一个问题你可以用多进程来解决,然后你用了,之后你就会有两个问题了。说笑,多进程会带来的问题就是内存不共享,因为每个进程都独享自己的一块内存,没办法直接互相访问内存。一个进程中已经存在对象另外一个进程中不能直接使用,不过好在Android提供了很多方式给我们“跨进程”,AIDL便是我们首先要探究的一种方式。
AIDL小例子 我看了几篇文的例子,《Android开发艺术探索》、鸿洋大神和网上其他人写的,感觉还是先用鸿洋大神的例子好一点,鸿洋大神的例子是简单的计算两个整型数值的相加和相减的结果。我们首先新建一个AIDL文件:
AIDL文件代码如下:
1 2 3 4 5 6 7 8 9 package com.xiasuhuei321.forjianshu;interface CalcManage { int plus (int a,int b) ; int min (int a,int b) ; }
然后点击Android Studio的make project
完成之后会在项目的app/build/generated/source/aidl/debug下生成一个java文件,这里和鸿洋大神一样,具体的解释放到之后再说,我们先走完这个流程。
接着新建一个CalcService的Java类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package com.xiasuhuei321.forjianshu;import android.app.Service;import android.content.Intent;import android.os.IBinder;import android.os.RemoteException;import android.support.annotation.Nullable;public class CalcService extends Service { @Nullable @Override public IBinder onBind (Intent intent) { return binder; } CalcManage.Stub binder = new CalcManage.Stub() { @Override public int plus (int a, int b) throws RemoteException { return a + b; } @Override public int min (int a, int b) throws RemoteException { return a - b; } }; }
可以看到我这里只是返回了一个binder,不过这个binder是我们通过AIDL生成的java文件中的一个类,恩,下文再解释。
接着是主界面的布局,和MainActivity的代码,恩因为能直接用鸿洋大神的。。直接copy过来的。。。各位不要学我。。。
布局:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <?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:id ="@+id/activity_main" android:layout_width ="match_parent" android:layout_height ="match_parent" android:orientation ="vertical" tools:context ="com.xiasuhuei321.forjianshu.MainActivity" > <Button android:layout_width ="fill_parent" android:layout_height ="wrap_content" android:onClick ="bindService" android:text ="BindService" /> <Button android:layout_width ="fill_parent" android:layout_height ="wrap_content" android:onClick ="unbindService" android:text ="UnbindService" /> <Button android:layout_width ="fill_parent" android:layout_height ="wrap_content" android:onClick ="addInvoked" android:text ="12+12" /> <Button android:layout_width ="fill_parent" android:layout_height ="wrap_content" android:onClick ="minInvoked" android:text ="50-12" /> </LinearLayout >
MainActivity:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 package com.xiasuhuei321.forjianshu;import android.content.ComponentName;import android.content.Context;import android.content.Intent;import android.content.ServiceConnection;import android.content.pm.PackageManager;import android.content.pm.ResolveInfo;import android.os.IBinder;import android.os.RemoteException;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.util.Log;import android.view.View;import android.widget.Toast;import java.util.List;public class MainActivity extends AppCompatActivity { private CalcManage mCalcAidl; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); } private ServiceConnection mServiceConn = new ServiceConnection() { @Override public void onServiceConnected (ComponentName name, IBinder service) { Log.e("client" , "onServiceConnected" ); mCalcAidl = CalcManage.Stub.asInterface(service); } @Override public void onServiceDisconnected (ComponentName name) { Log.e("client" , "onServiceDisconnected" ); mCalcAidl = null ; } }; public void bindService (View v) { Intent intent = new Intent(); intent.setAction("com.xiasuhuei321.forjianshu.calc" ); final Intent i = new Intent(createExplicitFromImplicitIntent(this ,intent)); bindService(i, mServiceConn, Context.BIND_AUTO_CREATE); } public void unbindService (View v) { unbindService(mServiceConn); } public void addInvoked (View v) throws Exception { if (mCalcAidl != null ) { int addRes = mCalcAidl.plus(12 , 12 ); Toast.makeText(this , addRes + "" , Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this , "服务器被异常杀死,请重新绑定服务端" , Toast.LENGTH_SHORT).show(); } } public void minInvoked (View v) throws RemoteException { if (mCalcAidl != null ) { int addRes = mCalcAidl.min(50 , 12 ); Toast.makeText(this , addRes + "" , Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this , "服务端未绑定或被异常杀死,请重新绑定服务端" , Toast.LENGTH_SHORT).show(); } } public static Intent createExplicitFromImplicitIntent (Context context, Intent implicitIntent) { PackageManager pm = context.getPackageManager(); List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0 ); if (resolveInfo == null || resolveInfo.size() != 1 ) { return null ; } ResolveInfo serviceInfo = resolveInfo.get(0 ); String packageName = serviceInfo.serviceInfo.packageName; String className = serviceInfo.serviceInfo.name; ComponentName component = new ComponentName(packageName, className); Intent explicitIntent = new Intent(implicitIntent); explicitIntent.setComponent(component); return explicitIntent; } }
最后不要忘了在清单文件中注册这个service:
1 2 3 4 5 6 7 <service android:name =".CalcService" > <intent-filter > <action android:name ="com.xiasuhuei321.forjianshu.calc" /> <category android:name ="android.intent.category.DEFAULT" /> </intent-filter > </service >
最后来看一下结果如何:
一些值得注意的地方已经在代码中注释了,看结果很明显是对的,但是我这里还没有体现出来AIDL跨进程跨在哪了,接下来就给这个service指定一个进程,看一下还能跑不。
修改一下清单文件中service的属性:
1 2 3 4 5 6 7 8 9 <service android:name =".CalcService" android:process =":calc" > <intent-filter > <action android:name ="com.xiasuhuei321.forjianshu.calc" /> <category android:name ="android.intent.category.DEFAULT" /> </intent-filter > </service >
启动之后点击第一个按钮绑定服务,再查看果然多了:calc进程。
还能跑不?
结果是一样的,正常运行,这说明了AIDL的确能实现“跨进程”。这种方式也可以用于不同app间的通信,只不过可能会麻烦一点。接下来就新建一个client module:
现在我们新建的这个module将之称为client,而之前的项目将之称为server。现在我们将在server通过AIDL文件生成的java文件复制过来
当然了,因为包更改了,肯定一堆问题,改过来就好了,将该文件内的包名全部修改对了就可以了。然后,恩,把之前的布局和能用到的都复制过来……布局不怎么需要改,MainActivity的代码也先不该,复制过来跑起来看看咋样。
结果肯定是闪退了~别问我咋知道,如果你报的错是以下这种:
那说明CalcMange代码里的DESCRIPTOR错了,毕竟直接拷贝过来的。。。各种错不稀奇。。
1 private static final String DESCRIPTOR = "com.xiasuhuei321.client.CalcManage" ;
因为刚全局修改了包名,所以这里也改了,但是这不能改,不然就会抛这个异常。
1 private static final String DESCRIPTOR = "com.xiasuhuei321.forjianshu.CalcManage" ;
好了,改成这样再来一遍,看一下结果:
分析 正确的得出了结果,恩,试也试完了,接下来研究一下AIDL为啥能做到跨进程。前面一直拖着说道后面解释的玩意,现在再来看看,系统为我们生成的java文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 package com.xiasuhuei321.forjianshu;public interface CalcManage extends android .os .IInterface { public static abstract class Stub extends android .os .Binder implements com .xiasuhuei321 .forjianshu .CalcManage { private static final java.lang.String DESCRIPTOR = "com.xiasuhuei321.forjianshu.CalcManage" ; public Stub () { this .attachInterface(this , DESCRIPTOR); } public static com.xiasuhuei321.forjianshu.CalcManage asInterface (android.os.IBinder obj) { if ((obj == null )) { return null ; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null ) && (iin instanceof com.xiasuhuei321.forjianshu.CalcManage))) { return ((com.xiasuhuei321.forjianshu.CalcManage) iin); } return new com.xiasuhuei321.forjianshu.CalcManage.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_plus: { data.enforceInterface(DESCRIPTOR); int _arg0; _arg0 = data.readInt(); int _arg1; _arg1 = data.readInt(); int _result = this .plus(_arg0, _arg1); reply.writeNoException(); reply.writeInt(_result); return true ; } case TRANSACTION_min: { data.enforceInterface(DESCRIPTOR); int _arg0; _arg0 = data.readInt(); int _arg1; _arg1 = data.readInt(); int _result = this .min(_arg0, _arg1); reply.writeNoException(); reply.writeInt(_result); return true ; } } return super .onTransact(code, data, reply, flags); } private static class Proxy implements com .xiasuhuei321 .forjianshu .CalcManage { 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 int plus (int a, int b) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); int _result; try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeInt(a); _data.writeInt(b); mRemote.transact(Stub.TRANSACTION_plus, _data, _reply, 0 ); _reply.readException(); _result = _reply.readInt(); } finally { _reply.recycle(); _data.recycle(); } return _result; } @Override public int min (int a, int b) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); int _result; try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeInt(a); _data.writeInt(b); mRemote.transact(Stub.TRANSACTION_min, _data, _reply, 0 ); _reply.readException(); _result = _reply.readInt(); } finally { _reply.recycle(); _data.recycle(); } return _result; } } static final int TRANSACTION_plus = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0 ); static final int TRANSACTION_min = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1 ); } public int plus (int a, int b) throws android.os.RemoteException ; public int min (int a, int b) throws android.os.RemoteException ; }
写到这,我觉得有必要再重新审视一下Binder了,因为通过以上的代码和注释,你一定发现老提到Binder。Binder是Android中的一个类,它实现了IBinder接口。从IPC(Inter-Process Communication,进程间通信)角度来说,Binder是Android中的一种跨进程通信方式。从Android应用层来说,Binder是客户端和服务端调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务端包括普通服务和基于AIDL的服务。
本来还想分个服务端,客户端继续吹吹牛,但是写着写着觉得我注释够详细了。。。不吹了不吹了,各位自己看上面我带注释的代码吧。
写到这感觉篇幅有点小长了,关于Binder、Messenger和IPC还是留待下一篇吧~
参考资料