Android之Handler通信


一、课程背景

1、UI线程/主线程/ActivityThead
2、线程不安全(针对于多线程)
Android的UI线程——线程不安全,所以在子线程更新UI会出现问题,用Handler解决

线程安全: 多线程访问时,采用了加锁机制,当一个线程访问UI时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。

3、消息循环机制


二、应用场景

在这里插入图片描述


三、概念介绍

1、Handler:发送消息和处理消息
2、Looper:负责循环读取MessageQueen中的消息,读到消息之后就把消息交给Handler去处理

3、Message:消息对象
4、MessageQueue:存储消息对象的队列

在这里插入图片描述


四、代码实现最简单Handler

MainActicity

package com.example.handlerstudy;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    /*
    主线程范围
     */
    private static final String TAG="MainActivity";

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

        final TextView textView=(TextView)findViewById(R.id.textView);

        @SuppressLint("Handlerleak")
        //创建Handler
        final Handler handler=new Handler(){

            @Override
            public void handleMessage(@NonNull Message msg) {
                super.handleMessage(msg);

                //处理消息
                Log.d(TAG, "handleMessage: "+msg.what);
            }
        };

        handler.sendEmptyMessage(1001);
    }
}

logcat:
02-13 19:57:52.713 7432-7432/com.example.handlerstudy D/MainActivity: handleMessage: 1001


Q:代码为什么会有一块是黄色的高亮显示


activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
	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"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginStart="78dp"
        android:layout_marginLeft="78dp"
        android:layout_marginTop="154dp"
        android:text="Button"
        />
</RelativeLayout>

MainActivity:新增Button点击事件

package com.example.handlerstudy;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    /*
    主线程范围
     */
    private static final String TAG="MainActivity";

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

        final TextView textView=(TextView)findViewById(R.id.textView);

        @SuppressLint("Handlerleak")
        //创建Handler
        final Handler handler=new Handler(){

            @Override
            public void handleMessage(@NonNull Message msg) {
                super.handleMessage(msg);

                //处理消息
                Log.d(TAG, "handleMessage: "+msg.what);
            }
        };

        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        textView.setText("imooc");

                    }
                }).start();
            }
        });

        handler.sendEmptyMessage(1001);
    }


}


程序出现闪退: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

只能在主线程更新UI

MainActivity修改:

如果是在子线程中,把消息发出去,然后在主线程中拦截该消息,并进行处理

package com.example.handlerstudy;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private static final String TAG="MainActivity";

	/**
     *UI线程:
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final TextView textView=(TextView)findViewById(R.id.textView);

        @SuppressLint("Handlerleak")
        //创建Handler
        final Handler handler=new Handler(){

            @Override
            public void handleMessage(@NonNull Message msg) {
                super.handleMessage(msg);

				/**
                 * 主线程:接到子线程发出的消息,处理
                 */
                //处理消息
                Log.d(TAG, "handleMessage: "+msg.what);

                if(msg.what==1001){
                    textView.setText("imooc");
                }
            }
        };

        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                /**
                 * 子线程:
                 */
                //有可能做大量耗时操作
                new Thread(new Runnable() {
                    @Override
                    public void run() {

                        try {
                            Thread.sleep(4*1000);//休眠4秒
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        
						/**
                         * 通知UI更新
                         */
                        handler.sendEmptyMessage(1001);

                    }
                }).start();
            }
        });

    }
}

效果图
在这里插入图片描述在这里插入图片描述


五、Handler的发送消息方法

1、Handler.sendMessage()

MainActivity

public class MainActivity extends AppCompatActivity {

    private static final String TAG="MainActivity";

    /**
     *UI线程:
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final TextView textView=(TextView)findViewById(R.id.textView);

        @SuppressLint("Handlerleak")
        //创建Handler
        final Handler handler=new Handler(){

            @Override
            public void handleMessage(@NonNull Message msg) {
                super.handleMessage(msg);

                /**
                 * 主线程:接到子线程发出的消息,处理
                 */
                //处理消息
                Log.d(TAG, "handleMessage: "+msg.what);

