欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android 基础之 Handler

程序员文章站 2022-07-14 16:45:22
...

Android 是单线程模型,但是仅仅靠主线程是不够的,比如在主线程中进行网络请求、下载图片、下载视频等耗时操作时,就会造成阻塞,用户用起来会很不舒服。所以,除了 UI 的更新外,一些耗时的操作需开启子线程来处理。主线程和子线程之间需要数据交换等通信,子线程和子线程之间同样也需要通信。

目前线程中的通信是借助 Handler 实现的,但 Handler 的作用不仅限于线程间通信,还有延时启动 Runnable,有一点需要说明:一个线程 (Thread)对应一个 Looper,一个 Looper 对应一个消息队列 (MessageQueue) ,一个消息队列(MessageQueue )中可以包含多条 Message 消息,一个线程中可以有多个 Handler 。

Handler

Handler 是一个消息分发对象。当创建一个 Handle r时,会与创建他的线程和线程的消息队列绑定,从这时候开始,Handler 会向消息队列发送 Message ,并且在 Message 从消息队列出来时对Message 进行处理。
Handler 类包含如下方法用于发送、处理消息:

  • handleMessage(Message msg):处理消息的方法。
  • obtainMessage():获取消息。
  • sendEmptyMessage(int what):发送空消息。
  • sendMessage(Message msg):立即发送消息。
  • sendMessageDelayed(Message msg, long delayMillis):指定多少毫秒后发送消息。
  • sendEmptyMessageDelayed(int what, long delayMillis):指定多少毫秒后发送空消息。

Message

Handler接收和处理的消息对象。

  • 2个整型数值:轻量级存储int类型的数据。
  • 1个Object:任意对象。
  • replyTo:线程通信时使用。
  • what:用户自定义的消息码,让接收者识别消息。

MessageQueue

Message的队列

  • 采用先进先出的方式管理Message。
  • 每一个线程最多可以拥有一个。

Looper

消息泵,是 MessageQueue 的管理者,会不断从 MessageQueue 中取出消息,并将消息分给对应的 Handler 处理。

  • 每个线程只有一个 Looper。
  • Looper.prepare():为当前线程创建 Looper对象。
  • Looper.myLooper():可以获得当前线程的 Looper对象。
  • Handler:能把消息发送给 MessageQueue,并负责处理 Looper 分给它的消息。

Android 消息机制的运行流程

在子线程执行完耗时操作,当Handler发送消息时,将会调用MessageQueue.enqueueMessage,向消息队列中添加消息。当通过Looper.loop开启循环后,会不断地从线程池中读取消息,即调用MessageQueue.next,然后调用目标Handler(即发送该消息的Handler)的dispatchMessage方法传递消息,然后返回到Handler所在线程,目标Handler收到消息,调用handleMessage方法,接收消息,处理消息。

MessageQueue,Handler和Looper三者之间的关系

每个线程中只能存在一个Looper,Looper是保存在ThreadLocal中的。主线程(UI线程)已经创建了一个Looper,所以在主线程中不需要再创建Looper,但是在其他线程中需要创建Looper。每个线程中可以有多个Handler,即一个Looper可以处理来自多个Handler的消息。 Looper中维护一个MessageQueue,来维护消息队列,消息队列中的Message可以来自不同的Handler。

Handler 如何去实现发送和处理消息

1. 使用 Thread 发送消息

public class MainActivity extends AppCompatActivity {
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == 1001) {
                mTextView.setText("Star");
            }
        }
    };

    private TextView mTextView;
    private Button mButton;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTextView = findViewById(R.id.tv_text);
        mButton = findViewById(R.id.btn_send);

        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(2000);
                        }catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        mHandler.sendEmptyMessage(1001);
                    }
                }).start();
            }
        });
    }
}

2. 使用Handler下载文件并更新进度条

  1. 添加权限
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
  1. 实现代码
