Android Service浅析(下)
来源:互联网 发布:涂子沛 数据之巅 编辑:程序博客网 时间:2024/05/16 08:52
在我的上一篇文章Android Service浅析(上)介绍了服务的基本概念及启动服务的相关内容,由于文章篇幅的原因,将在本文继续梳理Service相关的其它知识。
绑定Local Service并与之通信
在上一篇介绍了启动服务startSerivce()的方式,虽然启动后的服务不再依赖于启动它的组件(如Activity),甚至在该活动销毁的情况下,服务继续保持长时间的运行,直到你通过stopService()或者stopSelf()主动终止它,但是实际应用中有时候希望与服务保持更加密切的关系,比如在上一篇中利用广播来实现活动和服务之间消息传递;除了这种间接方式,服务还提供一个更加直接的方式,就是绑定服务bindService(),关于绑定服务的定义如下:
当应用组件通过调用 bindService() 绑定到服务时,服务即处于“绑定”状态。绑定服务提供了一个客户端-服务器接口IBinder,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。绑定服务通常只在为其他应用组件服务时处于活动状态,不会无限期在后台运行。*
通俗的讲就是通过bindService()的方式,其他组件(包括跨进程组件)可以指挥Service去干活。
那么它是怎么实现的呢?
你必须实现 onBind() 回调方法。该方法返回的 IBinder 对象定义了客户端用来与服务进行交互的编程接口。
客户端通过调用 bindService(Intent intent,ServiceConnnection conn,int flags) 绑定到服务时,它必须提供 ServiceConnection 的实现,后者会监控与服务的连接。当客户端与服务之间的连接时,会调用 ServiceConnection 上的 onServiceConnected(),该方法参数会接受一个IBinder类型的对象,就是服务端onBind()方法返回的。
IBinder对象相当于Service组件的内部钩子,该钩子关联到绑定的Service组件,当其它组件绑定到该Service组件时,Service将会把IBinder对象返回给其它组件,其它组件通过该IBinder对象即可与Service组件进行实时通讯。
代码示例如下:
MyService:
package com.example.myapplication;import android.app.Service;import android.content.Intent;import android.os.Binder;import android.os.IBinder;import android.util.Log;public class MyService extends Service { public static final String TAG = "MyService"; //onBind()方法所返回的对象 private IBinder mBinder = new MyBinder(); /* * 实现IBinder接口,通常是Binder接口,它是IBinder的实现类,可以先把这看作规定,至于为什么,需要进一步探索。 * 另外这个类定义在MyService内部,也就是所谓的成员内部类,这是因为内部类对象天然持有外部类的this引用, * 所以可以利用这个特性,访问MyService的公共方法,甚至是返回外部类对象,内部类在开发中有很多的使用场景。 */ class MyBinder extends Binder{ public MyService getService(){ return MyService.this; } } //在MyService中定义一个公共的方法,这里为一个下载任务 public void startDownload(){ new Thread(new Runnable() { @Override public void run() { //添加两行打印语句 Log.d(TAG,"The thread id is " + Thread.currentThread().getId()); Log.d(TAG,"Start download..."); } }).start(); } @Override public void onCreate() { super.onCreate(); Log.d(TAG,"onCreate() executed"); } //必须实现的方法,绑定该Service时,回调该方法 @Override public IBinder onBind(Intent intent) { Log.d(TAG,"onBind() executed"); //返回一个IBinder接口类型的实例 return mBinder; } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(TAG,"onStartCommand() executed"); return super.onStartCommand(intent,flags,startId); } @Override public void onDestroy() { super.onDestroy(); Log.d(TAG,"onDestroy() executed"); }}
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/bind_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Bind MyService"/> <Button android:id="@+id/unbind_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Unbind MyService"/></LinearLayout>
MainActivity:
package com.example.myapplication;import android.app.Activity;import android.content.ComponentName;import android.content.Intent;import android.content.ServiceConnection;import android.os.Bundle;import android.os.IBinder;import android.util.Log;import android.view.View;import android.widget.Button;public class MainActivity extends Activity { private static final String TAG = "MainActivity"; private Button mBindService; private Button mUnbindService; private MyService mService; private MyService.MyBinder mBinder; //该标志为用来判断是否解除绑定 private boolean mBound; private ServiceConnection mConnection = new ServiceConnection() { //当该活动与服务连接成功时回调该方法,并且接收服务onBind()方法返回的IBinder类型的实例。 @Override public void onServiceConnected(ComponentName name, IBinder service) { //通过IBinder实例向下转型,获取到MyBinder实例,它属于MyService内部类对象,所以具备了操作MyService公共方法和成员变量的功能。 mBinder = (MyService.MyBinder) service; //返回MyService对象的实例 mService = mBinder.getService(); //调用MyService对象的公共方法 mService.startDownload(); mBound = true; } //该方法在系统异常导致绑定断开时才会执行,而主动通过unBind()并不会调用 @Override public void onServiceDisconnected(ComponentName name) { mBound = false; } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG,"The thread id is " + Thread.currentThread().getId()); mBindService = (Button) findViewById(R.id.bind_service); mUnbindService = (Button) findViewById(R.id.unbind_service); mBindService.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, MyService.class); /* * 第二个参数为一个ServiceConnection接口类型的实例; * 第三个参数为常量,它表示绑定时,在Service还未创建时,是否自动创建,一般都是用Context.BIND_AUTO_CREATE:自动创建。 */ bindService(intent, mConnection, BIND_AUTO_CREATE); } }); mUnbindService.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //在该活动未与服务绑定情况下,执行unbindService()会出现异常。 if (mBound) { unbindService(mConnection); mBound = false; } } }); } @Override protected void onDestroy() { if (mBound) { unbindService(mConnection); mBound = false; } super.onDestroy(); }}
运行程序,点击Bind Service按钮,Logcat打印日志如下:
你可以试着再次点击Bind Service按钮,你会发现Logcat中的内容并没有变化,这与startService()不同,它会多次执行onStartCommand()方法,但多次调用bindService()不会进行重复绑定。
再次点击Unbind Service按钮或者关闭程序,Logcat打印日志如下:
即使把MainActivity中onDestroy()调用unbindService()的代码注销掉,如下所示:
@Override protected void onDestroy() { /* if (mBound) { unbindService(mConnection); mBound = false; }*/ super.onDestroy(); }
重新运行程序,点击Bind Service按钮,直接通过back退出应用,Logcat的打印日志如下:
也就是说,不管是否主动解除绑定,当服务所绑定的组件被销毁时,该服务也自动终止。
不过一种情况除外,即既执行了startService(),又执行了bindService()(不论先后),此时要终止服务,必须要既执行onStop()又执行onUnbind()(不论先后),否则,服务不会终止。
代码修改:在activity_main中添加两个按钮分别用来启动、停止服务,代码示例如下:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/start_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Start MyService"/> <Button android:id="@+id/stop_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Stop MyService"/> <Button android:id="@+id/bind_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Bind MyService"/> <Button android:id="@+id/unbind_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Unbind MyService"/></LinearLayout>
在MainActivity中分别给新增按钮添加注册事件,分别用来启动、停止服务,代码示例如下:
package com.example.myapplication;import android.app.Activity;import android.content.ComponentName;import android.content.Intent;import android.content.ServiceConnection;import android.os.Bundle;import android.os.IBinder;import android.util.Log;import android.view.View;import android.widget.Button;public class MainActivity extends Activity { private static final String TAG = "MainActivity"; private Button mStartService; private Button mStoptService; private Button mBindService; private Button mUnbindService; private MyService mService; private MyService.MyBinder mBinder; //该标志为用来判断是否解除绑定 private boolean mBound; private ServiceConnection mConnection = new ServiceConnection() { //当该活动与服务连接成功时回调该方法,并且接收服务onBind()方法返回的IBinder类型的实例。 @Override public void onServiceConnected(ComponentName name, IBinder service) { //通过IBinder实例向下转型,获取到MyBinder实例,它属于MyService内部类对象,所以具备了操作MyService公共方法和成员变量的功能。 mBinder = (MyService.MyBinder) service; //返回MyService对象的应用 mService = mBinder.getService(); mService.startDownload(); mBound = true; } //该方法会在系统异常导致绑定断开时才会执行,而主动通过unBind()并不会调用 @Override public void onServiceDisconnected(ComponentName name) { mBound = false; } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, "The thread id is " + Thread.currentThread().getId()); mStartService = (Button) findViewById(R.id.start_service); mStoptService = (Button) findViewById(R.id.stop_service); mBindService = (Button) findViewById(R.id.bind_service); mUnbindService = (Button) findViewById(R.id.unbind_service); mStartService.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, MyService.class); startService(intent); } }); mStoptService.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, MyService.class); stopService(intent); } }); mBindService.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, MyService.class); /* * 第二个参数为一个ServiceConnection接口类型的实例; * 第三个参数为常量,它表示绑定时,在Service还未创建时,是否自动创建,一般都是用Context.BIND_AUTO_CREATE:自动创建。 */ bindService(intent, mConnection, BIND_AUTO_CREATE); } }); mUnbindService.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //在该活动未与服务绑定情况下,执行unbindService()会出现异常。 if (mBound) { unbindService(mConnection); mBound = false; } } }); } @Override protected void onDestroy() { /* if (mBound) { unbindService(mConnection); mBound = false; }*/ super.onDestroy(); }}
根据上面的结论来随机操作,可以验证该结论。
那么为什么会有这样的现场呢?
可以想象到这样的应用场景:用户通过音乐播放器播放一个音乐,此时应用会在后台启动一个服务(startService),然后用户由于要看微信而退出应用,由于音乐播放是一个服务,所以它并不会停止,过一会你听到不喜欢的歌,你需要进入应用进行切歌,此时会通过bindService()来绑定到后台的服务,并进行播放控制,然后又退出应用(执行unBindService),那么现在音乐是否需要停止播放呢?实际并不会,所以这个特性很好解决了这种应用场景。
所以启动服务和绑定服务根据应用场景来设计的功能,它们不是对立的,根据功能的需要决定使用哪种方式来创建服务,或者两者方式都会使用到,这是服务就必须要提供两者的实现。
管理绑定服务的生命周期
您通常应该在客户端生命周期的匹配引入 (bring-up) 和退出 (tear-down) 时刻期间配对绑定和取消绑定。 例如:
- 如果您只需要在 Activity 可见时与服务交互,则应在 onStart() 期间绑定,在 onStop() 期间取消绑定。
- 如果您希望 Activity 在后台停止运行状态下仍可接收响应,则可在 onCreate() 期间绑定,在 onDestroy() 期间取消绑定。请注意,这意味着您的 Activity 在其整个运行过程中(甚至包括后台运行期间)都需要使用服务,因此如果服务位于其他进程内,那么当您提高该进程的权重时,系统终止该进程的可能性会增加
通常情况下,切勿在 Activity 的 onResume() 和 onPause() 期间绑定和取消绑定,因为每一次生命周期转换都会发生这些回调,您应该使发生在这些转换期间的处理保持在最低水平。此外,如果您的应用内的多个 Activity 绑定到同一服务,并且其中两个 Activity 之间发生了转换,则如果当前 Activity 在下一次绑定(恢复期间)之前取消绑定(暂停期间),系统可能会销毁服务并重建服务。 (Activity文档中介绍了这种有关 Activity 如何协调其生命周期的 Activity 转换。)
如果您的服务已启动并接受绑定,则当系统调用您的 onUnbind() 方法时,如果您想在客户端下一次绑定到服务时接收 onRebind() 调用(而不是接收 onBind() 调用),则可选择返回 true。onRebind() 返回空值,但客户端仍在其 onServiceConnected() 回调中接收 IBinder。下文图 1 说明了这种生命周期的逻辑。
图 1. 允许绑定的已启动服务的生命周期。
你可以在MyService中添加onUnbind()和onRebind()方法,让onUnbind()返回true,代码如下:
@Override public boolean onUnbind(Intent intent) { Log.d(TAG,"onUnbind() executed"); return true; } @Override public void onRebind(Intent intent) { Log.d(TAG,"onRebind() executed"); super.onRebind(intent); }
启动程序,分别点击Start MyService、Bind MyService、Unbind MyService、Bind MyService按钮,Logcat打印日志如下:
最后点击Bind MyService按钮并没有回调onBind(),而是onReBind(),但是也成功返回IBinder给客户端了。至于这有什么意义,暂时还不了解。
如果您的服务仅供本地应用使用,不需要跨进程工作,则可以实现自有 Binder 类,让您的客户端通过该类直接访问服务中的公共方法。之所以要求服务和客户端必须在同一应用内,是为了便于客户端转换返回的对象和正确调用其 API。服务和客户端还必须在同一进程内,因为此方法不执行任何跨进程编组。
那么怎么实现跨进程服务的通信呢,绑定本地服务的方式能不能适用到远程服务,答案是否定的。下面介绍绑定服务实现不同进程间通信的方式。
绑定Remote Service并与之通信
在绑定服务的定义中,提到Service可以实现跨进程的通信,所谓进程通信(IPC),狭义的来说是向其它进程传递一个数据,比如Intent可以实现;广义的来说就是获取其它进程中的对象,并可以调用对象中的方法,而一般IPC都是指广义上的概念,这属于个人的理解,不然Intent和广播也属于IPC的一种。上文中绑定方式只是实现了同一个应用相同进程下其他组件与本地服务的通信,并不属于IPC,要实现跨进程服务通信,必须使用AIDL(Android Interface Definition Language)翻译为中文就是Android接口定义语句,它可以让服务与不同进程下的组件进行通信,实现多个应用程序共享同一个Service的功能。
我们要是实现不同的进程间的通信,不一定需要建立两个应用,同一个应用也可以有多个进程,设置非常简单,在AndroidManifest.xml中将组件android:process属性指定为一个不同于应用包名的字符串。而将一个普通的Service转换成远程Service其实非常简单,只需要在注册Service的时候将它的android:process属性指定成:remote就可以了,代码如下所示:
<service android:name=".MyService" android:process=":remote"></service>
为了证实MyService现在确实已经运行在另外一个进程当中了,我们分别在MainActivity的onCreate()方法和MyService的onCreate()方法里加入一行日志,打印出各自所在的进程id,如下所示:
Log.d("TAG", "process id is " + Process.myPid());
再次重新运行程序,然后点击一下Bind Service按钮,打印结果如下图所示:
可以看到,不仅仅是进程id不同了,就连应用程序包名也不一样了,MyService中打印的那条日志,包名后面还跟上了:remote标识。
下面我们使用Android提供AIDL机制来实现进程的通信:
使用AIDL的步骤:
1. 创建.aidl文件。
在Android Studio中光标放到对应项目中任何一处,右击-【New】-【AIDL】-【AIDL FILE】,如下图:
打开“AIDL FILE”需要输入Interface Name,输入“MyAidlService”,如下图:
打开AIDL文件,用Java语法编写一个接口,代码如下:
package com.example.myapplication;interface MyAidlService { int add(int a,int b);}
定义了一个MyAidlService接口,该名称名称由系统生成,与文件名一致,在里面声明了一个add方法。
编写完成后,在Android Studio中需要主动编译一下文件,而Eclipse保存后会自动编译,可以点击工具栏中“Sync Projects with Gradle Files”,如下图:
(不同AS版本,图标可能一样),编译完成以后,SDK会自动生成一个MyAidlService.java文件,地址在项目下build/generated/source/debug/package_name(你的应用包名)下,如下图:
2、实现接口
在生成的接口文件MyAidlService.java中,有一个抽象的内部类Stub(存根),它不仅实现了IBinder接口,而实现了外部类MyAidlService接口,所以它也继承了外部类的方法声明。
通过匿名类的方式,继承MyAidlService.Stub类并实现接口中方法,并且返回一个IBinder实例。
代码如下:
private IBinder mBinder = new MyAidlService.Stub() { @Override public int add(int a, int b) throws RemoteException { return a + b; } };
3、把接口暴露给客户端
通过onBind()向客户端返回IBinder实例,回调ServiceConnction类中onServiceConnected(),该方法接收该实例。另外,在生成的MyAidlService.java文件中,MyAidlService.Stub会提供一个静态方法MyAidlService asInterface(IBinder obj),它可以接收IBinder实例并返回一个MyAidlService类型的实例,通过该方法把IBinder实例转型为MyAidlService类型,这样就可以通过该实例来调用服务端的方法,代码如下:
MyService
@Override public IBinder onBind(Intent intent) { return mBinder; }
MainActivity
package com.example.myapplication;import android.app.Activity;import android.content.ComponentName;import android.content.Intent;import android.content.ServiceConnection;import android.os.Bundle;import android.os.IBinder;import android.os.Process;import android.os.RemoteException;import android.util.Log;import android.view.View;import android.widget.Button;import android.widget.Toast;import static com.example.myapplication.R.id.bind_service;import static com.example.myapplication.R.id.invoke_remote_service;import static com.example.myapplication.R.id.start_service;import static com.example.myapplication.R.id.stop_service;import static com.example.myapplication.R.id.unbind_service;public class MainActivity extends Activity implements View.OnClickListener { private Button mStartService; private Button mStoptService; private Button mBindService; private Button mUnbindService; //返回的AIDL接口类型的对象 private MyAidlService mService; //该标志为用来判断是否解除绑定 private boolean mBound; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { //通过MyAidlService.Stub静态方法asInterface()把IBinder类型转换为MyAidlService类型 mService = MyAidlService.Stub.asInterface(service); mBound = true; } @Override public void onServiceDisconnected(ComponentName name) { mService = null; mBound = false; } }; private Button mInvokeRS; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d("TAG", "process id is " + Process.myPid()); initView(); } private void initView() { mStartService = (Button) findViewById(R.id.start_service); mStoptService = (Button) findViewById(R.id.stop_service); mBindService = (Button) findViewById(R.id.bind_service); mUnbindService = (Button) findViewById(R.id.unbind_service); mInvokeRS = (Button) findViewById(invoke_remote_service); mStartService.setOnClickListener(this); mStoptService.setOnClickListener(this); mBindService.setOnClickListener(this); mUnbindService.setOnClickListener(this); mInvokeRS.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case start_service: Intent startIntent = new Intent(MainActivity.this, MyService.class); startService(startIntent); break; case stop_service: Intent stopIntent = new Intent(MainActivity.this, MyService.class); stopService(stopIntent); break; case bind_service: Intent bindIntent = new Intent(MainActivity.this, MyService.class); bindService(bindIntent, mConnection, BIND_AUTO_CREATE); break; case unbind_service: if (mBound) { unbindService(mConnection); mBound = false; } break; case invoke_remote_service: if(mService!=null){ try { //调用AIDL接口中声明的方法 int mSum = mService.add(1, 2); Toast.makeText(this, "a + b =" + mSum, Toast.LENGTH_SHORT).show(); } catch (RemoteException e) { e.printStackTrace(); } }else{ Toast.makeText(this, "请先绑定远程服务", Toast.LENGTH_SHORT).show(); } break; default: break; } } @Override protected void onDestroy() { if (mBound) { unbindService(mConnection); mBound = false; } super.onDestroy(); }}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/start_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Start MyService"/> <Button android:id="@+id/stop_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Stop MyService"/> <Button android:id="@+id/bind_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Bind MyService"/> <Button android:id="@+id/unbind_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Unbind MyService"/> <Button android:id="@+id/invoke_remote_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="调用远程服务方法"/></LinearLayout>
运行程序,分别点击“Bind MyService”和“调用远程服务方法”,结果如下图:
关于不同应用中ADIL实现方式与同一应用下不同进程的操作步骤及部分内容有所差异,AIDL更多信息暂时不做描述,可以参照:
慕客网:AIDL-小白成长记
任玉刚:android跨进程通信(IPC):使用AIDL
本文主要参考内容:
李刚:疯狂Android讲义-10.1.3 绑定本地Service并与之通信
Google:API Guide-Android Interface Definition Language (AIDL)
郭霖: Android Service完全解析,关于服务你所需知道的一切(上)
- Android Service浅析(下)
- Android Service浅析(上)
- Android Service 浅析
- Android中的Service浅析
- Android Service 浅析(生命周期,启动方式,前台Service)
- Android中远程Service浅析
- android Service显示Notification浅析
- Android 中service的浅析
- Android Service 完全解析(下)
- Android Service完全解析(下)
- android service完全解析(下)
- Android Service学习总结(下)
- Android Service服务详细解析(下)
- Android学习之Service(下)
- Android Service完全解析(下)
- Android 四大组件之 Service (下)
- android service 学习(下)
- Android下Service入门
- 网站建设流程_网站建设流程介绍
- Picasso粗糙分析
- CSDN-markdown编辑器 使用方法
- [leetcode]Implement strStr()
- 工厂方法模式
- Android Service浅析(下)
- DeepID人脸识别算法之三代
- Joysticks
- Web测试关注点的攻与防--SQL注入
- Cannot truncate non-managed table t306
- 《从JAVA到Android》JAVA篇之流程控制
- 注册窗口监听器
- ES6学习9(Iterator&for...of)
- 小白的开始