内容简介:本文主要介绍Service相关的使用,以及使用Service实现IPC通信根据官方的介绍:进程存在的目的有2个:
本文主要介绍Service相关的使用,以及使用Service实现IPC通信
What is a Service
根据官方的介绍:
- Service既不是一个线程,Service通常运行在当成宿主进程的主线程中,所以在Service中进行一些耗时操作就需要在Service内部开启线程去操作,否则会引发ANR异常。
- 也不是一个单独的进程。除非在清单文件中声明时指定进程名,否则Service所在进程就是application所在进程。
进程存在的目的有2个:
- 告诉系统,当前程序需要在后台做一些处理。这意味着,Service可以不需要UI就在后台运行,不用管开启它的页面是否被销毁,只要进程还在就可以在后台运行。可以通过startService()方式调用,这里需要注意,除非Service手动调用stopService()或者Service内部主动调用了stopSelf(),否则Service一直运行。
- 程序通过Service对外开放某些操作。通过bindService()方式与Service调用,长期连接和交互,Service生命周期和其绑定的组件相关。
Service Lifecycle
public class MyService extends Service { @Override public void onCreate() { super.onCreate(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { return startCommandReturnId; } @Override public void onDestroy() { super.onDestroy(); } @Nullable @Override public IBinder onBind(Intent intent) { return null; } } 复制代码
要解释这个首先要知道Service的实现,需要实现抽象方法onBind,以及重写onStartCommand,这2个方法会在下文介绍到。
通过上面的介绍可以知道,Service有3种启动方式:
- startService()
- bindService()
- 同时调用
这几种方式启动的Service生命周期略微不同。
startService方式
startService()只要一个Intent参数,指定要开启的Service即可
Intent intent = new Intent(MainActivity.this, MyService.class); 复制代码
-
当调用Service的startService()后,
- Service首次启动,则先调用onCreate(),在调用onStartCommand()
- Service已经启动,则直接调用onStartCommand()
-
当调用stopSelf()或者stopService()后,会执行onDestroy(),代表Service生命周期结束。
-
startService方式启动Service不会调用到onBind()。 startService可以多次调用,每次调用都会执行onStartCommand()。 不管调用多少次startService,只需要调用一次stopService就结束。 如果startService后没有调用stopSelf或者stopService,则Service一直存活并运行在后台。
-
onStartCommand的返回值一共有3种
- START_STICKY = 1:service所在进程被kill之后,系统会保留service状态为开始状态。系统尝试重启service,当服务被再次启动,传递过来的intent可能为null,需要注意。
- START_NOT_STICKY = 2:service所在进程被kill之后,系统不再重启服务
- START_REDELIVER_INTENT = 3:系统自动重启service,并传递之前的intent
默认返回START_STICKY;
bindService方式
通过bindService绑定Service相对startService方式要复杂一点。 由于bindService是异步执行的,所以需要额外构建一个ServiceConnection对象用与接收bindService的状态,同时还要指定bindService的类型。
//1. 定义用于通信的对象,在Service的onBind()中返回的对象。 public class MyBind extends Binder { public int mProcessId; } //2. 定义用于接收状体的ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { //和服务绑定成功后,服务会回调该方法 //服务异常中断后重启,也会重新调用改方法 MyService.MyBind myBinder = (MyService.MyBind) service; } @Override public void onNullBinding(ComponentName name) { //Service的onBind()返回null时将会调用这个方法,并不会调用onServiceConnected() } @Override public void onServiceDisconnected(ComponentName name) { // 当服务异常终止时会调用。 // 注意,unbindService时不会调用 } }; //3. 在需要的地方绑定到Service bindService(intent, mServiceConnection, BIND_AUTO_CREATE); 复制代码
bindService()也可以调用多次,与startService()不同,当发起对象与Service已经成功绑定后,不会多次返回ServiceConnection中的回调方法。
通过bindService方式与Service进行绑定后,当没有对象与Service绑定后,Service生命周期结束,这个过程包括绑定对象被销毁,或者主动掉调用unbindService()
startService和bindService同时开启
当同时调用startService和bindService后,需要分别调用stopService和unbindService,Service才会走onDestroy()
一个Service必须要在既没有和任何Activity关联又处理停止状态的时候才会被销毁。
代码实操---与远端进程的Service绑定
上面的代码都是在当前进程内跟Service通信,现在我们来实现一下,不同进程内Service如何绑定。
主要步骤是这样的
- 编写aidl文件,AS自动生成的 java 类实现IPC通信的代理
- 继承自己的aidl类,实现里面的方法
- 在onBind()中返回我们的实现类,暴露给外界
- 需要跟Service通信的对象通过bindService与Service绑定,并在ServiceConnection接收数据。
我们通过代码来实现一下:
-
首先我们需要新建一个Service
public class MyRemoteService extends Service { @Nullable @Override public IBinder onBind(Intent intent) { Log.e("MyRemoteService", "MyRemoteService thread id = " + Thread.currentThread().getId()); return null; } } 复制代码
-
在manifest文件中声明我们的Service同时指定运行的进程名, 这里并是不只能写remote进程名,你想要进程名都可以
<service android:name=".service.MyRemoteService" android:process=":remote" /> 复制代码
-
新建一个aidl文件用户进程间传递数据。
AIDL支持的类型:八大基本数据类型、String类型、CharSequence、List、Map、自定义类型。List、Map、自定义类型放到下文讲解。
里面会有一个默认的实现方法,删除即可,这里我们新建的文件如下:
package xxxx;//aidl所在的包名 //interface之前不能有修饰符 interface IProcessInfo { //你想要的通信用的方法都可以在这里添加 int getProcessId(); } 复制代码
-
实现我们的aidl类
public class IProcessInfoImpl extends IProcessInfo.Stub { @Override public int getProcessId() throws RemoteException { return android.os.Process.myPid(); } } 复制代码
-
在Service的onBind()中返回
public class MyRemoteService extends Service { IProcessInfoImpl mProcessInfo = new IProcessInfoImpl(); @Nullable @Override public IBinder onBind(Intent intent) { Log.e("MyRemoteService", "MyRemoteService thread id = " + Thread.currentThread().getId()); return mProcessInfo; } } 复制代码
-
绑定Service
mTvRemoteBind.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, MyRemoteService.class); bindService(intent, mRemoteServiceConnection, BIND_AUTO_CREATE); } }); mRemoteServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.e("MainActivity", "MyRemoteService onServiceConnected"); // 通过aidl取出数据 IProcessInfo processInfo = IProcessInfo.Stub.asInterface(service); try { Log.e("MainActivity", "MyRemoteService process id = " + processInfo.getProcessId()); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { Log.e("MainActivity", "MyRemoteService onServiceDisconnected"); } }; 复制代码
只要绑定成功就能在有log打印成MyRemoteService所在进程的进程id。这样我们就完成了跟不同进程的Service通信的过程。
代码实操---调用其他app的Service
跟调同app下不同进程下的Service相比,调用其他的app定义的Service有一些细微的差别
-
由于需要其他app访问,所以之前的bindService()使用的隐式调用不在合适,需要在Service定义时定义action
我们在定义的线程的App A 中定义如下Service:
<service android:name=".service.ServerService"> <intent-filter> //这里的action自定义 <action android:name="com.jxx.server.service.bind" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </service> 复制代码
-
我们在需要bindService的App B 中需要做这些处理
-
首先要将A中定义的aidl文件复制到B中,比如我们在上面定义的IProcessInfo.aidl这个文件,包括路径在内需要原封不动的复制过来。
-
在B中调用Service通过显式调用
mTvServerBind.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(); intent.setAction("com.jxx.server.service.bind");//Service的action intent.setPackage("com.jxx.server");//App A的包名 bindService(intent, mServerServiceConnection, BIND_AUTO_CREATE); } }); 复制代码
-
aidl中自定义对象的传递
主要步骤如下:
- 定义自定对象,需要实现Parcelable接口
- 新建自定义对象的aidl文件
- 在传递数据的aidl文件中引用自定义对象
- 将自定义对象以及aidl文件拷贝到需要bindService的app中,主要路径也要原封不动
我们来看一下具体的代码:
-
定义自定义对象,并实现Parcelable接口
public class ServerInfo implements Parcelable { public ServerInfo() { } String mPackageName; public String getPackageName() { return mPackageName; } public void setPackageName(String packageName) { mPackageName = packageName; } protected ServerInfo(Parcel in) { mPackageName = in.readString(); } public static final Creator<ServerInfo> CREATOR = new Creator<ServerInfo>() { @Override public ServerInfo createFromParcel(Parcel in) { return new ServerInfo(in); } @Override public ServerInfo[] newArray(int size) { return new ServerInfo[size]; } }; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(mPackageName); } //使用out或者inout修饰时需要自己添加这个方法 public void readFromParcel(Parcel dest) { mPackageName = dest.readString(); } } 复制代码
-
新建自定义对象的aidl文件
package com.jxx.server.aidl; //注意parcelable 是小写的 parcelable ServerInfo; 复制代码
-
引用自定义对象
package com.jxx.server.aidl; //就算在同一包下,这里也要导包 import com.jxx.server.aidl.ServerInfo; interface IServerServiceInfo { ServerInfo getServerInfo(); void setServerInfo(inout ServerInfo serverinfo); } 复制代码
注意这里的set方法,这里用了inout,一共有3种修饰符
- in:客户端写入,服务端的修改不会通知到客户端 - out:服务端修改同步到客户端,但是服务端获取到的对象可能为空 - inout:修改都收同步的 复制代码
当使用out和inout时,除了要实现Parcelable外还要手动添加readFromParcel(Parcel dest)
-
拷贝自定义对象以及aidl文件到在要引用的App中即可。
-
引用
mServerServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { IServerServiceInfo serverServiceInfo = IServerServiceInfo.Stub.asInterface(service); try { ServerInfo serviceInfo = serverServiceInfo.getServerInfo(); Log.e("MainActivity", "ServerService packageName = " + serviceInfo.getPackageName()); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { Log.e("MainActivity", "ServerService onServiceDisconnected"); } }; 复制代码
List、Map中引用的对象也应该是符合上面要求的自定义对象,或者其他的几种数据类型。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Flutter 完整开发实战详解(十六、详解自定义布局实战)
- 数据结构 1 线性表详解 链表、 栈 、 队列 结合JAVA 详解
- 详解Openstack环境准备
- Java泛型详解
- iOS RunLoop 详解
- Raft协议详解
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。