Service
Service声明
Service的启动方式上,可以将Service分为Started Service和Bind Service。无论哪种具体的Service启动类型,都是通过继承Service基类自定义而来。在使用Service时,要想系统
能够找到此自定义Service,无论哪种类型,都需要在AndroidManifest.xml中声明,语法格式如下:
<service android:enabled=["true" | "false"]
android:exported=["true" | "false"]
android:icon="drawable resource"
android:isolatedProcess=["true" | "false"]
android:label="string resource"
android:name="string"
android:permission="string"
android:process="string" >
</service>
上述使用到的属性说明在下面的表格:
模式 | 描述 |
---|---|
enable | Service是否能被系统初始化,默认为true。 |
exported | 其他应用能否访问该服务,如果不能,则只有本应用或有相同用户ID的应用能访问。 |
icon | 类似 label ,是图标,尽量用 drawable 资源的引用定义。 |
isolatedProcess | 设置 true 意味着,服务会在一个特殊的进程下运行,这个进程与系统其他进程分开且没有自己的权限。 |
lable | 可以显示给用户的服务名称。如果没设置,就用 <application> 的 lable 。 |
name | 你所编写的服务类的类名,可填写完整名称,包名+类名 |
permission | 标识此Service的调用权限,如果没有设置,则默认使用 |
process | 服务运行所在的进程名。通常为默认为应用程序所在的进程,与包名同名。 |
在清单文件进行注册之后,创建一个类继承Service并实现onBind()方法:
public class CustomService extends Service {
@Override
public IBinder onBind(Intent intent) {
throw new UnsupportedOperationException("Not yet implemented");
}
}
Service基础
开启Service有两种方式:start Service(标准启动) 和 bound Serive(绑定启动)。
Start Service
标准开启的Service会长期在后台运行,但是此时Activity是不能调用服务中的方法。
Intent service = new Intent(MainActivity.this, CustomService.class);
context.startService(service);
如果想停止Service则调用stopService(intent)即可,当然Service也可以调用stopSelf()来停止自身。
Bind Service
绑定开启Service时,Activity可以调用服务中的方法,但是此时服务是不能长期在后台运行。
Intent service = new Intent(MainActivity.this, CustomService.class);
/**
* service: intent意图
* ServiceConnection: 绑定开启的代理对象
* BIND_AUTO_CREATE: 服务如果在绑定的时候不存在,会自动创建
*/
bindService(service, new MyServiceConnection(), Context.BIND_AUTO_CREATE);
绑定开启服务需要注意在Service中的onBind()方法必须有Bind的实现类的当做返回值,否则会报错。
Mix Service
mix Service(混合启动)来启动。它具备标准启动和绑定启动的共同优点,此时服务即可长期在后台运行,Activity也可以调用服务中的方法。
1、首先通过标准模式启动服务,这样服务就长期在后台运行。
2、如果需要调用服务中的方法,则再使用绑定模式绑定服务。
3、如果需要解绑服务则调用unbindService()解绑服务。
4、如果需要停止服务,则调用stopService()停止服务。
注:有时候我们解绑服务后,发现还是可以调用服务中的方法,是因为垃圾回收器还没有回收调用该方法的对象。
生命周期
Service的生命周期根据不同的启动方式而不同,具体参看下图所示:
![img](file:///C:/Users/Legend/Documents/My Knowledge/temp/f7e21929-10e7-4a9f-a7c8-d88d9613bae6/128/index_files/641556de-37ab-4be9-8a6a-44074e20b0e7.jpg)
Start Service的生命周期
标准开启Service时,会执行onCreate(0 -> onStartCommand()方法,如果多次开启服务只会执行onStartCommand()方法。
如果停止Service的话,会执行stopService() -> onDestory()方法。
startService() -> onCreate() -> onStartCommand() -> running -> stopService()/stopSelf() -> onDestroy() -> stopped
BInd Service的生命周期
绑定开启Service时,会执行onCreate() -> onBind()方法,如果多次开启不会调用任何方法。如果停止Service的话会执行onUnbind() -> onDestory()方法。
bindService() -> onCreate() -> onBind() -> running-> onUnbind() -> onDestroy() -> stopped
绑定开启服务还有如下一些地方需要注意:
1、如果oBind()方法返回值是null,onServerConnected方法不会被调用。
2、绑定的服务在系统设置界面,正在运行条目是看不到的。
3、绑定的服务和Activity不求同时生,但求同时死。
4、解除绑定服务后,服务会立即停止,且服务只可以被解除绑定一次,多次解除绑定代码会抛出异常。
Service的销毁
Service进程在处理完任务后,要使用安卓Service的stopself方法或者onDestroy方法结束Service,所在进程的回收交给安卓的垃圾回收机制来做。
其中,onStartCommand返回值有下面几种情况:建议不要修改onStartCommand方法的返回值,都交由系统处理比较好。
START_STICKY:Service被异常kill掉,会被系统拉起,但Intent内容丢失。
START_NOT_STICKY:Service被异常kill掉时,不会被系统拉起。
START_REDELIVER_INTENT:Service被异常kill掉时,会被系统拉起,并且Intent内容保留。
START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保证服务被终止后一定能重启。
Service通信
如果Activity需要和Service进行通信,我们必须用Bind Service的方法来开启服务。绑定方式开启的Service必须实现onBind()方法,该方法返回同一个定义
了服务通信接口的IBinder对象,Activity或其他程序组件可以调用bindService()方法获取接口并且调用服务上的方法。
创建绑定的服务,首先要定义客户端和服务通信方式的接口,这个接口必须是IBinder的实现类,并且必须通过onBind()方法返回,一旦客户端接收到IBinder,
就可以通过这个接口进行交互。除此之外,多个客户端可以绑定一个服务,还可以通过unBindService()方法解除绑定,当没有组件绑定在该服务时,服务会自动销毁。
// Activity
public class MainActivity extends AppCompatActivity {
private class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 调用Service中的方法
MyBinder binder = (MyBinder) service;
String message = binder.getMessage();
System.out.println(message);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_nomal).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent service = new Intent(MainActivity.this, CustomService.class);
bindService(service, new MyServiceConnection(), Context.BIND_AUTO_CREATE);
}
});
}
}
// Service
public class CustomService extends Service {
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
public class MyBinder extends Binder {
/**
* 该方法提供给Activity或其他组件进行调用
*/
public String getMessage() {
return "I am in Service";
}
}
}
IntentService
IntentService是继承于Service并处理异步请求的一个类,在IntentService内有一个工作线程来处理耗时操作,启动IntentService的方式和启动传统Service一样,
同时,当任务执行完后,IntentService会自动停止,另外,IntentService可以被启动多次,而每一个耗时操作会以工作队列的方式在IntentService的onHandleIntent
回调方法中执行,所有请求都在一个单线程中,不会阻塞应用程序的主线程(UI Thread),同一时间只处理一个请求。
Service是运行在主线程的,所以它不能做耗时操作,如果要做耗时操作则可以开启一个线程,而IntentService就是解决该问题的,它的处理流程如下:
创建worker线程传递给onStartCommand()的所有intent,不占用UI线程。
创建一个工作队列,传递一个intent到你实现的onHandleIntent()方法,避免多线程。
在所有启动请求被处理后,会自动关闭服务,不需要调用stopSelf()方法。
默认提供onBind()的实现,并返回null。
默认提供onStartCommand()的实现,实现发送intent到工作队列,再到onHandleIntent()方法实现。
以上都是IntentService都已经实现的,我们需要做的就是实现构造方法和 onHandleIntent():
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
try {
for (int i = 0; i < 20; i++) {
Log.w("MyIntentService:", String.valueOf(i));
Thread.sleep(1 * 1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
}
注意:如果需要重写其他回调方法一定要调用 super() 方法,保证 IntentService 正确处理 worker 线程,只有 onHandleIntent() 和 onBind() 不需要如此。
前台服务
Service按照运行来划分有两种:前台服务和后台服务。
- 前台服务:前台服务可以一直保持运行状态,且不会在内存不足的情况下被回收。
- 后台服务:后台服务也就是我们平时使用的普通服务,它的优先级比较低,会在内存不够的情况下可能被回收。
public class ForeGroundService extends Service {
@Override
public void onCreate() {
super.onCreate();
// 构建通知栏
Notification.Builder builder = new Notification.Builder(getApplicationContext());
Intent intent = new Intent(this, MainActivity.class);
builder.setContentIntent(PendingIntent.getActivity(this, 0, intent, 0))
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
.setContentTitle("title")
.setContentText("text")
.setWhen(System.currentTimeMillis());
Notification notification = builder.build();
notification.defaults = Notification.DEFAULT_SOUND;
// 设置服务为前台服务,参数一:唯一的通知标识;参数二:通知消息。
startForeground(110, notification);
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onDestroy() {
// 停止前台服务--参数:表示是否移除之前的通知
stopForeground(true);
}
}
远程服务
aidl(Android Interface definition language),它是一种android内部进程通信接口的描述语言,通过它我们可以定义进程间的通信接口IPC(interprocess communication)
内部进程通信,满足两个进程之间接口数据的交换。
使用远程服务有如下的优点和缺点:
优点
远程服务有自己的独立进程,不会受到其它进程的影响;
可以被其它进程复用,提供公共服务;
具有很高的灵活性。
缺点
相对普通服务,占用系统资源较多,使用AIDL进行IPC也相对麻烦。
关于远程服务的通信示意图如下:
远程服务创建
定义AIDL接口 通过AIDL文件定义服务(Service)向客户端(Client)提供的接口,我们需要在对应的目录下添加一个后缀为.aidl的文件,IMyAidlInterface.aidl文件内容如下:
interface IMyAidlInterface {
String getMessage();
}
注:如果服务端与客户端不在同一App上,需要在客户端、服务端两侧都建立该aidl文件。
创建远程Service
在远程服务中,通过Service的onBind(),在客户端与服务端建立连接时,用来传递Stub(存根)对象。
// 远程服务示例
public class RemoteService extends Service {
public RemoteService() {
}
@Override
public IBinder onBind(Intent intent) {
return stub;// 在客户端连接服务端时,Stub通过ServiceConnection传递到客户端
}
// 实现接口中暴露给客户端的Stub--Stub继承自Binder,它实现了IBinder接口
private IMyAidlInterface.Stub stub = new IMyAidlInterface.Stub(){
// 实现了AIDL文件中定义的方法
@Override
public String getMessage() throws RemoteException {
// 在这里我们只是用来模拟调用效果,因此随便反馈值给客户端
return "Remote Service方法调用成功";
}
};
}
同时,在AndroidManifest.xml中对Remote Service进行如下配置:
<service
android:name=".RemoteService"
android:process="com.test.remote.msg">
<intent-filter>
<action android:name="com.legend.remoteservice.RemoteService"/>
</intent-filter>
</service>
如果客户端与服务端在同个App中,AndroidManifest.xml中设置Remote Service的andorid:process属性时,有两种情况需要注意:
设置的进程名以(:)开头,则新进程是私有的,每次被执行或者被需要的时候会在新进程中创建。
设置的进程以小写字符开头,则服务运行在以这个名字命名的全局进程中,允许不同组件共享进程,从而减少资源浪费(需要相应权限)。
客户端调用远程服务接口
在客户端中建立与Remote Service的连接,获取Stub,然后调用Remote Service提供的方法来获取对应数据。
public class MainActivity extends AppCompatActivity {
private IMyAidlInterface iMyAidlInterface;// 定义接口变量
private ServiceConnection connection;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindRemoteService();
}
private void bindRemoteService() {
Intent intentService = new Intent();
intentService.setClassName(this,"com.zihao.remoteservice.RemoteService");
connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName,IBinder iBinder) {
// 从连接中获取Stub对象
iMyAidlInterface = IMyAidlInterface.Stub.asInterface(iBinder);
// 调用Remote Service提供的方法
try {
Log.d("MainActivity", "获取到消息:" + iMyAidlInterface.getMessage());
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
// 断开连接
iMyAidlInterface = null;
}
};
bindService(intentService, connection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (connection != null)
unbindService(connection);// 解除绑定
}
}
远程服务实例
反射挂断电话
1、找到上下文的mBase引用的类ContextImpl,通过查看getSystemService源码可以知道,所有的系统服务都在一个map集合中。
public Object getSystemService(String name){
ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
return fetcher == null ? null : fetcher.getService(this);
}
2、接下来去查找map集合SYSTEM_SERVERCE_MAP,发现它其实是一个hashMap,这里需要详细解说:
registerService(POWER_SERVICE, new ServiceFetcher(){
public Object createService(ContextImpl ctx){
IBinder b = ServiceManager.getService(POWER_SERVICE);
IPowerManager service = IPowerManager.Stub.asInterface(b);
return new PowerManager(service, ctx.mMainThread.getHandler());
}}
);
由于某些服务被认为不安全或侵犯用户隐私,所以谷歌在包装系统服务的时候,将某些服务进行了隐藏(@hide),比如挂断电话。我们需要先拿到ServiceManager对象, 但是谷歌不希望我们使用该对象,所以将该对象进行隐藏,所以参考下面的反射。
IBinder iBinder = ServiceManager.getService(TELEPHONY_SERVICE);
3、通过当前的service类的字节码来获取ServiceManager的字节码文件
// IBinder iBinder = ServiceManager.getService(TELEPHONY_SERVICE);
try{
Class clazz = CallSmsSafeService.class.getClassLoader().loadClass("android.os.ServiceManager");
Method method = clazz.getDeclaredMethod("getService", String.class);
IBinder iBinder = (IBinder) method.invoke(null, TELEPHONY_SERVICE);
}catch (Exception e){
e.printStackTrace();
}
4、下一步则是将iBinder转成接口类型,需要两个aidl文件,其中一个是依赖另外一个存在的,注意保证包名一致
1、android.telephony下的NeighboringCellInfo.aidl
/* //device/java/android/android/content/Intent.aidl
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
package android.telephony;
parcelable NeighboringCellInfo;
2、com.android.internal.telephony下的ITelephony.aidl
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.telephony;
import android.os.Bundle;
import java.util.List;
import android.telephony.NeighboringCellInfo;
/**
* Interface used to interact with the phone. Mostly this is used by the
* TelephonyManager class. A few places are still using this directly.
* Please clean them up if possible and use TelephonyManager insteadl.
*
* {@hide}
*/
interface ITelephony {
/**
* Dial a number. This doesn't place the call. It displays
* the Dialer screen.
* @param number the number to be dialed. If null, this
* would display the Dialer screen with no number pre-filled.
*/
void dial(String number);
/**
* Place a call to the specified number.
* @param number the number to be called.
*/
void call(String number);
/**
* If there is currently a call in progress, show the call screen.
* The DTMF dialpad may or may not be visible initially, depending on
* whether it was up when the user last exited the InCallScreen.
*
* @return true if the call screen was shown.
*/
boolean showCallScreen();
/**
* Variation of showCallScreen() that also specifies whether the
* DTMF dialpad should be initially visible when the InCallScreen
* comes up.
*
* @param showDialpad if true, make the dialpad visible initially,
* otherwise hide the dialpad initially.
* @return true if the call screen was shown.
*
* @see showCallScreen
*/
boolean showCallScreenWithDialpad(boolean showDialpad);
/**
* End call or go to the Home screen
*
* @return whether it hung up
*/
boolean endCall();
/**
* Answer the currently-ringing call.
*
* If there's already a current active call, that call will be
* automatically put on hold. If both lines are currently in use, the
* current active call will be ended.
*
* TODO: provide a flag to let the caller specify what policy to use
* if both lines are in use. (The current behavior is hardwired to
* "answer incoming, end ongoing", which is how the CALL button
* is specced to behave.)
*
* TODO: this should be a oneway call (especially since it's called
* directly from the key queue thread).
*/
void answerRingingCall();
/**
* Silence the ringer if an incoming call is currently ringing.
* (If vibrating, stop the vibrator also.)
*
* It's safe to call this if the ringer has already been silenced, or
* even if there's no incoming call. (If so, this method will do nothing.)
*
* TODO: this should be a oneway call too (see above).
* (Actually *all* the methods here that return void can
* probably be oneway.)
*/
void silenceRinger();
/**
* Check if we are in either an active or holding call
* @return true if the phone state is OFFHOOK.
*/
boolean isOffhook();
/**
* Check if an incoming phone call is ringing or call waiting.
* @return true if the phone state is RINGING.
*/
boolean isRinging();
/**
* Check if the phone is idle.
* @return true if the phone state is IDLE.
*/
boolean isIdle();
/**
* Check to see if the radio is on or not.
* @return returns true if the radio is on.
*/
boolean isRadioOn();
/**
* Check if the SIM pin lock is enabled.
* @return true if the SIM pin lock is enabled.
*/
boolean isSimPinEnabled();
/**
* Cancels the missed calls notification.
*/
void cancelMissedCallsNotification();
/**
* Supply a pin to unlock the SIM. Blocks until a result is determined.
* @param pin The pin to check.
* @return whether the operation was a success.
*/
boolean supplyPin(String pin);
/**
* Handles PIN MMI commands (PIN/PIN2/PUK/PUK2), which are initiated
* without SEND (so <code>dial</code> is not appropriate).
*
* @param dialString the MMI command to be executed.
* @return true if MMI command is executed.
*/
boolean handlePinMmi(String dialString);
/**
* Toggles the radio on or off.
*/
void toggleRadioOnOff();
/**
* Set the radio to on or off
*/
boolean setRadio(boolean turnOn);
/**
* Request to update location information in service state
*/
void updateServiceLocation();
/**
* Enable location update notifications.
*/
void enableLocationUpdates();
/**
* Disable location update notifications.
*/
void disableLocationUpdates();
/**
* Enable a specific APN type.
*/
int enableApnType(String type);
/**
* Disable a specific APN type.
*/
int disableApnType(String type);
/**
* Allow mobile data connections.
*/
boolean enableDataConnectivity();
/**
* Disallow mobile data connections.
*/
boolean disableDataConnectivity();
/**
* Report whether data connectivity is possible.
*/
boolean isDataConnectivityPossible();
Bundle getCellLocation();
/**
* Returns the neighboring cell information of the device.
*/
List<NeighboringCellInfo> getNeighboringCellInfo();
int getCallState();
int getDataActivity();
int getDataState();
/**
* Returns the current active phone type as integer.
* Returns TelephonyManager.PHONE_TYPE_CDMA if RILConstants.CDMA_PHONE
* and TelephonyManager.PHONE_TYPE_GSM if RILConstants.GSM_PHONE
*/
int getActivePhoneType();
/**
* Returns the CDMA ERI icon index to display
*/
int getCdmaEriIconIndex();
/**
* Returns the CDMA ERI icon mode,
* 0 - ON
* 1 - FLASHING
*/
int getCdmaEriIconMode();
/**
* Returns the CDMA ERI text,
*/
String getCdmaEriText();
/**
* Returns true if CDMA provisioning needs to run.
*/
boolean getCdmaNeedsProvisioning();
/**
* Returns the unread count of voicemails
*/
int getVoiceMessageCount();
/**
* Returns the network type
*/
int getNetworkType();
/**
* Return true if an ICC card is present
*/
boolean hasIccCard();
}
这时系统会在gen目录的com.android.internal.telephony包下自动生成一个ITelephony.java的接口文件
5、继续代码实现反射挂断电话的操作,这时会出现很多高级的api可以供我们使用了
// IBinder iBinder = ServiceManager.getService(TELEPHONY_SERVICE)
try {
Class clazz = CallSmsSafeService.class.getClassLoader().loadClass("android.os.ServiceManager");
Method method = clazz.getDeclaredMethod("getService", String.class);
IBinder iBinder = (IBinder) method.invoke(null, TELEPHONY_SERVICE);
ITelephony iTelephony = ITelephony.Stub.asInterface(iBinder);
iTelephony.endCall();
} catch (Exception e) {
e.printStackTrace();
}
要挂断电话还需要添加拨打电话的权限
<uses-permission android:name="android.permission.CALL_PHONE"/>
此时如果是在API28以下的设备中可以正常拦截 但API28以上的设备会报如下错误:
JAVA.LANG.NOSUCHMETHODERROR: NO INTERFACE METHOD ENDCALL()Z IN CLASS LCOM/ANDROID/INTERNAL/TELEPHONY/ITELEPHONY;
出现这个异常的原因就是因为在API28以上的设备时,不再支持反射。而是同过TelecomManager 调用endCall()方法。