Handler与多线程
概述
Handler译为处理者,不难理解,它的作用就算发送和处理消息。
在Android开发中,我们常常会使用单独的线程来完成某些操作。
如用一个线程来完成从网络上下载图片,然后显示在ImageView上。
在多线程操作时,Android中必须保证以下两点:
- 不要阻塞UI线程
- 不要在UI线程之外访问Android UI工具包
有了以上两点限制,我们在线程之间的消息如何进行传递?回顾一下Handler的作用,确实,线程之间的消息传递就是依靠它。
出处见图上水印
初级案例
准备Button和TextView
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
android:id="@+id/text1"
android:textSize="33sp"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/text1"
android:id="@+id/button1"
android:onClick="download"
android:text="下载"/>
</android.support.constraint.ConstraintLayout>
要实现Handler线程之间的消息传递,需要重写Handler方法。
package com.example.a4_9handler;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.text1);
}
//匿名内部类
private Handler handler=new Handler(){
//重写Handler方法
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 100:
textView.setText("下载完成");
break;
}
}
};
//使用线程模拟下载操作
public void download(View v){
new Thread(new Runnable() {
@Override
public void run() {
while (true){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
break;
}
//下载完成后更新UI状态
//错误的示范:textView.setText("下载完成");
//发送了一个标记——100
handler.sendEmptyMessage(100);
}
}).start();
}
}
效果如下:
Handler常用API
使用Handler通常完成以下两点工作:
- 消息调度和在将来的某个时间点执行一个Runnable
- 多个任务加入到一个队列中执行
Handler的相关方法:
- void handleMessage(Message msg):处理消息的方法,通常是用于被重写!
- sendEmptyMessage(int what):发送空消息
- sendEmptyMessageDelayed(int what,long delayMillis):指定延时多少毫秒后发送空信息
- sendMessage(Message msg):立即发送信息
- sendMessageDelayed(Message msg):指定延时多少毫秒后发送信息
- final boolean hasMessage(int what):检查消息队列中是否包含what属性为指定值的消息 如果是参数为(int what,Object object):除了判断what属性,还需要判断Object属性是否为指定对象的消息
在相关方法的帮助下,再看之前的案例
handler.sendEmptyMessage(100);
实际上是发送了一个空消息,并标记为100
其等价于下面一段话
//获取一个消息对象
Message msg=handler.obtainMessage();
//标记为100
msg.what=100;
//任意类型
msg.obj="要存储的信息";
//发送消息
handler.sendMessage(msg);
另外还可以延迟发送(之前是立即发送)
//在指定时间后发送消息(当前时间3秒后)
handler.sendEmptyMessageAtTime(200,System.currentTimeMillis()+3000);
//延迟多少时间后发送消息(延迟2秒)
handler.sendEmptyMessageDelayed(300,2000);
//对于非空有...
handler.sendMessageDelayed();
handler.sendMessageAtTime();
Handler内部实现原理
Handler实现机制
- Message对象:表示要传递的一个消息。(要传递的数据对象打包成了一个Message,内部使用链表数据结构实现了一个可重复利用的消息池,避免了资源浪费)
- MessageQueue对象:Android启动程序时会在UI线程创建,存放消息对象的消息队列,先进先出原则。(sendMessage实际上就是把Message放如MessageQueue的过程)
- Looper对象:负责管理当前线程的消息队列MessageQueue(循环检查MessageQueue是否有消息,有就取出来)
- Handler对象:负责把消息push到消息队列中,以及接受Looper从消息队列中取出的消息,通过handlerMessage方法处理消息。
出处见图上水印
Handler内存泄露问题分析
程序运行过程中会使用内存,正常情况下,退出时会释放内存。但是,如果在退出时占用的部分没有被正常释放,就会造成内存泄漏。
回顾之前的案例中,IDE已经给出了相应的警告——
This Handler class should be static or leaks might occur
这个处理程序类应该是静态的,否则可能会发生泄漏。
显然这么写是不合理的。
在外部类内定义Handler,Handler会持有外部类的引用(内部类的对象会依赖于外部类的对象),在使用的时候,退出Activity,Activity对象会被销毁,此时如何Handler还在工作,且获取外部类的引用(Handler隐式的获取Activity对象),结果就是Activity无法正常退出(Activity依然占有内存)
即存在内存泄漏风险
案例分析
新建一个Activity(HandlerMemoryActivity)
package com.example.a4_9handler;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class HandlerMemoryActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_memory);
//使用handler延迟执行一个Runnable
handler.postDelayed(new Runnable() {
@Override
public void run() {
System.out.println("Runnable");
}
//延迟10分钟
},1000*60*10);
//10分钟后关闭当前Activity
finish();
}
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
}
启动程序,程序一闪而过,看似退出,实际上还是在后台继续运行。
解决方案
- 定义一个内部类时,会默认拥有外部类对象的引用,所以建议使用内部类时,最好定义为一个静态内部类
- 引用的强弱:强引用(普通new,不会自动回收) > 软引用(内存不足时候回收) > 弱引用(对象不存在就引用不到),所以最好使用弱引用
package com.example.a4_9handler;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import java.lang.ref.WeakReference;
public class HandlerMemoryActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_memory);
//使用handler延迟执行一个Runnable
handler.postDelayed(new Runnable() {
@Override
public void run() {
System.out.println("Runnable");
}
//延迟10分钟
},1000*60*10);
//10分钟后关闭当前Activity
finish();
}
/* private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};*/
private MyHandler handler=new MyHandler(this);
private static class MyHandler extends Handler{
//弱引用,引用当前Activity
WeakReference<HandlerMemoryActivity> weakReference;
public MyHandler(HandlerMemoryActivity activity){
weakReference=new WeakReference<HandlerMemoryActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
HandlerMemoryActivity activity=weakReference.get();
if (activity!=null){
}
}
}
}
Handler实现闪屏页功能
新建一个Activity(,并准备一张图片(用作闪屏页)
布局方面,Imageview也好,background也好,反正就是把准备好的图片设置为Activity的背景
android:background="@mipmap/s123"
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@mipmap/s123"
/>
闪屏页显然不需要标题栏,于是可以在配置清单文件里设置一下全屏的主题。
android:theme="@android:style/Theme.DeviceDefault.Light.NoActionBar.Fullscreen"
完整代码如下:
package com.example.a4_9handler;
import android.app.Activity;
import android.content.Intent;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class SplashActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
handler.postDelayed(new Runnable() {
@Override
public void run() {
startMainActivity();
}
//延迟三秒
},3000);
}
private void startMainActivity(){
//启动主页
Intent intent=new Intent(this,MainActivity.class);
startActivity(intent);
}
private Handler handler=new Handler();
}
效果如下:三秒后从图1跳到图2
AsyncTask
Android提供的一个抽象类,可以更好的协助我们完成多线程编程,这部分在
https://blog.csdn.net/nishigesb123/article/details/89145264
这篇文章中描述