public class MainActivity extends AppCompatActivity {

    private static final int DOWNLOAD_FILE_CODE = 100001 ;
    private static final String DOWNLOAD_URL ="http://download.sj.qq.com/upload/connAssitantDownload/upload/MobileAssistant_1.apk" ;
    private static final int DOWNLOAD_FILE_FAILE_CODE = 100002;
    private Button StartDownload;
    private TextView percent;
    private ProgressBar progressBar;
    private Handler handler;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        StartDownload.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //执行下载程序
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        download(DOWNLOAD_URL);
                    }
                }).start();

            }
        });
        handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what){
                    case 100001:
                        //更新进度条
                        progressBar.setProgress((Integer) msg.obj);
                        percent.setText(String.valueOf(msg.arg1)+"%");
                        if (progressBar.getProgress()==100){
                            Toast.makeText(MainActivity.this, "下载完成", Toast.LENGTH_SHORT).show();
                        }
                        break;
                    case 100002:
                        Toast.makeText(MainActivity.this, "下载失败", Toast.LENGTH_SHORT).show();
                        break;

                }
            }
        };
    }

    private void download(String AppUrl) {
        //实例化URL对象
        try {
            URL url = new URL(AppUrl);
            //实例化一个URLConnection对象
            URLConnection conn = url.openConnection();
            //获取下载路径
            String path = Environment.getExternalStorageDirectory()
                    + File.separator + "ZerGen" + File.separator;
            //创建目录
            File PathName = new File(path);
            //如果PathName不存在的话 就创建这个目录
            if(!PathName.exists()){
                PathName.mkdir();
            }
            //有了目录之后,就需要一个文件名
            String ApkName = path+"ZerGen.apk";
            //判断一下这个文件是否已经存在,存在的话就删除它
            File ApkFile = new File(ApkName);
            if(ApkFile.exists()){
                ApkFile.delete();
            }
            //获取文件的总长度
            int ContentLength = conn.getContentLength();
            //获取输入流
            InputStream in = conn.getInputStream();
            byte[] b = new byte[1024];
            int DownloadLength = 0; //用于保存实时下载长度
            int len  =0;
            OutputStream out = new FileOutputStream(ApkName);
            while((len = in.read(b))>-1){
                 out.write(b,0,len);
                 DownloadLength += len;
                //将实时的下载长度传给UI线程
                Message message=handler.obtainMessage();
                message.what =DOWNLOAD_FILE_CODE;
                message.obj = DownloadLength*100/ContentLength;
                message.arg1 = DownloadLength*100/ContentLength;
                handler.sendMessage(message);

            }

        } catch (java.io.IOException e) {
            downloadfail();
            e.printStackTrace();
        }
    }

    private void downloadfail() {
        Message message=handler.obtainMessage();
        message.what =DOWNLOAD_FILE_FAILE_CODE;
        handler.sendMessage(message);
    }

    private void initView() {
        StartDownload = (Button) findViewById(R.id.btn_start);
        percent = (TextView) findViewById(R.id.percent);
        progressBar = (ProgressBar) findViewById(R.id.progressBar);

    }
}

3. 使用 handler实现倒计时功能

public class SplashActivity extends AppCompatActivity {
    private TextView mTime;
    private static int TIME = 5;

    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                    TIME--;
                    mTime.setText(TIME + "s");
                    if (TIME > 0) {
                        Message message = mHandler.obtainMessage(1);
                        mHandler.sendMessageDelayed(message, 1000);      // send message
                    } else {
                        //跳转到主界面
                        goHome();
                    }
            }
            super.handleMessage(msg);
        }
    };

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

        mTime = findViewById(R.id.textView);
        Message message = mHandler.obtainMessage(1);
        mHandler.sendMessageDelayed(message,1000);


    }

    private void goHome() {
        Intent intent = new Intent(this, MainActivity.class);
        startActivity(intent);
        finish();
    }
}