                if(msg.what==1002){
                    textView.setText("imooc");
                    Log.d(TAG, "handleMessage: "+msg.arg1);
                    Log.d(TAG, "handleMessage: "+msg.arg2);
                    Log.d(TAG, "handleMessage: "+msg.obj);

                }
            }
        };

        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                /**
                 * 子线程:
                 */
                //有可能做大量耗时操作
                new Thread(new Runnable() {
                    @Override
                    public void run() {

                        try {
                            Thread.sleep(4*1000);//休眠4秒
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        /**
                         * 通知UI更新
                         */
                        Message message=Message.obtain();//不直接使用new
                        message.what=1002;
                        message.arg1=1003;
                        message.arg2=1004;
                        message.obj=MainActivity.this;

                        handler.sendMessage(message);

                    }
                }).start();
            }
        });

    }
}


logcat:
02-13 20:47:08.195 7817-7817/com.example.handlerstudy D/MainActivity: handleMessage: 1002 02-13 20:47:08.196 7817-7817/com.example.handlerstudy D/MainActivity: handleMessage: 1003 02-13 20:47:08.196 7817-7817/com.example.handlerstudy D/MainActivity: handleMessage: 1004 02-13 20:47:08.196 7817-7817/com.example.handlerstudy D/MainActivity: handleMessage: com.example.handlerstudy.MainActivity@96eaa15


【注意】Message.obtain()方法

做了一个缓存,有的话直接取,没有的话,再new Message()

public static Message obtain() {
  synchronized (sPoolSync) {
      if (sPool != null) {
          Message m = sPool;
          sPool = m.next;
          m.next = null;
          m.flags = 0; // clear in-use flag
          sPoolSize--;
          return m;
      }
  }
  return new Message();
}

Handler.sendMessageAtTime(Message ,uptimeMillis)

约定一个时间发送消息(绝对的)

handler.sendMessageAtTime(message, SystemClock.uptimeMillis()+3*1000);


Handler.sendMessageDelayed(Message,delayMillis)

2秒后送达(相对的)
handler.sendMessageDelayed(message,2*1000);



2、Handler.post()

注意: 在main主线程执行完后立即调用

MainActivity–>onClick()–>new Thead中

Runnable runnable = new Runnable() {
   @Override
   public void run() {
       int a = 1 + 2 + 3;
       System.out.println(a);
   }
};
handler.post(runnable);

runnable.run();


Handler.postAtTime(Runnable,long)

handler.postAtTime(runnable,4*1000);


Handler.postDelayed(Runnable,long)

延迟4秒
注意: postDelayed的方法意在延迟执行,在main主线程执行完后延迟3秒后开始调用。
handler.postDelayed(runnable,4*1000);


六、Handler实践的三种效果

1、异步下载文件更新进度条

具体代码

activity_download.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".DownloadActivity">

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="New Button"
        />

    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:max="100"
        android:layout_gravity="center_horizontal"
        />


</LinearLayout>

DownloadActivity

package com.example.handlerstudy;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;

public class DownloadActivity extends AppCompatActivity {

    private static final int DOWNLOAD_MESSAGE_FAIL_CODE = 100002;
    private final int DOWNLOAD_MESSAGE_CODE = 100001;
    private static Handler mHandler;
    private String APP_URL="http://download.sj.qq.com/upload/connAssitantDownload/upload/MobileAssistant_1.apk";

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

        final ProgressBar progressBar=(ProgressBar)findViewById(R.id.progressBar);
        /**
         * 主线程 --> start
         * 点击按钮 |
         * 发起下载 |
         * 开启子线程做下载 |
         * 下载过程中通知主线程 --> 主线程更新进度条
         */

        findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        download(APP_URL);

                    }
                }).start();
            }
        });

        mHandler = new Handler(){

            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);

                switch (msg.what){
                    case DOWNLOAD_MESSAGE_CODE:

                        progressBar.setProgress((Integer) msg.obj);
                        break;

                    case DOWNLOAD_MESSAGE_FAIL_CODE:


                        break;
                }
            }
        };
    }

    private void download(String appUrl) {

        try {
            URL url=new URL(appUrl);
            URLConnection urlConnection=url.openConnection();

            /**
             * 获取文件的流
             */
            InputStream inputStream=urlConnection.getInputStream();

            /**
             * 获取文件的总长度
             */
            int contentLength=urlConnection.getContentLength();

            /**
             * 获取存储设备的地址
             * File.separator:斜杠/
             */
            String downloadFolderName= Environment.getExternalStorageDirectory()
                    + File.separator+"imooc"+File.separator;
            Log.d("哈哈哈哈哈哈:", String.valueOf(Environment.getExternalStorageDirectory()));

            File file=new File(downloadFolderName);
            if(!file.exists()){
                file.mkdir();//创建
            }

            String fileName=downloadFolderName+"imooc.apk";

            File apkFile=new File(fileName);
            if(apkFile.exists()){
                apkFile.delete();
            }

            int downloadSzie=0;
            byte bytes[]=new byte[1024];//用于缓存

            int length=0;

            //输出
            OutputStream outputStream=new FileOutputStream(fileName);
            while ((length=inputStream.read(bytes)) != -1){
                outputStream.write(bytes,0,length);
                downloadSzie += length;

                /**
                 * update UI
                 */

                Message message=Message.obtain();
                message.obj=downloadSzie * 100 / contentLength ;
                message.what= DOWNLOAD_MESSAGE_CODE;

                mHandler.sendMessage(message);

            }

            inputStream.close();
            outputStream.close();

        } catch (MalformedURLException e) {
            NotifyDownloadFailed();
            e.printStackTrace();
        } catch (IOException e) {
            NotifyDownloadFailed();
            e.printStackTrace();
        }

    }

    private void NotifyDownloadFailed() {
        Message message=Message.obtain();
        message.what= DOWNLOAD_MESSAGE_FAIL_CODE;
        mHandler.sendMessage(message);
    }
}

Q:怎么打开模拟机的文件管理器
A:点击Android Studio侧边的Device File Explorer。

在这里插入图片描述

效果图:
在这里插入图片描述


Handler部分黄色的高亮显示,有内存泄漏的风险

内存泄漏

原因:如果发件人传入上下文,activity可能已经被销毁,但异步里可能还持有该activity的引用,因为垃圾回收器GC不会把它回收掉


2、倒计时的实现

内存泄漏解决

使用弱引用解决了内存泄漏的风险

具体代码

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    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"
    tools:context=".MainActivity"
    android:padding="16dp"
    >

<!--  倒计时  -->
    <TextView
        android:id="@+id/countdownTimeTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/maxTime"
        android:textSize="36sp"
        android:layout_centerInParent="true"
        />

</RelativeLayout>

MainActivity

package com.example.handlerstudy2;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.TextView;

import java.lang.ref.WeakReference;

public class MainActivity extends AppCompatActivity {

    /**
     * 倒计时标记handler code
     */
    public static final int COUNTDOWN_TIME_CODE = 100001;
    /**
     * 倒计时间隔
     */
    public static final int DELAY_MILLIS = 1 * 1000;
    /**
     * 倒计时最大值
     */
    public static final int MAX_COUNT = 10;

    private TextView mCountdownTimeTextView;

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


        //得到控件
        mCountdownTimeTextView = (TextView) findViewById(R.id.countdownTimeTextView);

        //创建了一个handler
        CountdownTimeHandler handler = new CountdownTimeHandler(this);

        //新建了一个message
        Message message = Message.obtain();
        message.what = COUNTDOWN_TIME_CODE;
        message.arg1 = MAX_COUNT;

        //第一次发送message
        handler.sendMessageDelayed(message, DELAY_MILLIS);//延迟1秒

    }

    public static class CountdownTimeHandler extends Handler {

        /**
         * 倒计时最小值
         */
        static final int MIN_COUNT = 0;

        final WeakReference<MainActivity> mWeakReference;

        CountdownTimeHandler(MainActivity activity) {
            mWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);

            //获取当前activity的弱引用
            MainActivity activity = mWeakReference.get();


            switch (msg.what) {
                case COUNTDOWN_TIME_CODE:

                    int value = msg.arg1;

                    activity.mCountdownTimeTextView.setText(String.valueOf(value--));

                    //循环发的消息控制
                    if (value >= MIN_COUNT) {
                        Message message = Message.obtain();
                        message.what = COUNTDOWN_TIME_CODE;
                        message.arg1 = value;
                        sendMessageDelayed(message, DELAY_MILLIS);
                    }

                    break;
            }

        }
    }
}

效果图:

10 --> 0 的倒计时


