二、Android多线程编程
1.线程的基本用法
2.在子线程中更新UI
3.解析异步消息处理机制
Android中异步消息处理主要由4个部分组成:Message、Handler、MessageQueue和Looper。
Message
Message是在线程之间传递的消息,可以在内部携带少量的信息,用于在不同线程之间传递数据。
一般有wha字段、用来携带整形数据的args1、args2和携带Object对象的obj字段。
Handler
Handler是处理者的意思,主要用于发送和处理消息。发送消息一般是使用Handler的sendessage()方法、post()方法等,发出的消息最终会传递到Handler的handleMessage()方法中。
MessageQueue
MessageQueue是消息队列的意思,主要用于存放所有通过Handler发送的消息。这部分消息会一直存在于消息队列中,的呢古代被处理。每个线程只有一个MessageQueue对象。
Looper
Looper是每个线程中的MessageQueue的管家,调用Looper的loop()方法后,就会进入一个ie无限循环中,然后每当发现MessageQueue中存在一条消息时,就会将它取出,并传递到Handler的handleMessage()方法中。每个线程只会有一个Looper对象。
异步消息处理的整个流程:
首先需要在主线程中创建一个Handler对象,并重写handleMessage()方法。当子线程中需要进行UI操作时,就创建一个Message对象,并通过Handler将这条消息发送出去。之后这条消息会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue中取出待处理消息,最后分发回Handler的handleMessage()方法中。如果Handler的构造函数中我们传入了Looper.getMainLooper(),handleMessaeg()方法中的代码就会在主线程中运行。如下所示:
三、Service的基本用法
1.定义一个Service
new->Service->Service,会弹出如下窗口:
Exported属性表示是否将这个Service暴露给外部其他程序访问,Enabled属性表示是否启用这个Service 。
每个Service中最常用的3个方法:
onCreate()方法会在Service创建的时候调用,onStartCommand()方法会在每次Service启动的时候调用,onDestroy()方法会在Service销毁的时候调用。如下所示:
public class BaseService extends Service {
public BaseService() {
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
}
2.启动和停止Service
Context类中定义了startService()方法和stopService()方法,直接在Activity中调用这两个方法,就可以启动和停止Service 。如下所示,定义了两个按钮分别设置启动和停止:
final Intent intent1 = new Intent(this,BaseService.class);
mStartServiceBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startService(intent1);
}
});
mStopServiceBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
stopService(intent1);
}
});
注:onCreate()方法和onStartCommand()方法的区别:
onCreate()方法是在Service第一次创建的时候调用的,而onStartCommand()方法则在每次启动Service的时候都会调用。为了验证这个说法,可以连续多点击几次startService的按钮,加入日志打印即可看出。
3.Activity和Service进行通信
虽然Service是在Activity里启动的,但是在启动了Service之后,Acitivity与Service基本就没有什么关系了,调用Service的onCreate()方法和onStartCommand()方法后,Service就会一直处于运行状态,但具体运行的是什么逻辑,Activity就无法控制了。
比如说,如果希望在Service里提供一个下载功能,然后在Activity中决定何时开始下载,并可以岁时查看下载进度。实现这个功能的思路是创建一个专门的Binder对象来对下载功能进行管理,如下所示:
public class BaseService extends Service {
private Binder mBinder = new DownloadBinder();
static class DownloadBinder extends Binder {
public void startDownload() {
}
public int getProgress() {
return 0;
}
}
}
然后在Activity中设置两个按钮分别用于绑定和取消绑定Service,当一个Activity和Service绑定了之后,就可以调用Service里的binder提供的方法了,如下所示:
public class AndroidThreadTestActivity extends AppCompatActivity {
private Button mStartServiceBtn;
private Button mStopServiceBtn;
private BaseService.DownloadBinder mDownloadBinder = new BaseService.DownloadBinder();
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mDownloadBinder.startDownload();
mDownloadBinder.getProgress();
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thread);
mStartServiceBtn = findViewById(R.id.startServiceBtn);
mStopServiceBtn = findViewById(R.id.stopServiceBtn);
final Intent intent1 = new Intent(this,BaseService.class);
mStartServiceBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
bindService(intent1,connection, Context.BIND_AUTO_CREATE);
}
});
mStopServiceBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
unbindService(connection);
}
});
}
}
onServiceConnected()方法会在Activity与Service成功绑定的时候调用,而onServiceDsiconnected()方法只有在Service的创建进程崩溃或者被杀掉的时候才会调用。
通过调用onServiceConnected()方法,可以获取DownloadBinder实例,然后调用任何public方法。
bindService()方法将Acitivity和Service进行绑定,该方法接收3个参数,第一个参数是Intent对象,第二个对象是ServiceConnection实例,第三个参数是标志位,BIND_AUTO_CREATE表示在Activity和Service进行绑定后自动创建Service,调用onCreate()方法,但是onStartCommand()方法不会执行。
四、Service的生命周期
1.启动Service
一旦在项目的任何位置调用了Context的startService()方法,相应的Service就会自动启动,并回调onStartCommand()方法。如果这个Service之前还没有创建国,onCreate()方法会先于onStartCommand()方法执行。Service启动了之后会一直保持运行状态,直到stopService()或stopSelf()方法被调用,或者被系统回收。
注:理论上来说,每调用一次startService()方法,onStartCommand()就会执行一次,但实际上每个Service只会存在一个实例。所以不管你调用了多少次startService()方法,只需调用一次stopService()或stopSelf()方法,Service就会停止。
2.建立连接
另外,还可以调用Context的bindService()来获取一个Service的持久连接,这时就会回调Service中的onBind()方法。同样,如果这个Service还没有创建过,onCreae()方法会先于onBind()方法执行。之后,可以获取到onBind()方法里返回的IBinder对象的实例,这样就可以和Service进行通信了。只要连接没有断开,Service就会一直保持运行状态,直到被系统回收。
3.销毁
当调用了startService()方法后,再去调用stopService()方法。这时Service中的onDestroy()方法就会执行,表示Service已经销毁了。同样的,当调用了bindService()方法后,再去调用unbindService()方法,onDestroy()方法也会执行。
注:如果对一个Service既调用了startService()方法,又调用了bindService()方法,根据Android系统的机制,一个Service只要被启动或者被绑定了之后,就会处于运行状态,必须要让以上两种条件都不满足,Service才能被销毁。所以,这种情况必须同时调用stopService()和unbindService()方法,onDestroy()方法才会执行。
五、Service的更多技巧
1.使用前台Service
从Android8.0系统开始,只有当应用保持在前台可见状态的情况下,Service才能保证稳定运行,一旦应用进入后台之后,Service随时都有可能被系统回收。如果希望Service能够一直保持运行状态,可以考虑使用前台Service。前台Service和普通Service最大的区别在于,一直会有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,类似于通知的效果。
由于状态栏中一直有一个正在运行的图标,相当于我们的应用以另外一种形式保持在前台可见状态,所以系统不会倾向于回收前台Service。另外,用户也可以通过下拉状态栏清楚地指导当前什么应用正在与兴宁,因此也不存在某些恶意应用长期在后台偷偷占用收集资源的情况。
使用前台Servcie,关键是调用了startForeground()方法,让Service变成一个前台Service,并在系统状态栏显示出来。这个方法接受两个参数:第一个参数是通知的id,类似notify方法的第一个参数;第二个参数是构建的Notification对象。如下所示:
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG,"onCreate executed");
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel = new NotificationChannel("my-service","前台Service通知", NotificationManager.IMPORTANCE_DEFAULT);
manager.createNotificationChannel(channel);
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);
Notification notification = new NotificationCompat.Builder(this,"my_service")
.setContentTitle("This is a content title")
.setContentText("This is content text")
.setContentIntent(pi)
.build();
startForeground(1,notification);
}
从Android9.0系统开始,使用前台Servcie必须在AndroidManifest.xml文件中进行权限声明。如下所示:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
以前台Service的模式启动服务后,在系统状态栏会显示一个通知图表,即使退出应用程序,Service也会一直处于运行状态,而且不用担心会被系统回收。Service对应的通知也会一直显示在状态栏上面。如果不希望程序一直运行,也可以选择手动杀掉应用,这样Service就会跟着一起停止运行了。
2.使用IntentService
Service中的代码都是默认运行主线程当中的,如果直接在Service里处理一些耗时的逻辑,就很容易出现ANR的情况。
所以一般情况下,应该在Service的每个具体的方法里开启一个子线程,然后在这里处理那些耗时的逻辑。但是这种Service一旦启动,就会一直处于运行状态,必须调用stopService()或stopSelf()反法,或者被系统回收,Service才会停止。实现一个Service在执行完毕后自动停止的功能,如下所示:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
// TODO: 21-9-25 处理具体的逻辑
stopSelf();
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
上面这种写法并不复杂,但是有些时候总会忘记开启线程,或者忘记调用stopSelf()方法。为了可以简单地创建一个异步的、会自动停止的Service,Andriod专门提供了一个IntentService类,这个类就很好地解决了这些问题。如下所示:
public class MyIntentService extends IntentService {
@Override
protected void onHandleIntent(Intent intent) {
//打印当前线程的id
Log.d(TAG,"Thread id is: " + Thread.currentThread().getName());
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG,"onDestroy executed");
}
}
为了验证这一说法,可以在UI设计一个按钮,用于启动IntentService,并打印线程id,如下所示:
mStartIntentServiceBtn = findViewById(R.id.startIntentServiceBtn);
final Intent intent = new Intent(this, MyIntentService.class);
mStartIntentServiceBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.d(TAG, "Thread id is: " + Thread.currentThread().getName());
startService(intent);
}
});
在xml布局文件中设置:
<Button
android:id="@+id/startIntentServiceBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start IntentService"/>
注意别忘了在AndroidManifest.xml中声明,一般来说,使用AndroidStudio来创建一个Servcie,声明都会自动创建:
<service
android:name=".MyIntentService"
android:exported="false"></service>
最终的验证这里就不展示了,可以想象,IntentService的线程name和主线程的肯定不一样,并且,IntentService的onDestroy()方法也会触发。