安卓多线程
先从线程开始学起
线程的基本用法
- 用Thread
跟java的用法一样,比如说,定义一个线程只需要新建一个类继承自Thread,然后重写父类的run()方法,并在里面编写耗时逻辑即可。
class MyThread extends Thread{
@Override
public void run() {
//处理具体的逻辑
}
}
启动就更简单了,只需要new出线程的实例,然后调用它的start()方法
new MyThread().start();
- 用Runnable接口
class MyThread implements Thread{
@Override
public void run() {
//处理具体的逻辑
}
}
使用这种写法,那么启动线程的方法就会改变
MyThread mythread=new MyThread();
new Thread(mythread).start();/
可以看到,Thread构造函数接收了Runnable参数,而我们new出的MyThread正是一个实现了Runnable接口的对象,所以可以将他传入Thread的构造函数中,然后调用start()方法。
- 也可以使用匿名类的方式
new Thread(new Runnable() {
@Override
public void run() {
}
}).start();
在子线程中更新UI
在安卓中,如果想更新程序的组件,则必须在主线程进行,否则将出现异常,下面我用一个实例来演示一遍。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/change_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="改变文字"/>
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="你好世界"
android:textSize="20sp"/>
</RelativeLayout>
public class MainActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView=(TextView)findViewById(R.id.text);
Button button=(Button)findViewById(R.id.change_text);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
textView.setText("你也好");
}
}).start();
}
});
}
}
好了,我们运行…
接着去看错误日志,可以看出是在子线程进行ui所导致的错误。
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
但是当我们必须在子线程里去执行一些耗时任务,然后根据任务的执行结果来更新相应的ui控件,怎么办呢?
安卓很好的为我们提供了一套异步消息处理机制,完美解决在子线程中进行ui操作的问题。
使用方法
修改MainActivity的代码
public class MainActivity extends AppCompatActivity {
private static final int UPDATE_TEXT=1;//定义一个常量
private TextView textView;
private Handler handler=new Handler(){//新增一个handler对象
@Override
public void handleMessage(Message msg) {//重写handleMessage方法,对message进行处理
if (msg.what==UPDATE_TEXT){//如果message的What字段等于UPDATE_TEXT,就执行
//在这里进行ui操作
textView.setText("你也好");
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView=(TextView)findViewById(R.id.text);
Button button=(Button)findViewById(R.id.change_text);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
Message message=new Message();//创建message对象
message.what=UPDATE_TEXT;//将message的what字段的值改成UPDATE_TEXT
handler.sendMessage(message);//调用sendMessage()方法将这条message发送出去,然后Handler就会收到消息并执行,注意此时handlerMessafe()方法就是在主线程中运行的,所以我们可以放心的在这里进行ui操作
}
}).start();
}
});
}
}
Handler消息处理机制
主要由四个部分组成:Message,Handler,MessageQueue和Looper.
- Message
Message是在线程之间传递的信息,他可以在内部携带少量的信息,用于在不同线程之间交换数据,可以使用what字段,也可以使用arg1和arg2字段携带一些整型数据,使用obj字段携带object字段。
- Handler
Handler就是处理者的意思,主要用于发送和处理信息的。发送消息一般是使用Handler的sendMessage()方法,而发送的消息会传递到Handler的handlerMessage()方法中。
- MessageQueue
MessageQueue是消息队列的意思,它主要用于存放所有通过Handler发送的消息。等待被处理,每个线程中只会有一个MessageQueue对象。
- Looper
Looper是每个线程中的MessageQueue的管家,调用Looper的loop()方法,就会进入到无限循环中,然后每当发现一个MessageQueue中存在一个消息,就会将它取出,并传递到Handler的HandlerMessage()方法中。每个线程只有一个Loop对象。
而runOnUiThread()方法其实就是一个异步消息处理机制的接口封装。
- 用runOnUiThread直接更新ui
runOnUiThread(new Runnable() {
@Override
public void run() {
}
});
使用AsyncTask
为了我i们在子线程中对UI进行操作,安卓还提供了另外一个工具,借助AsyncTask,即使你对异步处理消息机制完全不了解,也可以十分简单的从子线程切换到主线程。
首先我们来看AsyncTask的基本用法,用于AsyncTask是一个抽象类,所以如果我们想使用他,必须要创建一个子类去继承他,在继承时我们可以为AsyncTask类指定3个泛型整数,
- params。在执行AsyncTask时,需要传入的参数,可用于在后台任务中使用。
- progress。后台任务执行时,如果需要对任务在界面显示当前的进度,则使用这里的指定的泛型作为进度单位
- result。当任务执行完毕时,如果需要对结果进行返回。则使用这里指定的泛型作为返回值类型
class DownloadTask extends AsyncTask<Void, Integer,Boolean>{
//这里我们把第一个泛型参数设置成void,表示在执行AsyncTask的时候不需要传入参数给后台服务
//第二个人泛型参数指定为Integer,表示使用整型数据来作为进度显示单位
//第三个泛型指定为Boolean,则表示使用布尔型数据来反馈执行结果
}
现在的AsyncTask还是一个空任务,需要重写几个方法才能完成对任务的定制。经常重写的有以下四个
onPreExecute()
//这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示进度条对话框等
onPostExecute(Boolean aBoolean)
/**这个方法所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。任务一旦完成就可以通过return语句来将任务的执行结果返回。
如果AsyncTask的第三个泛型参数为void,就可以不返回任务执行结果。
注意:在这个方法中是不可以进行ui操作的,如果需要更新ui元素,比如说反馈任务的执行进度,可以调用publishProgress(Progress..)来完成**/
onProgressUpdate(Integer... values)
/**
当在后台任务中调用了publishProgress(Progress...)方法后,onProgressUpdata(Progress..)方法就会很快被调用,
该方法中携带的参数就是在后台任务中传递过来的。
在这个方法中可以UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新
**/
doInBackground(Void... voids)
/**
当后台任务执行完毕时并通过return语句进行返回时,
这个方法就很快被调用。返回的数据会作为参数传递到此方法中,
可以利用返回的数据进行一些ui操作,比如说提醒任务的结果,以及关闭对话框等
**/
比如这个
class DownloadTask extends AsyncTask<Void, Integer,Boolean>{
@Override
protected void onPreExecute() {
super.onPreExecute();
progressDialog.show;//显示进度对话框
}
@Override
protected Boolean doInBackground(Void... voids) {
while (true){
int downloadpercent = doDownload;//这是一个虚构的方法
publishProgress(downloadpercent);
if (downloadpercent>=100){
break;
}
}
return true;
}
@Override
protected void onProgressUpdate(Integer... values) {
//在这里更新下载进度
progressDialog.setMessage("Dowmload"+values[0]+"%");
}
@Override
protected void onPostExecute(Boolean aBoolean) {
//关闭进度提示框
progressDialog.dismiss();
//提示下载结果
if (aBoolean){
Toast.makeText(MainActivity.this, "下载完成", Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(MainActivity.this, "没", Toast.LENGTH_SHORT).show();
}
}
服务的基本用法
定义一个服务
新建项目,然后右键,New-Service-Service
Exported属性表示允许除了当前程序之外的其他程序访问这个服务。Enabled属性表示是否启用这个服务。
然后重写一些方法。
@Override
public void onCreate() {//在服务创建时调用
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//每次服务启动时调用
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {//销毁时调用
super.onDestroy();
}
注意:
每一个服务都要在AndroidManifext.xml文件中注册才可以
<service
android:name=".MyService"
android:enabled="true"
android:exported="true"></service>
启动和停止服务
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/start_service"
android:text="启动Service"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/stop_service"
android:text="停止Service"/>
</LinearLayout>
MainActivity中的代码
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button start=findViewById(R.id.start_service);
Button stop=findViewById(R.id.stop_service);
start.setOnClickListener(this);
stop.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.start_service:
Intent intent = new Intent(this,MyService.class);
startService(intent);//启动服务
break;
case R.id.stop_service:
Intent intent1=new Intent(this,MyService.class);
stopService(intent1);//停止服务
break;
default:
break;
}
}
}
Service中代码
public class MyService extends Service {
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onCreate() {//在服务创建时调用
super.onCreate();
Log.d("MyService", "创建服务 ");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {//每次服务启动时调用
Log.d("MyService", "启动服务");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {//销毁时调用
super.onDestroy();
Log.d("MyService", "停止服务 ");
}
}
运行成功
活动和服务进行通信
比如说我们希望在service里提供一个下载功能,然后活动中可以决定何时开始下载,以及随时查看下载进度。实现这个功能的思路是创建一个专门的Binder对象来对下载功能进行管理。修改MyService的代码
public class MyService extends Service {
private DownloadBinder mBiner=new DownloadBinder();
class DownloadBinder extends Binder{
public void startDownload(){
Log.d("MyService", "开始下载 ");
}
public int getProgress(){
Log.d("MyService", "获取下载进度 ");
return 0;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBiner;//返回mBiner实例
}
......
}
修改activity_mian中的代码添加绑定服务和取消服务的按钮
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/bind_service"
android:text="绑定服务"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/unbind_service"
android:text="取消绑定服务"/>
MainActivity的方法
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private MyService.DownloadBinder downloadBinder;
private ServiceConnection connection=new ServiceConnection() {//创建ServiceConnection的实例。
@Override
public void onServiceConnected(ComponentName name, IBinder service) {//绑定成功时调用
downloadBinder= (MyService.DownloadBinder) service;//向下转型。得到DownloadBinder的实例
downloadBinder.startDownload();
downloadBinder.getProgress();
}
@Override
public void onServiceDisconnected(ComponentName name) {//绑定结束时调用
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
...
Button bind=findViewById(R.id.bind_service);
Button unbind=findViewById(R.id.unbind_service);
bind.setOnClickListener(this);
unbind.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
.....
case R.id.bind_service:
Intent intent2 = new Intent(this,MyService.class);
bindService(intent2,connection,BIND_AUTO_CREATE);
//绑定服务 第一个参数是intent对象,第二个是ServiceConnection实例,第三个是则是标志位,这里的表示在活动和服务进行绑定后自动创建服务。
break;
case R.id.unbind_service:
Intent intent3=new Intent(this,MyService.class);
unbindService(connection);//解绑
break;
default:
break;
}
}
}
使用前台服务
<!--android 9.0上使用前台服务,需要添加权限-->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
修改MyService的代码
public class MyService extends Service {
......
@Override
public void onCreate() {//在服务创建时调用
super.onCreate();
Log.d("MyService", "创建服务 ");
/**
* 下面的是通知栏
* 不懂的可以去这里查看
*
*/
String id = "my_channel_01";
String name="我是渠道名字";
NotificationManager notificationManager= (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
Notification notification=null;
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
NotificationChannel mchannel=new NotificationChannel(id,name,NotificationManager.IMPORTANCE_HIGH);
notificationManager.createNotificationChannel(mchannel);
}
NotificationCompat.Builder builder=new NotificationCompat.Builder(this,id);
Intent intent=new Intent(this, MainActivity.class);
PendingIntent pi= PendingIntent.getActivity(this,0,intent,0);
builder.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("通知标题")
.setContentText("通知内容")
.setAutoCancel(true)//点击通知是否可以移除
.setContentIntent(pi);//跳转
startForeground(1, builder.build());
super.onCreate();
}
.......
}
使用IntentService
因为Service中几个方法的回调都是在主线程中,如果使用Service执行特别耗时的操作,建议单独新建线程去操作,避免阻塞主线程(UI线程)。IntentService在内部帮我们新建的线程,执行完毕任务后会自动关闭,无需手动结束它。
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService");//调用父类的有参构造函数
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
//打印当前线程的id
Log.d("MyIntentService", "thread id is"+Thread.currentThread().getId());
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d("MyIntentService", "thread is stop");
}
}
在MainActivity中
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
........
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.start_service:
Log.d("MainActivity", "thread id is"+Thread.currentThread().getId());
Intent intent = new Intent(this,MyIntentService.class);
startService(intent);//启动服务
break;
default:
break;
}
}
}
log日志
D/MainActivity: thread id is2
D/MyIntentService: thread id is307
D/MyIntentService: thread is stop
可以看到,不仅MyIntentService和MainActivity的线程id不同,而且onDestroy()方法也执行了,说明MyIntentService在运行完后确实自动停止了。