Android 服务学习(一)——多线程编程

  • 服务的运行不依赖于任何的用户界面,因而非常适合于执行那些不需要和用户交互而且长期运行的任务
  • 服务不会自动开启线程,所有的代码都默认运行在主线程当中
  • 我们需要在服务内部手动创建子线程,并在这里执行具体任务,否则会出现主线程堵塞的情况

一、线程的基本用法

只需要一个类继承Thread,然后重写父类的run()就行:

class MyThread extends Thread{
	@Override
	public void run(){
		// 具体逻辑
	}
}

怎么启动这个线程呢?new出实例,.start()就行

new MyThread().start()

还可以使用继承的方式创建线程:

class MyThread implements Runnable{
	@Override
	public void run(){
		// 具体逻辑
	}
}

继承的方式启动线程:

MyThread myThread = new Thread();
new Thread(myThread).start();

或者一口气用匿名类的方式创建启动:

new Thread(new Runnable(){
	@Override
	public void run(){
		// 具体逻辑
	}
}).start();

二、在子线程中更新UI

新建一个空项目 day17_AndroidThreadTest

想要更新应用程序里的UI元素,必须在主线程中进行,否则就会出现异常。

1、一个崩溃的例子

主布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/change_text"
        android:text="改变文本"/>
    
    <TextView
    	android:id="@+id/some_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 implements View.OnClickListener {
    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button_change = findViewById(R.id.change_text);
        textView = findViewById(R.id.some_text);
        button_change.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.change_text:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        textView.setText("咋地啦?");
                    }
                }).start();
                break;
            default:
                break;
        }
    }
}

点击按钮就会奔溃:
在这里插入图片描述

2、更正:异步消息机制

可以使用一部消息机制解决这个子线程不能修改UI的问题,示例:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private TextView textView;
    public static final int UPDATE_TEXT = 1;

    @SuppressLint("HandlerLeak")
    private Handler handler = new Handler() {
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case UPDATE_TEXT:
                    // 这里进行UI操作
                    textView.setText("咋滴啦?");
                    break;
                default:
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button_change = findViewById(R.id.change_text);
        textView = findViewById(R.id.some_text);
        button_change.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.change_text:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Message message = new Message();
                        message.what = UPDATE_TEXT;
                        handler.sendMessage(message); //发送Message对象
                    }
                }).start();
                break;
            default:
                break;
        }
    }
}
  1. 定义一个整型常量UPDATE_TEXT表示更新TextView这个动作
  2. 新增一个Handler对象,并重写了父类的handleMessage()方法,在这里对Message处理
  3. 如果发现Message.what 等于 UPDATE_TEXT,就进行更新TextView
  4. 在子线程中未直接更新UI,而是创建了一个Message(android.os.Message)对象,并指定了what字段的值
  5. 调用Handler.sendMessage()Message对象发送出去,Handler收到后会调用handleMessage()对其处理,此时就切换到主线程了

在这里插入图片描述

3、异步消息机制的原理

Android 的异步消息处理主要由4部分组成:

a、Message

在线程中传递的消息,用于在不同线程间交换数据。其常用字段有whatarg1agr2obj

b、Handler

顾名思义,“处理者”。用于发送sendMessage()和处理handleMessage()消息

c、MessageQueue

消息队列,用于存放所有通过Handler发送的消息,每个线程中只会有一个MessageQueue对象

d、Looper

是每个线程中MessageQueue的管家,调用Looper.loop()会进入消息循环中,将存在消息队列的消息取出一条,传递到handleMessage()方法中。每个线程只会有一个Looper对象

说白了,异步消息机制的原理就是:子线程发个通知让主线程更新UI
同理,runOnUiThread()也是这个原理

4、使用AsyncTask

为了更方便的在子线程中对UI进行操作,Android还提供了一个工具——AsyncTask,原理和上文了一样,不过封装起来很好用

使用方法:
一、创建一个子类去继承它,继承时可以为Async类指定三个泛型参数,用途如下:

  • Params。执行 AsyncTask时需要传入的参数,可用于后台任务
  • Progress。后台任务执行时,需要在界面上显示任务进度,使用这里的泛型作为进度单位
  • Result。任务执行完毕,对需要的结果进行返回,使用这里的泛型作为返回值类型
class DownloadTask extends AsyncTask<Void, Integer, Boolean>{
	...
}

第一个参数为Void,表示执行AsyncTask的时候不需传入参数给后台任务;
第二个参数为Integer,表示使用整型数据来作为进度显示单位
第三个参数为Boolean,表示使用布尔参数来反馈执行结果

二、接着需要重写AsyncTask的几个任务才能完成对任务的定制,经常重写的4个方法:

  1. onPreExecute()
    在后台任务开始之前调用,用于完成界面上的初始化操作,比如显示进度条对话框
  2. doInBackground(Params...)
    这个方法中的代码都会在子线程中运行,在这里处理耗时任务。任务一旦完成就可以通过return语句将执行结果返回,如果第三个泛型参数指定是Void,就可以不返回执行结果。
    这个方法中不可以执行UI操作,要执行UI相关操作,可以调用publishProgress(Progress...)方法
  3. onProgressUpdate(Progress...)
    当后台调用了 publishProgress(progress...),该方法很快就会被调用,其携带的参数就是在后台任务中传递过来的。这个方法可以对UI进行操作
  4. onPostExecute(Result)
    当后台任务执行完毕并return后,这个方法就会被调用。返回的数据会传递到此方法中,可以对UI进行操作。

举个例子:

public class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
    @Override
    protected void onPreExecute() {
        progressDialog.show(); // 显示进度条
    }

    @Override
    protected Boolean doInBackground(Void... voids) {
        try {
            while (true){
                int donloadPercent = download(); // 虚构的方法,返回下载进度
                publishProgress(donloadPercent);
                if(donloadPercent>=100){
                    break;
                }
            }
        }catch (Exception e){
            return false;
        }
        return true;
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        // 更新下载进度
        progressDialog.setMessage("Downloaded " + values[0] + "%");
    }

    @Override
    protected void onPostExecute(Boolean aBoolean) {
        progressDialog.dismiss(); // 关闭进度条
        if (aBoolean){
            Toast.makeText(context, "下载成功", Toast.LENGTH_SHORT).show();
        }else {
            Toast.makeText(context, "下载失败", Toast.LENGTH_SHORT).show();
        }
    }
}

简单来说,doInBackground()执行耗时任务,onProgressUpdate()执行UI操作,onPostExecute()任务收尾

启动这个任务:

new DownloadTask().execute();
发布了166 篇原创文章 · 获赞 14 · 访问量 9088

猜你喜欢

转载自blog.csdn.net/qq_41205771/article/details/104356872