3、用Handler来实现简单打地鼠游戏

具体代码

activity_diglett.xml

<?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"
    android:background="#FFFFFF">

<!--  地鼠,随机出现  -->
    <ImageView
        android:id="@+id/image_view"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:src="@drawable/diglett"
        android:visibility="gone"
        />

    <Button
        android:id="@+id/start_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:text="@string/begin_game"

        />

    <TextView
        android:id="@+id/text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:textAppearance="?android:attr/textAppearanceLarge"
        />

</RelativeLayout>

DiglettActivity

package com.example.handlerstudy;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import java.lang.ref.WeakReference;
import java.util.Random;

//Diglett:地鼠
public class DiglettActivity extends AppCompatActivity implements View.OnClickListener, View.OnTouchListener {

    private TextView mResultTextView;//显示文本
    private ImageView mDiglettImageView;//地鼠图像
    private Button mStartButton;//开始按钮

    private static final int CODE = 123;

    //地鼠出现位置
    public int[][] mPosition = new int[][]{
            {342, 180}, {432, 880},
            {521, 256}, {429, 780},
            {456, 976}, {145, 665},
            {123, 678}, {564, 567},
    };

    private int mTotalCount;//所有的数量
    private int mSuccessCount;//成功的数量

    public static final int MAX_COUNT = 10;//地鼠最大数量

    private DiglettHandler mHandler = new DiglettHandler(this);//创建handler


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

        initView();//初始化

        setTitle("打地鼠");

    }

    /**
     * 初始化控件
     */
    private void initView() {
        mResultTextView = (TextView) findViewById(R.id.text_view);
        mDiglettImageView = (ImageView) findViewById(R.id.image_view);
        mStartButton = (Button) findViewById(R.id.start_button);

        /**
         * 用实现的方法实现点击事件
         */
        mStartButton.setOnClickListener(this);
        mDiglettImageView.setOnTouchListener(this);
    }


    /**
     * 实现View.OnClickListener必须实现的方法
     *
     * @param v
     */
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.start_button:
                start();
                break;
        }
    }

    private void start() {

        //发送消息 handler.sendmessagedelayed
        mResultTextView.setText("开始啦");
        mStartButton.setText("游戏中...");
        mStartButton.setEnabled(false);//不能按了

        next(0);

    }

    private void next(int delayTime) {

        int position = new Random().nextInt(mPosition.length);

        Message message = Message.obtain();
        message.what = CODE;
        message.arg1 = position;

        mHandler.sendMessageDelayed(message, delayTime);
        mTotalCount++;

    }

    /**
     * 实现View.OnTouchListener必须实现的方法
     * @param v
     * @param event
     * @return
     */
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        v.setVisibility(View.GONE);
        mSuccessCount++;
        mResultTextView.setText("打到了" + mSuccessCount + "只,共" + MAX_COUNT + "只");
        return false;
    }

    //防止内存泄漏
    public static class DiglettHandler extends Handler {

        private static final int RANDOM_NUMBER = 500;
        public final WeakReference<DiglettActivity> mWeakReference;


        public DiglettHandler(DiglettActivity activity) {
            mWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

            //拿到activity
            DiglettActivity activity = mWeakReference.get();

            switch (msg.what) {
                case CODE:

                    if (activity.mTotalCount > MAX_COUNT) {
                        activity.clear();
                        Toast.makeText(activity, "地鼠打完了!", Toast.LENGTH_LONG).show();
                        return;
                    }

                    int position = msg.arg1;
                    activity.mDiglettImageView.setX(activity.mPosition[position][0]);
                    activity.mDiglettImageView.setY(activity.mPosition[position][1]);
                    activity.mDiglettImageView.setVisibility(View.VISIBLE);

                    int randomTime = new Random().nextInt(RANDOM_NUMBER) + RANDOM_NUMBER;

                    activity.next(randomTime);
                    break;
            }
        }
    }

    private void clear() {
        mTotalCount = 0;
        mSuccessCount = 0;
        mDiglettImageView.setVisibility(View.GONE);
        mStartButton.setText("点击开始");
        mStartButton.setEnabled(true);
    }
}

效果图:
在这里插入图片描述在这里插入图片描述

发布了64 篇原创文章 · 获赞 5 · 访问量 9310
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章