安卓的Handler机制、AsyncTask 、Toast和事件监听机制

本文详细介绍了安卓开发中的Handler机制,包括其作用、相关名词和使用方法,强调了其在多线程环境下的线程安全。此外,还讲解了AsyncTask的异步任务处理,Toast的使用,事件监听机制的两种方式,以及Notification和AlertDialog的用法。还涵盖了文件存储和读写以及SharedPreference保存用户数据的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一. Handler 的使用方法

1. Handler 作用

在开发中,我们经常会需要做一些耗时的操作:比如下载图片、打开网页、下载视频等。如果将这些耗时的操作放在主线程(UI线程),长时间的阻塞导致应用ANR。必然应该将这些操作放在子线程中处理,这些操作处理过程中,我们需要更新UI界面以告知用户现在具体的进度、状态等信息。

所以:在多线程的应用场景中,将工作线程中需更新UI的操作信息 传递到 UI主线程,从而实现 工作线程对UI的更新处理,最终实现异步消息的处理

示意图

但是,多个线程并发执行UI 主线程,会导致数据更新异常,使用 Handler 的作用时: 多个线程并发更新UI的同时 保证线程安全

 

2. Handler 相关的名词

Handler 作为主线程和 工作线程的一个媒介

HandlerMessageMessage QueueLooper

示意图

3. 使用方法

在子线程中Handler将消息发送到MessageQueue中,然后Looper不断的从MessageQueue中读取消息,并调用Handler的dispatchMessage发送消息,最后再Handler来处理消息。为了更好的帮助大家一起理解,我画了一个Handler机制的原理图:

解析Android中Handler机制原理

使用方式分为2 种:使用Handler.sendMessage()、使用Handler.post()

3.1 Handler.sendMessage()使用步骤:

1. 自定义Handler子类(继承Handler类) & 复写handleMessage()方法

@Override

