Android Service 小结
IntentService
- 创建
public class MyIntentService extends IntentService {
/**
* Creates an IntentService. Invoked by your subclass's constructor.
*
*/
public MyIntentService() {
//Used to name the worker thread, important only for debugging.
super("MyIntentService");
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
String url = intent.getStringExtra("url");
Log.i("MyIntentService", "onHandleIntent url:" + url);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i("MyIntentService", "onHandleIntent end url:" + url);
}
}
AndroidManifest
注册
<service android:name=".service.MyIntentService" android:exported="false"/>
- 使用
Intent intent = new Intent(MainActivity.this, MyIntentService.class);
intent.putExtra("url", "abcd" + count++);
startService(intent);
- 测试结果
I/MyIntentService: onHandleIntent url:abcd0
2020-06-23 09:30:20.399 21121-21175/com.aaaa.sdkdemo I/MyIntentService: onHandleIntent end url:abcd0
2020-06-23 09:30:20.403 21121-21175/com.aaaa.sdkdemo I/MyIntentService: onHandleIntent url:abcd1
2020-06-23 09:30:23.405 21121-21175/com.aaaa.sdkdemo I/MyIntentService: onHandleIntent end url:abcd1
2020-06-23 09:30:23.407 21121-21175/com.aaaa.sdkdemo I/MyIntentService: onHandleIntent url:abcd2
2020-06-23 09:30:26.409 21121-21175/com.aaaa.sdkdemo I/MyIntentService: onHandleIntent end url:abcd2
2020-06-23 09:30:26.412 21121-21175/com.aaaa.sdkdemo I/MyIntentService: onHandleIntent url:abcd3
2020-06-23 09:30:29.414 21121-21175/com.aaaa.sdkdemo I/MyIntentService: onHandleIntent end url:abcd3
- 总结
- 执行简单后台操作的首选方式
IntentService
在单线程后台运行操作- 工作请求依序运行。即发送多个工作请求,按顺序单个运行,不会并行运行
- 在
IntentService
上运行的操作无法中断。 IntentService
运行完成后自动停止服务。
JobIntentService
- 创建
public class MyJobIntentService extends JobIntentService {
public static final int JOB_ID = 1000;
@Override
protected void onHandleWork(@NonNull Intent intent) {
String url = intent.getStringExtra("url");
Log.i("MyJobIntentService", "onHandleWork url:" + url);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i("MyJobIntentService", "onHandleWork end url:" + url);
}
}
AndroidManifest
注册
<service android:name=".service.MyJobIntentService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="false"/>
如果不添加 android:permission="android.permission.BIND_JOB_SERVICE"
会报错:
java.lang.IllegalArgumentException: Scheduled service ComponentInfo{...} does not require android.permission.BIND_JOB_SERVICE permission
- 使用
Intent serviceIntent = new Intent();
serviceIntent.putExtra("url", "aaa" + count++);
MyJobIntentService.enqueueWork(MainActivity.this, MyJobIntentService.class,
MyJobIntentService.JOB_ID, serviceIntent);
- 测试结果
2020-06-23 10:22:27.246 23370-23417/com.aaaa.sdkdemo I/MyJobIntentService: onHandleWork url:aaa0
2020-06-23 10:22:30.253 23370-23417/com.aaaa.sdkdemo I/MyJobIntentService: onHandleWork end url:aaa0
2020-06-23 10:22:30.256 23370-23417/com.aaaa.sdkdemo I/MyJobIntentService: onHandleWork url:aaa1
2020-06-23 10:22:33.267 23370-23417/com.aaaa.sdkdemo I/MyJobIntentService: onHandleWork end url:aaa1
2020-06-23 10:22:33.273 23370-23417/com.aaaa.sdkdemo I/MyJobIntentService: onHandleWork url:aaa2
2020-06-23 10:22:36.317 23370-23417/com.aaaa.sdkdemo I/MyJobIntentService: onHandleWork end url:aaa2
2020-06-23 10:22:36.321 23370-23417/com.aaaa.sdkdemo I/MyJobIntentService: onHandleWork url:aaa3
2020-06-23 10:22:39.362 23370-23417/com.aaaa.sdkdemo I/MyJobIntentService: onHandleWork end url:aaa3
- 总结
- 适用于执行后台任务;
- 多个工作请求,按顺序单个运行;
- 在
Android O
及以上版本内部实现存在不同- 在
Android O
之前,- 分发工作请求通过
Context.startService
; - 工作请求入队后会立刻执行;
service
可以无限期运行,运行时间越长,越容易被系统彻底杀死进程;- 在内存压力下,预期进程会被杀死,即使是最近启动的服务;
- 分发工作请求通过
- 在
Android O
以及之后的版本- 分发工作请求是作为
job
通过JobScheduler.enqueue
分发的; - 如果设备在
dozing
状态,会以Job
的形式运行,将会遵守JobScheduler
策略;Job
将不会立刻运行;可能延迟很多,在强大的内存压力下;
- 分发工作请求是作为
- 在
- 使用
JobIntentService
需要以下权限: (运行在Android O
时)Manifest.permission.WAKE_LOCK
BoundService (扩展 Binder 类)
绑定服务是客户端-服务器接口中的服务器。借助绑定服务,组件(例如 Activity
)可以绑定到服务、发送请求、接收响应,以及执行进程间通信 (IPC)。绑定服务通常只在为其他应用组件提供服务时处于活动状态,不会无限期在后台运行。
- 创建
public class MusicService extends Service {
private final MusicBinder binder = new MusicBinder();
public class MusicBinder extends Binder {
public MusicService getService() {
return MusicService.this;
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return binder;
}
public void doSomething(int a) {
Log.i("MusicService", "doSomething " + a);
}
}
AndroidManifest
注册
<service android:name=".service.MusicService" android:exported="false"/>
- 使用
public class MainActivity extends AppCompatActivity {
int count = 0;
private MusicService musicService = null;
private boolean isBound = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_bound_service).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (isBound) {
musicService.doSomething(20);
}
}
});
}
@Override
protected void onStart() {
super.onStart();
// bind to service
Intent intent = new Intent(this, MusicService.class);
boolean bindRet = bindService(intent, connection, Context.BIND_AUTO_CREATE);
Log.i("Main", "bindRet:" + bindRet);
}
ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
MusicService.MusicBinder binder = (MusicService.MusicBinder) service;
musicService = binder.getService();
isBound = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
isBound = false;
}
};
@Override
protected void onStop() {
super.onStop();
unbindService(connection);
}
}
- 总结
- 您可以创建同时具有已启动和已绑定两种状态的服务。换言之,可通过调用
startService()
启动服务,让服务无限期运行;此外,还可通过调用bindService()
让客户端绑定到该服务。 - 如果您确实允许服务同时具有已启动和已绑定状态,则在启动服务后,如果所有客户端均解绑服务,则系统不会销毁该服务。为此,您必须通过调用
stopSelf()
或stopService()
显式停止服务。 - 尽管您通常应实现
onBind()
或onStartCommand()
,但有时需同时实现这两种方法。例如,音乐播放器可能认为,让其服务无限期运行并同时提供绑定会很有用处。如此一来,Activity
便可通过启动服务来播放音乐,并且即使用户离开应用,音乐也不会停止。然后,当用户返回应用时,Activity
便能绑定到服务,重新获得回放控制权。
BoundService (使用 Messenger)
- 创建
public class LocalService extends Service {
private Messenger mMessenger;
static class LocalHandler extends Handler {
private Context context;
LocalHandler(Context context) {
this.context = context;
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.i("LocalService", "handleMessage msg:" + msg.obj);
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
mMessenger = new Messenger(new LocalHandler(this));
return mMessenger.getBinder();
}
}
AndroidManifest
注册
<service android:name=".service.LocalService" android:exported="false"/>
- 使用
public class MainActivity extends AppCompatActivity {
private Messenger localMessenger;
private boolean isBoundLocal = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_bound_local_service).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (isBoundLocal) {
Message message = Message.obtain();
message.obj = "local_msg";
try {
localMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
});
}
@Override
protected void onStart() {
super.onStart();
// bind to local service
Intent localIntent = new Intent(this, LocalService.class);
if (!bindService(localIntent, localConnection, Context.BIND_AUTO_CREATE)) {
unbindService(localConnection);
}
}
ServiceConnection localConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
localMessenger = new Messenger(service);
isBoundLocal = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
isBoundLocal = false;
}
};
@Override
protected void onStop() {
super.onStop();
unbindService(localConnection);
}
- 总结
- 如需让服务与远程进程通信,则可使用
Messenger
为您的服务提供接口。借助此方法,您无需使用AIDL
便可执行进程间通信 (IPC)。
为接口使用 Messenger
比使用 AIDL
更简单,因为 Messenger
会将所有服务调用加入队列。纯 AIDL
接口会同时向服务发送多个请求,服务随后必须执行多线程处理。
对于大多数应用,服务无需执行多线程处理,因此使用 Messenger
即可让服务一次处理一个调用。
- 只有 Activity、服务和内容提供程序可以绑定到服务,您无法从广播接收器绑定到服务。
bindService()
绑定为异步操作;通过ServiceConnection
传递IBinder
;- 如果
bindService()
方法返回“false”,则说明您的客户端未与服务进行有效连接。但是,您的客户端仍应调用unbindService()
;否则,您的客户端将阻止服务在空闲时关闭。 - 如果您使用
Intent
来绑定到Service
,请务必使用显式 Intent 来确保应用的安全性。使用隐式Intent
启动服务存在安全隐患,因为您无法确定哪些服务会响应该Intent
,并且用户无法看到哪些服务已启动。从Android 5.0
(API 级别 21)开始,如果使用隐式 Intent 调用bindService()
,则系统会抛出异常。 - 您应该始终捕获
DeadObjectException
异常,系统会在连接中断时抛出此异常。这是远程方法抛出的唯一异常。 - 当 Activity 在后台处于停止运行状态时,若您仍希望其能接收响应,则可在 onCreate() 期间进行绑定,在 onDestroy() 期间取消绑定。请注意,这意味着您的 Activity 在整个运行过程中(甚至包括后台运行期间)均需使用服务,因此如果服务位于其他进程内,则当您提高该进程的权重时,系统便更有可能会将其终止。
管理绑定服务的生命周期
当取消服务与所有客户端之间的绑定时,Android 系统会销毁该服务(除非您还使用 onStartCommand()
启动了该服务)。因此,如果您的服务完全是绑定服务,则您无需管理其生命周期,Android 系统会根据它是否绑定到任何客户端代您管理。
不过,如果您选择实现 onStartCommand()
回调方法,则您必须显式停止服务,因为系统现已将其视为已启动状态。在此情况下,服务将一直运行,直到其通过 stopSelf()
自行停止,或其他组件调用 stopService()(与该服务是否绑定到任何客户端无关)。
此外,如果您的服务已启动并接受绑定,则当系统调用您的 onUnbind()
方法时,如果您想在客户端下一次绑定到服务时接收 onRebind()
调用,则可选择返回 true。onRebind()
返回空值,但客户端仍在其 onServiceConnected()
回调中接收 IBinder。下图说明这种生命周期的逻辑。
已启动且允许绑定的服务的生命周期:
参考地址: https://developer.android.com/guide/components/services