什么是 service
- A Service is an application component that can perform long-running operations in the background
- service没有UI
- Additionally, a component can bind to a service to interact with it and even perform interprocess communication (IPC)
- For example, a service can handle network transactions, play music, perform file I/O, or interact with a content provider, all from the background
注意
- service不是一个进程或者一个线程, 它是在应用主进程中运行的组件;
- 开发者可以在service中创建新的进程来处理耗时的活动, 来避免Application Not Responding (ANR) errors
后台服务 — background Service
- 在后台运行的service
- 后台服务在系统内存不足的时候,可能会被系统杀死
startService()
- caller: startService() ---- invoke — calledService: onStartCommand()
- 需要传入intent
Intnet sIntent = new Intent(this, MyService.class)
startService(sIntent)
onStartCommand()
- 当一个component调用startService之后会启动service中的onStartCommand()
public int onStartCommand(Intent intent, int flags, int startId)
- intent: 启动时,启动组件传递过来的Intent,如Activity可利用Intent封装所需要的参数并传递给Service
- flags: 表示启动请求时是否有额外数据,可选值有 0,START_FLAG_REDELIVERY,START_FLAG_RETRY
- 0, 在正常创建Service的情况下,onStartCommand传入的flags为0。
- START_FLAG_REDELIVERY 这个值代表了onStartCommand()方法的返回值为 START_REDELIVER_INTENT,而且在上一次服务被杀死前会去调用stopSelf()方法停止服务。其中START_REDELIVER_INTENT意味着当Service因内存不足而被系统kill后,则会重建服务,并通过传递给服务的最后一个 Intent调用 onStartCommand(),此时Intent时有值的。
- START_FLAG_RETRY, 该flag代表当onStartCommand()调用后一直没有返回值时,会尝试重新去调用onStartCommand()
- startId : 指明当前服务的唯一ID,与stopSelfResult(int startId)配合使用,stopSelfResult() 可以更安全地根据ID停止服务
返回值
- START_STICKY
当Service因内存不足而被系统kill后,一段时间后内存再次空闲时,系统将会尝试重新创建此Service,一旦创建成功后将回调onStartCommand方法,但其中的Intent将是null,除非有挂起的Intent,如pendingintent,这个状态下比较适用于不执行命令、但无限期运行并等待作业的媒体播放器或类似服务。 - START_NOT_STICKY
当Service因内存不足而被系统kill后,即使系统内存再次空闲时,系统也不会尝试重新创建此Service。除非程序中再次调用startService启动此Service,这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。 - START_REDELIVER_INTENT
当Service因内存不足而被系统kill后,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand(),任何挂起 Intent均依次传递。与START_STICKY不同的是,其中的传递的Intent将是非空,是最后一次调用startService中的intent。这个值适用于主动执行应该立即恢复的作业(例如下载文件)的服务。
stopService(sIntent)
- caller: stopService() ---- invoke — calledService: onDestroy()
- 需要传入intent
Intnet sIntent = new Intent(this, MyService.class)
stopService(sIntent)
onDestroy()
- 当一个component调用stopService之后会启动service中的onDestroy()
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "on destroy");
}
前台服务 — foreground service
- 后台服务在系统内存不足的时候,可能会被系统杀死. 所以为了保证service不被杀死, 可以使用前台服务
- 前台服务会显示在通知栏中,展示正在运行的service
- 通常在onStartCommand() 中调用startForeground()
startForeground()
public int startForeground(Integer NotificationID, Notification notification)
Example:
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent =
PendingIntent.getActivity(this, 0, notificationIntent, 0);
Notification notification =
new Notification.Builder(this, CHANNEL_DEFAULT_IMPORTANCE)
.setContentTitle(getText(R.string.notification_title))
.setContentText(getText(R.string.notification_message))
.setSmallIcon(R.drawable.icon)
.setContentIntent(pendingIntent)
.setTicker(getText(R.string.ticker_text))
.build();
startForeground(ONGOING_NOTIFICATION_ID, notification);
Bound services — 将service和组件绑定
- 在安卓开发中, 开发者可以将service和别的组件绑定(通常是activity), 这样可以达到service和其他组件沟通的目的(如组件使用service的方法或属性)
首先在Activity中开启service, 会调用service中的onStartCommand:
private void startMyService() {
Log.i(TAG, "Starting Service");
Log.i(TAG, "Thread Id: " + Thread.currentThread().getId());
startService(sIntent);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "onStartComment");
Log.i(TAG, "Thread Id: " + Thread.currentThread().getId() + " Start Id: " + startId);
if (!isRandOn) {
Runnable runnable = () -> {
Log.i(TAG, "Inside Run");
isRandOn = true;
generateRandomNumber();
};
Thread t = new Thread(runnable);
t.start();
}
return START_NOT_STICKY;
}
然后在service中使用binder
class MyLocalBinder extends Binder {
public MyService getMyService() {
return MyService.this;
}
}
private Binder myBinder = new MyLocalBinder();
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "on Bind");
return myBinder;
}
在Activity中bind service, 通过以下代码拿到service object, 这样可以调用service中的函数
private void bindMyService() {
if (myConnection == null) {
Log.i(TAG, "Binding service");
myConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder ibinder) {
Log.i(TAG, "onServiceConnected");
MyService.MyLocalBinder myBinder= (MyService.MyLocalBinder) ibinder;
myService = myBinder.getMyService();
isBound = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected");
isBound = false;
}
};
}
bindService(sIntent, myConnection, BIND_AUTO_CREATE);
}
private void getNumber() {
if (isBound) {
tvRandomNumber.setText("Random Number " + myService.getRandomNumber());
}
}
完整代码
MainActivity.java
package com.project.lab3;
import androidx.appcompat.app.AppCompatActivity;
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;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private static final String TAG = "MainActivity";
Button buttonStart, buttonStop;
Button buttonBind, buttonUnBind;
Button buttonGetRandomNumber;
TextView tvRandomNumber;
Intent sIntent;
private ServiceConnection myConnection;
MyService myService;
boolean isBound;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
buttonStart = findViewById(R.id.btn_start);
buttonStop = findViewById(R.id.btn_stop);
buttonBind = findViewById(R.id.btn_bind);
buttonUnBind = findViewById(R.id.btn_unbind);
buttonGetRandomNumber = findViewById(R.id.btn_getNumber);
buttonGetRandomNumber = findViewById(R.id.btn_getNumber);
tvRandomNumber = findViewById(R.id.tv_random_number);
buttonStart.setOnClickListener(this);
buttonStop.setOnClickListener(this);
buttonBind.setOnClickListener(this);
buttonUnBind.setOnClickListener(this);
buttonGetRandomNumber.setOnClickListener(this);
sIntent = new Intent(this, MyService.class);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_start:
startMyService();
break;
case R.id.btn_stop:
stopMyService();
break;
case R.id.btn_bind:
bindMyService();
break;
case R.id.btn_unbind:
unbindMyService();
break;
case R.id.btn_getNumber:
getNumber();
break;
}
}
private void getNumber() {
if (isBound) {
tvRandomNumber.setText("Random Number " + myService.getRandomNumber());
}
}
private void unbindMyService() {
if (isBound) {
Log.i(TAG, "Unbinding the service");
unbindService(myConnection);
myConnection = null;
isBound = false;
}
}
private void bindMyService() {
if (myConnection == null) {
Log.i(TAG, "Binding service");
myConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder ibinder) {
Log.i(TAG, "onServiceConnected");
MyService.MyLocalBinder myBinder= (MyService.MyLocalBinder) ibinder;
myService = myBinder.getMyService();
isBound = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected");
isBound = false;
}
};
}
bindService(sIntent, myConnection, BIND_AUTO_CREATE);
}
private void stopMyService() {
Log.i(TAG, "Stopping service");
stopService(sIntent);
}
private void startMyService() {
Log.i(TAG, "Starting Service");
Log.i(TAG, "Thread Id: " + Thread.currentThread().getId());
startService(sIntent);
}
}
MyService.java
package com.project.lab3;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import java.util.Random;
public class MyService extends Service {
private static final String TAG = "MyService";
private boolean isRandOn = false;
int rNum;
public MyService() {
}
class MyLocalBinder extends Binder {
public MyService getMyService() {
return MyService.this;
}
}
private Binder myBinder = new MyLocalBinder();
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "on Bind");
return myBinder;
}
private void generateRandomNumber() {
while(isRandOn) {
try {
Thread.sleep(1000);
rNum = new Random().nextInt(1000);
Log.i(TAG, "Thread Id: " + Thread.currentThread().getId() + " Random Number " + rNum);
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
}
}
public int getRandomNumber() {
return rNum;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "onStartComment");
Log.i(TAG, "Thread Id: " + Thread.currentThread().getId() + " Start Id: " + startId);
if (!isRandOn) {
Runnable runnable = () -> {
Log.i(TAG, "Inside Run");
isRandOn = true;
generateRandomNumber();
};
Thread t = new Thread(runnable);
t.start();
}
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "on destroy");
isRandOn = false;
}
@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG, "onUnbind");
return super.onUnbind(intent);
}
}
|