public void handleMessage(Message msg) { ...

switch(msg.what){} // 根据不同工作线程,执行不同操作

// 需执行的UI操作 }

2. 在 UI 主线程中, 创建Handler实例

private Handler mhandler = new mHandler();

3. 在工作线程中 , 创建所需的消息对象

Message msg = Message.obtain(); // 实例化消息对象 ,推荐使用这种方法。而不是 Message msg = new Message();

msg.what = 1; //

消息标识 msg.obj = "AA"; // 消息内容存放

4. 在工作线程中, 通过Handler发送消息到消息队列中

mHandler.sendMessage(msg);

5. 在监听事件中,开启工作线程

例子:模拟点击按钮,进行下载的小栗子


import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;


public class MainActivity extends AppCompatActivity implements Button.OnClickListener {

    private TextView statusTextView = null;

    private Handler uiHandler;

    class Mhandler extends Handler {

        //步骤1:(自定义)新创建Handler子类(继承Handler类) & 复写handleMessage()方法
        @Override
        public void handleMessage(Message msg) {
            // 工作线程发过来的信息
            switch (msg.what){
                case 1:
                    System.out.println("handleMessage thread id " + Thread.currentThread().getId());
                    System.out.println("msg.arg1:" + msg.arg1);
                    System.out.println("msg.arg2:" + msg.arg2);
                    MainActivity.this.statusTextView.setText("文件下载完成");

                    // 获取传入的参数
                    Bundle bundle = msg.getData();
                    String value = bundle.getString("list");
                    textView.setText(value);

                    break;
            }
        }
    };

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

        // 步骤2:在主线程中创建Handler实例
        uiHandler = new Handler()
        statusTextView = (TextView)findViewById(R.id.statusTextView);
        Button btnDownload = (Button)findViewById(R.id.btnDownload);
        btnDownload.setOnClickListener(this);

        System.out.println("Main thread id " + Thread.currentThread().getId());
    }

    @Override
    public void onClick(View v) {
        DownloadThread downloadThread = new DownloadThread();
        downloadThread.start();
    }


    // 工作线程
    class DownloadThread extends Thread{
        @Override
        public void run() {
            try{
                System.out.println("DownloadThread id " + Thread.currentThread().getId());
                System.out.println("开始下载文件");
                //此处让线程DownloadThread休眠5秒中,模拟文件的耗时过程
                Thread.sleep(5000);
                System.out.println("文件下载完成");
                //文件下载完成后更新UI

                // 步骤3:在工作线程中 创建所需的消息对象
                Message msg = new Message();
                 
                //msg = Message.obtain(); // 最好使用这种方法


                //what是我们自定义的一个Message的识别码,以便于在Handler的handleMessage方法中根据what识别
                //出不同的Message,以便我们做出不同的处理操作
                msg.what = 1;

                //我们可以通过arg1和arg2给Message传入简单的数据
                msg.arg1 = 123;
                msg.arg2 = 321;
                //我们也可以通过给obj赋值Object类型传递向Message传入任意数据
                //msg.obj = null;
                //我们还可以通过setData方法和getData方法向Message中写入和读取Bundle类型的数据
                //msg.setData(null);
                //Bundle data = msg.getData();

            // 传入更多的参数
            Bundle bundle = new Bundle();
            bundle.putString("list", "this is mesage");
            bundle.putString("list2", "this is list2");
            msg.setData(bundle);

                //步骤4:在工作线程中 通过Handler发送消息到消息队列中
                uiHandler.sendMessage(msg);

            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

3.2 Handler.post()使用方法:较为简单,但是底层还是 调用Handler.sendMessage()

package ispring.com.testhandler;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;


public class MainActivity extends AppCompatActivity  implements Button.OnClickListener {

    private TextView statusTextView = null;

    //uiHandler在主线程中创建,所以自动绑定主线程
    private Handler uiHandler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        statusTextView = (TextView)findViewById(R.id.statusTextView);
        Button btnDownload = (Button)findViewById(R.id.btnDownload);

        btnDownload.setOnClickListener(this);
        System.out.println("Main thread id " + Thread.currentThread().getId());
    }

    @Override
    public void onClick(View v) {
        DownloadThread downloadThread = new DownloadThread();
        downloadThread.start();
    }

    class DownloadThread extends Thread{
        @Override
        public void run() {
            try{
                System.out.println("DownloadThread id " + Thread.currentThread().getId());
                System.out.println("开始下载文件");
                //此处让线程DownloadThread休眠5秒中,模拟文件的耗时过程
                Thread.sleep(5000);
                System.out.println("文件下载完成");

                //文件下载完成后更新UI
                Runnable runnable = new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("Runnable thread id " + Thread.currentThread().getId());
                        MainActivity.this.statusTextView.setText("文件下载完成");
                    }
                };
                // 这里使用 post
                uiHandler.post(runnable);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

二、AsyncTask 异步任务

Android 提供了一个轻量级的用于处理异步任务的类 AsyncTask,对Thread 和 Handler 进行了封装,用于多线程通信。

我们一般是继承 AsyncTask,然后在类中实现异步操作,再将异步执行的进度,反馈给 UI 主线程

AsyncTask 是一个抽象类,一般我们都会定义一个类继承 AsyncTask 然后重写相关方法

AsyncTask<Params, Progress, Result> 三个参数说明:

参数 说明
Params 启动任务执行的是如参数,比如一个网络请求的 URL
Progress 后台任务执行的百分比
Result 后台任务执行完毕后返回的结果

如果不需要一些参数,可以使用 void 代替

需要重写的方法,不能直接调用 

方法 说明
onPreExecute() 在执行后台耗时操作前调用,通常用于一些初始化操作,比如显示进度条
doInBackground(params...) 在 onPreExecute() 方法执行完毕后立即执行,该方法运行于后台,主要负责执行耗时的后台处理工作,可调用 publishProgress(progress) 来更新时时的任务进度
onPostExecute(Result) 在 doInBackground(params...) 执行完毕后,该方法会被 UI 线程调用,后台任务的运行结果将通过该方法传递到 UI 线程,然后展示给用户
onProgressUpdate(progress) 在 publishProgress(progress...) 被调用后,接收publishProgress传来的参数,UI 线程将调用该方法在界面上展示任务的进度,比如更新进度条
onCancelled()

用户取消线程操作的时候调用,也就是在主线程调用 onCancelled() 的时候调用,

doInBackground方法中调用cancel时会触发该方法

可以直接调用的方法

方法 说明
execute 开始执行异步处理任务。params参数对应execute方法的输入参数
executeOnExecutor

以指定线程池模式开始执行任务。THREAD_POOL_EXECUTOR表示异步线程池,

SERIAL_EXECUTOR表示同步线程池。默认是SERIAL_EXECUTOR。

publishProgress 更新进度。该方法只能在doInBackground方法中调用,调用后会触发onProgressUpdate方法。
cancel 取消任务。该方法调用后,doInBackground的处理立即停止,并且接着调用onCancelled方法,而不会调用onPostExecute方法。
get 获取处理结果。
getStatus 获取任务状态。PENDING表示还未执行,RUNNING表示正在执行,FINISHED表示执行完毕
isCancelled 判断该任务是否取消。true表示取消,false表示未取消

 

使用 AsyncTask 几点注意事项

  1. Task 的实例必须在 UI 线程中创建
  2. execute() 方法必须在 UI 线程中调用
  3. 不要手动调用 onPreExecute() 、doInBackground(params...) 、onPostExecuteonCancelled() 这几个方法
  4. Task 只能执行一次,运行多次会出现异常
  5. execute() 开启任务入口,应该在UI 线程中运行  

模拟下载的例子:

1. activity_main.xml ui文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    xmlns:tools="http://schemas.android.com/tools"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:orientation="vertical">  
    <TextView  
        android:id="@+id/txttitle"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content" />

    <!--设置一个进度条,并且设置为水平方向-->  
    <ProgressBar  
        android:layout_width="fill_parent"  
        android:layout_height="wrap_content"  
        android:id="@+id/pgbar"  
        style="?android:attr/progressBarStyleHorizontal"/>  
    <Button  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:id="@+id/btn_update"  
        android:text="开始下载"/>  
</LinearLayout>

2. 创建 Download.java 用于模拟耗时操作

package cn.twle.android.asynctask;

public class Download {
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值