Service简介
Service是Android中实现程序后台运行的解决方案,Service非常适合执行不需要和用户交互而且还需要长期运行的任务。
Service运行不依赖任何用户界面,即使程序被切换到后台,或者用户打开了另一个应用程序。
Service并不是运行在一个独立的进程中,是依赖于创建Service的进程。当某个进程被杀死时,依赖于该进程的所有Service也就被杀死。
Service并不会自动开启新的线程,所有的代码默认都是在主线程中运行。
Android多线程编程
线程的基本用法
创建线程的三种方法:
-
创建Thread对象,并重写run()方法
匿名内部类形式:
Thread thread = new Thread(new Runnable() { @Override public void run() { } });
创建子类继承Thread类,并重写run()方法:
public class MyThread extends Thread{ @Override public void run() { } }
-
创建自定义类,实现Runnable接口
示例代码:
public class MyThread implements Runnable{ @Override public void run() { } }
-
创建自定义类,实现Callable<T>接口
示例代码:
public class MyThread<T> implements Callable<T> { @Override public T call() throws Exception { return null; } }
在子线程中更新UI
Android的UI线程是不安全的,如果想要更新应用程序里的UI元素,必须在主线程中进行,不然会出现异常。
创建一个布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btn_change_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/changeTextContent"/>
<TextView
android:id="@+id/tv_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
通过异步机制来执行耗时的操作:
public class ChangeUIActivity extends AppCompatActivity {
private Button btnChangeText = null;
private TextView tvTextContent = null;
private int updateText = 1;
private Handler handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
if (msg.what == updateText){
tvTextContent.setText("这是更新后的代码");
}
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.update_ui_layout);
btnChangeText = findViewById(R.id.btn_change_content);
tvTextContent = findViewById(R.id.tv_text_view);
//在子线程中更新UI
btnChangeText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
new Thread(new Runnable() {
@Override
public void run() {
Message msg = new Message();
msg.what = updateText;
handler.sendMessage(msg);
}
}).start();
}
});
}
}
解析异步消息处理机制
Android的异步消息机制主要有以下四部分组成:
-
Message
Message是线程之间传递的消息,内部可以携带少量信息,用于在不同线程之间传递信息;
-
Handler
Handler是用来发送和处理消息的;
通过Handler.sendMessage()或者Handler.post()等方法将Message发出;
Message在Handler.handleMessage()方法中被处理;
-
MessageQueue
MessageQueue是消息队列的意思,主要用于存放所有通过Handler发送的消息;
这部分消息会一直存在于消息队列中,等待被处理;
一个线程只能有一个MessageQueue;
-
Looper
Looper用来管理线程中的MessageQueue,调用Looper.loop()之后,就会进入一个无限循环,每当发现MessageQueue中存在一条消息,就将其取出,并传递到Handler.handleMessage()中处理;
一个线程只能有一个Loop对象;
使用AsyncTask
AsyncTask是一个抽象类,如果要是用它必须要继承并实现相关方法。
在继承AsyncTask类时,可以选择指定3个泛型参数:
-
Params
在执行AsyncTask时需要传入的参数,可以用于在后台任务中使用;
-
Progress
在后台任务执行时,如果需要在界面上显示当前的进度,则使用这个指定的泛型作为进度单位;
-
Result
当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型;
AsyncTask中几个常用的方法:
-
onPreExecute()
在后台任务开始执行之前调用,用于做初始化处理;
-
doInBackground(Params…)
此方法中的所有代码都会在子线程中运行,应该在此处执行耗时任务;
任务执行完成后可以返回指定类型的运行结果;
此方法中不可进行UI操作,如果要反馈任务执行进度,需要调用publishProgress()方法来完成;
-
onProgressUpdate(Progress…)
在调用publishProgress()方法后会调用此方法,根据传入的值对UI进行更新;
-
onPostExecute(Result)
当后台任务执行完毕之后,会调用此方法,返回的数据会作为参数传入此方法,可以在此处对UI进行操作;
示例代码:
public class MyAsyncTask extends AsyncTask<Object, Integer, Boolean> {
private ProgressDialog progressDialog = null;
private Context mContext = null;
private Integer downloadPercent = 0;
public MyAsyncTask(Context mContext) {
//获取上下文环境
this.mContext = mContext;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
//创建进度条
progressDialog = new ProgressDialog(mContext);
progressDialog.show();
}
@Override
protected Boolean doInBackground(Object... params) {
while (true){
//执行耗时任务
Integer percent = getDownloadPercent();
if (percent > 100){
percent = 100;
}
//更新UI
publishProgress(percent);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
return false;
}
if (downloadPercent >= 100){
break;
}
}
return true;
}
@Override
protected void onProgressUpdate(Integer... values) {
//更新UI
progressDialog.setMax(100);
progressDialog.setProgress(values[0]);
progressDialog.setMessage("下载进度" + values[0] + "%");
}
@Override
protected void onPostExecute(Boolean result) {
progressDialog.dismiss();
//结果处理
if (result){
Toast.makeText(mContext, "下载完成", Toast.LENGTH_LONG).show();
}else {
Toast.makeText(mContext, "下载失败", Toast.LENGTH_LONG).show();
}
}
/**
* 模拟下载进度
* @return 下载进度
*/
private Integer getDownloadPercent(){
Random random = new Random();
downloadPercent += random.nextInt(5);
return downloadPercent;
}
}
测试运行:
//调用异步任务
new MyAsyncTask(this).execute();
Service的基本用法
定义一个Service
创建自定义类,继承Service类:
public class MyFirstService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
在Manifest文件中注册Service:
<!--注册Service-->
<service
android:name=".Services.MyFirstService"
android:enabled="true"
android:exported="true"/>
启动和停止Service
创建测试用Activity:
public class TestServiceActivity extends AppCompatActivity {
private Button btnStartService = null;
private Button btnStopService = null;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test_service_layout);
btnStartService = findViewById(R.id.btn_test_start_service);
btnStopService = findViewById(R.id.btn_test_stop_service);
//点击启动Service
btnStartService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent start = new Intent(TestServiceActivity.this, MyFirstService.class);
startService(start);
}
});
//点击关闭Service
btnStopService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent stop = new Intent(TestServiceActivity.this, MyFirstService.class);
stopService(stop);
}
});
}
}
现在只有当应用程序保持在前台可见状态下,Service才能保证稳定运行,一旦进入后台之后,Service随时都有可能被系统回收。
Activity和Service进行通讯
以下载功能为例,希望可以决定何时开始下载,随时查看下载进度;
创建自定义类,并继承Service:
public class DownloadService extends Service {
private final String TAG = "下载服务";
private DownloadBinder binder = new DownloadBinder();
private Integer downloadPercent = 0;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return binder;
}
public class DownloadBinder extends Binder{
public void startDownload(){
Log.i(TAG, "开始下载");
Random random = new Random();
for (int i = 0; i < random.nextInt(51); i++) {
downloadPercent += random.nextInt(3);
}
}
public int getProgress(){
Log.i(TAG, "获取下载进度:" + downloadPercent + "%");
return downloadPercent;
}
}
}
在Manifest中注册Service:
<service
android:name=".Services.DownloadService"
android:enabled="true"
android:exported="true"/>
调用测试Service:
public class TestServiceActivity extends AppCompatActivity {
private final String TAG = "测试Service的Activity";
private boolean isBindingService = false;
private DownloadService.DownloadBinder binder = null;
private Button btnBindService = null;
private Button btnUnbindService = null;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
binder = (DownloadService.DownloadBinder)iBinder;
binder.startDownload();
binder.getProgress();
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
Log.i(TAG, "Activity解绑了Service");
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test_service_layout);
btnBindService = findViewById(R.id.btn_test_bind_service);
btnUnbindService = findViewById(R.id.btn_test_unbind_service);
//绑定服务
btnBindService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent bind = new Intent(TestServiceActivity.this, DownloadService.class);
bindService(bind, connection, Context.BIND_AUTO_CREATE);
isBindingService = true;
}
});
//解绑服务
btnUnbindService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (isBindingService){
unbindService(connection);
}
}
});
}
}
如果没有绑定Service,直接调用解绑Service,会抛出Service not registered的错误。
Service的生命周期
启动Service有两种方式,所以生命周期略有不同:
- 使用Context.startService()方法启动
- 如果之前没有创建过该Service,那么会首先调用onCreate()
- 调用onStartCommand()
- 保持运行状态
- 调用Context.stopService()方法,或者Service.stopSelf(),或者被系统回收,Service就会停止
- 回调onDestroy()方法
- 使用Context.bindService()方法启动
- 如果之前没有创建过该Service,那么会首先调用onCreate()
- 调用onBind()方法
- 保持运行状态
- 调用Context.unbindService()方法,Service停止
- 回调onDestroy()方法
如果此时某个Service在调用startService()的同时,又调用了bindService(),如果要停止该Service就需要调用stopService()和unbindService()两个方法。
Service更多技巧
使用前台Service
前台Service与后台Service最大的不同就是,前台Service一直会有一个运行的图标在系统的状态栏显示,下拉状态栏后有更详细的信息,类似于通知的样式。
创建自定义类继承Service类:
public class FrontService extends Service {
private String uuid = UUID.randomUUID().toString();
@Override
public void onCreate() {
super.onCreate();
//创建一个通知
NotificationManager manager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel = new NotificationChannel(uuid,"my_front_service", NotificationManager.IMPORTANCE_DEFAULT);
manager.createNotificationChannel(channel);
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
Notification notification = new NotificationCompat.Builder(this, uuid)
.setContentTitle("这是标题")
.setContentText("这是内容")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_background))
.setContentIntent(pendingIntent)
.build();
//启动前台Service
startForeground(1, notification);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
在Manifest中注册Service:
<service
android:name=".Services.FrontService"
android:exported="true"
android:enabled="true"/>
获取前台Service权限:
<!--前台Service权限-->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
像启动普通Service一样启动即可,调用测试:
//启动前台服务
btnStartFrontService = findViewById(R.id.btn_test_start_front_service);
btnStartFrontService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent start = new Intent(TestServiceActivity.this, FrontService.class);
startService(start);
}
});
使用IntentService
Service的代码默认都是在主线程里运行的,如果直接在Service里进行耗时操作,很容易出现ANR。
使用IntentService可以更加简单地创建一个异步的并且会自动停止的Service。
在执行完onHandleIntent之后,就会被销毁。
创建自定义类继承IntentService,需要有一个无参的构造方法:
public class MyIntentService extends IntentService {
private final String TAG = "这是IntentService";
private final static String defaultName = "MyIntentService";
public MyIntentService() {
super(defaultName);
}
public MyIntentService(String name) {
super(name == null ? defaultName : name);
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
Log.i(TAG, "IntentService所属前线程为" + Thread.currentThread().getName());
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "IntentService被销毁");
}
}
在Manifest中注册Service:
<service
android:name=".Services.MyIntentService"
android:enabled="true"
android:exported="true"/>
像启动普通Service一样启动IntentService:
//启动IntentService
btnStartIntentService = findViewById(R.id.btn_test_start_intent_service);
btnStartIntentService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.i(TAG, "启动IntentService的线程为" + Thread.currentThread().getName());
Intent intent = new Intent(TestServiceActivity.this, MyIntentService.class);
startService(intent);
}
});