1.二种创建方法及内存泄漏

MainActivity

package com.example.handler_sample;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.TextView;public class MainActivity extends AppCompatActivity {public static final int MSG_CODE = 1001;private TextView textView;//handler创建方法一 使用Callbackprivate Handler handler1=new Handler(new Handler.Callback() {@Overridepublic boolean handleMessage(@NonNull Message msg) {return false;}});//handler创建方法二 重写handlerMessage()方法private Handler handler2=new Handler(){@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);textView.setText(msg.obj.toString());}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);textView = findViewById(R.id.tv);test();}//子线程创建发送消息给主线程,更新组件的显示,并且赋值消息private void test() {new Thread(()->{//常规写法Message message = new Message();message.obj="Next";message.what= MSG_CODE;handler2.sendMessage(message);}).start();}
}

activity_main.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"><TextViewandroid:id="@+id/tv"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:text="Hello World!" /></RelativeLayout>

内存泄漏分析:

添加测试代码

package com.example.handler_sample;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.TextView;import java.util.concurrent.TimeUnit;public class MainActivity extends AppCompatActivity {public static final int MSG_CODE = 1001;private TextView textView;//handler创建方法一 使用Callbackprivate Handler handler1=new Handler(new Handler.Callback() {@Overridepublic boolean handleMessage(@NonNull Message msg) {startActivity(new Intent(MainActivity.this,SecondActivity.class));return false;}});//handler创建方法二 重写handlerMessage()方法private Handler handler2=new Handler(){@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);textView.setText(msg.obj.toString());}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);textView = findViewById(R.id.tv);test();}//子线程创建发送消息给主线程,更新组件的显示,并且赋值消息private void test() {new Thread(()->{//常规写法Message message = new Message();/*   message.obj="Next";message.what= MSG_CODE;handler2.sendMessage(message);*/try {message.what=MSG_CODE;TimeUnit.SECONDS.sleep(3);//休眠3秒 并且销毁Activityhandler1.sendMessage(message);//跳转到第二个界面} catch (InterruptedException e) {e.printStackTrace();}}).start();}@Overrideprotected void onDestroy() {super.onDestroy();Log.e("TAG", "onDestroy: ");}
}

第二个Activity

package com.example.handler_sample;import android.os.Bundle;import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;public class SecondActivity extends AppCompatActivity {@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_personal);}
}

activity_personal.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"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:text="Hi,186"/></RelativeLayout>

让线程休眠三秒,发消息给handler1,然后跳转到第二个界面启动APP的时候,退出销毁掉Activity,但是三秒后还是会跳转到第二个界面,这里就出现了假销毁现象,也就是内存泄漏

在Activity销毁时,移除message

@Override
protected void onDestroy() {super.onDestroy();handler1.removeMessages(MSG_CODE);Log.e("TAG", "onDestroy: ");
}

也是无济于事,因为线程休眠的时候还未将消息放入到消息队列中,销毁时remove的是空

使用sendMessageDelayed()延时发送

private void test() {new Thread(()->{//常规写法Message message = new Message();/*   message.obj="Next";message.what= MSG_CODE;handler2.sendMessage(message);*/message.what=MSG_CODE;handler1.sendMessageDelayed(message,3000);}).start();
}

此时销毁Activity即可终止发送

针对第一种方式的解决方法

private void test() {new Thread(()->{//常规写法Message message = new Message();message.what=MSG_CODE;try {TimeUnit.SECONDS.sleep(3);//休眠3秒 并且销毁Activityif (handler1!=null)handler1.sendMessage(message);//跳转到第二个界面} catch (InterruptedException e) {e.printStackTrace();}}).start();
}@Override
protected void onDestroy() {super.onDestroy();handler1=null;Log.e("TAG", "onDestroy: ");
}

当activity销毁时直接给hander1赋为null,线程判断hander1不为空再发送消息,这时候休眠结束就不会发送消息了

不推荐的写法

private Message message;
@Override
protected void onDestroy() {super.onDestroy();//  handler1.removeMessages(MSG_CODE);// handler1=null;message.recycle();Log.e("TAG", "onDestroy: ");
}

让Message设为全局变量,然后销毁的时候回收message,如果是发送延时消息没问题,如果是直接发送,则会抛出消息正在使用的异常

2.不能在子线程创建Handler

    private void test() {new Thread(()->{new Handler();}).start();}

直接闪退

java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-2,5,main] that has not called Looper.prepare()

会抛出异常,因为应用启动时会调用ActivtyThead方法 其中的main方法

public static void main(String[] args) {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");// Install selective syscall interceptionAndroidOs.install();// CloseGuard defaults to true and can be quite spammy.  We// disable it here, but selectively enable it later (via// StrictMode) on debug builds, but using DropBox, not logs.CloseGuard.setEnabled(false);Environment.initForCurrentUser();// Make sure TrustedCertificateStore looks in the right place for CA certificatesfinal File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());TrustedCertificateStore.setDefaultUserDirectory(configDir);// Call per-process mainline module initialization.initializeMainlineModules();Process.setArgV0("<pre-initialized>");Looper.prepareMainLooper();// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.// It will be in the format "seq=114"long startSeq = 0;if (args != null) {for (int i = args.length - 1; i >= 0; --i) {if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {startSeq = Long.parseLong(args[i].substring(PROC_START_SEQ_IDENT.length()));}}}ActivityThread thread = new ActivityThread();thread.attach(false, startSeq);if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}if (false) {Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, "ActivityThread"));}// End of event ActivityThreadMain.Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);Looper.loop();throw new RuntimeException("Main thread loop unexpectedly exited");
}

里边有关于looper的检测

Looper.prepareMainLooper();

进去prepareMainLooper()发现

@Deprecated
public static void prepareMainLooper() {prepare(false);synchronized (Looper.class) {if (sMainLooper != null) {throw new IllegalStateException("The main Looper has already been prepared.");}sMainLooper = myLooper();}
}

再进prepare(false)

private static void prepare(boolean quitAllowed) {if (sThreadLocal.get() != null) {throw new RuntimeException("Only one Looper may be created per thread");}sThreadLocal.set(new Looper(quitAllowed));
}
sThreadLocal.set(new Looper(quitAllowed));

它这里new了一个Looper 这个Looper是主线程的Looper,这个sThreadLocal是存在在ThreadLocalMap里的,查看其sThreadLocal.get()方法

public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();
}
Thread t = Thread.currentThread();

从主线程的ThreadLocal去给get得到的是主线程做为key

ThreadLocalMap map = getMap(t);

然后从getmap去找,找不到就抛出异常

再看new Handler

public Handler(@Nullable Callback callback, boolean async) {if (FIND_POTENTIAL_LEAKS) {final Class<? extends Handler> klass = getClass();if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&(klass.getModifiers() & Modifier.STATIC) == 0) {Log.w(TAG, "The following Handler class should be static or leaks might occur: " +klass.getCanonicalName());}}mLooper = Looper.myLooper();
mLooper = Looper.myLooper();

Looper从myLooper中去取

public static @Nullable Looper myLooper() {return sThreadLocal.get();
}

结果取的looper 是sThreadLocal.get()去主线程拿 从子线程拿是找不到的 因为上边发现sThreadLocal.get()是从ThreadLocalMap去取值。key已经设置为主线程,value是looper,此时的new Handler()是从子线程进去的所以拿不到,最终抛出异常

handler中

if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread()+ " that has not called Looper.prepare()");
}

mLooper为null就抛出异常,此时已经很清晰了

3.子线程修改UI

    private void test() {new Thread(()->{textView.setText("andy");}).start();}

发现子线程是可以修改控件的

使用Toast

private void test() {new Thread(()->{Toast.makeText(this, "andy", Toast.LENGTH_SHORT).show();}).start();
}

发现闪退,并且抛出异常

java.lang.RuntimeException: Can't toast on a thread that has not called Looper.prepare()

设置线程休眠后修改TextView

    private void test() {new Thread(()->{try {TimeUnit.SECONDS.sleep(3);textView.setText("andy");} catch (InterruptedException e) {e.printStackTrace();}}).start();}

发现休眠后闪退,抛出异常

    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

进入setText方法找到

if (mLayout != null) {checkForRelayout();
}

进入 checkForRelayout()方法

private void checkForRelayout() {// If we have a fixed width, we can just swap in a new text layout// if the text height stays the same or if the view height is fixed.if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT|| (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))&& (mHint == null || mHintLayout != null)&& (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {// Static width, so try making a new text layout.int oldht = mLayout.getHeight();int want = mLayout.getWidth();int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();/** No need to bring the text into view, since the size is not* changing (unless we do the requestLayout(), in which case it* will happen at measure).*/makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),false);if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {// In a fixed-height view, so use our new text layout.if (mLayoutParams.height != LayoutParams.WRAP_CONTENT&& mLayoutParams.height != LayoutParams.MATCH_PARENT) {autoSizeText();invalidate();return;}// Dynamic height, but height has stayed the same,// so use our new text layout.if (mLayout.getHeight() == oldht&& (mHintLayout == null || mHintLayout.getHeight() == oldht)) {autoSizeText();invalidate();return;}}// We lose: the height has changed and we have a dynamic height.// Request a new view layout using our new text layout.requestLayout();invalidate();} else {// Dynamic width, so we have no choice but to request a new// view layout with a new text layout.nullLayouts();requestLayout();invalidate();}
}

发现其if else都会执行

requestLayout();
invalidate();

一个请求布局,一个刷新

同时TextView继承View

public class TextView extends View implements ViewTreeObserver.OnPreDrawListener

查看requestLayout()方法

public void requestLayout() {if (mMeasureCache != null) mMeasureCache.clear();if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {// Only trigger request-during-layout logic if this is the view requesting it,// not the views in its parent hierarchyViewRootImpl viewRoot = getViewRootImpl();if (viewRoot != null && viewRoot.isInLayout()) {if (!viewRoot.requestLayoutDuringLayout(this)) {return;}}mAttachInfo.mViewRequestingLayout = this;}mPrivateFlags |= PFLAG_FORCE_LAYOUT;mPrivateFlags |= PFLAG_INVALIDATED;if (mParent != null && !mParent.isLayoutRequested()) {mParent.requestLayout();}if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {mAttachInfo.mViewRequestingLayout = null;}
}

ViewRootImpl是ViewPrivate的实现类

在ViewRootImpl中requestLayout()执行了checkThread()方法

@Override
public void requestLayout() {if (!mHandlingLayoutInLayoutRequest) {checkThread();mLayoutRequested = true;scheduleTraversals();}
}

查看checkThread()方法

void checkThread() {if (mThread != Thread.currentThread()) {throw new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its views.");}
}

当前的Thread和存入的Thread不一致就会抛出异常

public static Toast makeText(@NonNull Context context, @Nullable Looper looper,@NonNull CharSequence text, @Duration int duration) {if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {Toast result = new Toast(context, looper);result.mText = text;result.mDuration = duration;return result;} else {Toast result = new Toast(context, looper);View v = ToastPresenter.getTextToastView(context, text);result.mNextView = v;result.mDuration = duration;return result;}
}

Toast()最后也调用了view

如果执行的时候invalidate()快于requestLayout()方法,那么就不会抛出异常,如果requestLayout()先执行一段再invalidate()就会抛出异常

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8191)at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1420)

这里的异常说明也很清晰了ViewRootImpl.requestLayout ViewRootImpl.checkThread抛出异常主线程和子线程不一致

4.Handler的两种写法

//handler创建方法一 使用Callback
private Handler handler1=new Handler(new Handler.Callback() {@Overridepublic boolean handleMessage(@NonNull Message msg) {startActivity(new Intent(MainActivity.this,SecondActivity.class));return false;}
});
//handler创建方法二 重写handlerMessage()方法
private Handler handler2=new Handler(){@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);textView.setText(msg.obj.toString());}
};

方法二是谷歌备胎api不推荐使用

Handler收消息是

public void dispatchMessage(@NonNull Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}
}

如果mCallback不等于空,则直接返回,否则handleMessage(msg) 两个handleMessage(msg)是有差别的,一个是可以重写,一个是接口固定的方法

public void handleMessage(@NonNull Message msg) {}/*** Handle system messages here.*/
public void dispatchMessage(@NonNull Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}
}

其上边执行的handleCallback就是开启run方法

private static void handleCallback(Message message) {message.callback.run();
}

也就是子线程的run切换到主线程中,去执行run

5.ThreadLocal

创建测试方法

void test(){final ThreadLocal<String> threadLocal=new ThreadLocal<String>(){@Nullable@Overrideprotected String initialValue() {//重写初始化方法,默认返回为null,如果ThreadLocalMap拿不到值再调用初始化方法threadLocal.get();return "andy";}};
}

注意注释,查看threadLocal.get()方法

private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value;
}

key是主线程 value是T 是你定义的泛型

发现从threadLocal.get()获取的是主线程

注意

在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。 初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。 然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。

  1. 实际的通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的;
  2. 为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量,就像上面代码中的longLocal和stringLocal;
  3. 在进行get之前,必须先set,否则会报空指针异常;如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。 因为在上面的代码分析过程中,我们发现如果没有先set的话,即在map中查找不到对应的存储,则会通过调用setInitialValue方法返回i,而在setInitialValue方法中,有一个语句是T value = initialValue(), 而默认情况下,initialValue方法返回的是null。

深入理解Handler(上)相关推荐

  1. 彻底理解Handler的设计之传送带模型

    作者:彭泰强 0 这篇文章的目的 有时候在Handler相关的文章中可以看到,会把Handler机制的几个角色类比成一个传送带场景来理解. 例如,这篇文章中写到: 我们可以把传送带上的货物看做是一个个 ...

  2. MindSpore技术理解(上)

    MindSpore技术理解(上) 引言 深度学习研究和应用在近几十年得到了爆炸式的发展,掀起了人工智能的第三次浪潮,并且在图像识别.语音识别与合成.无人驾驶.机器视觉等方面取得了巨大的成功.这也对算法 ...

  3. 基于TensorRT的BERT实时自然语言理解(上)

    基于TensorRT的BERT实时自然语言理解(上) 大规模语言模型(LSLMs)如BERT.GPT-2和XL-Net为许多自然语言理解(NLU)任务带来了最先进的精准飞跃.自2018年10月发布以来 ...

  4. 重新理解函数空间(上)

    重新理解函数空间(上) 读论文时遇到了可再生核希尔伯特空间.尽管之前在雁栖湖上课学习SVM时听郭嘉丰老师讲过一嘴,但还是被这个名词吓得不敢动弹.张颢老师曾说,对一些经常用到的知识要有"小脑反 ...

  5. 世界上最美丽的语言python_你如何理解“世界上最美丽的语言是微笑”,要求是一篇3分钟的即兴评述,...

    题目: 你如何理解"世界上最美丽的语言是微笑",要求是一篇3分钟的即兴评述, 解答: "世界上最美丽的语言,就是微笑" 我要说,微笑,是无国界的语言.每一个微笑 ...

  6. 【Linux】1.0常见指令以及权限理解(上)

    文章目录 1.Linux简介 1.1 Linux内核介绍 1.2Centos操作系统介绍 2.Linux 常见指令 1. ls 指令 2.pwd 指令 3.cd 指令 4.touch指令 5.mkdi ...

  7. 概率密度函数及其在信号方面的简单理解(上)概率密度函数

    概率密度函数及其在信号方面的简单理解(上)概率密度函数 上篇 概率密度函数 1 离散随机变量与连续型随机变量 2 离散随机变量的分布函数 2.1 概率函数 2.2 概率分布 2.3 概率分布函数(累积 ...

  8. 云从科技上交大提出DCMN+ 模型,在多项阅读理解数据集上成绩领先

    2020 年 2 月 7 日-2 月 12 日,AAAI 2020 将于美国纽约举办.不久之前,大会官方公布了今年的论文收录信息:收到 8800 篇提交论文,评审了 7737 篇,接收 1591 篇, ...

  9. Handler(上)——Mars Andoird开发视频第二季第六集(重)

    2019独角兽企业重金招聘Python工程师标准>>> 1. 通过Handler实现线程间通信 handler.Looper.Message Queue(消息队列)的重要价值就是实现 ...

最新文章

  1. python解除windows锁屏_实战 | Python批量提取Win10锁屏壁纸
  2. 常用SQL Server 小语法、函数 等的实例汇总
  3. VIBE复现过程,使用nvidia和libOpenGL.so渲染出错及解决方案
  4. python中、变量指向的对象可以发生变化吗_python中的引用传递,可变对象,不可变对象,list注意点...
  5. .NET 6 中 gRPC 的新功能
  6. JavaScript三种弹出框(alert,confirm和prompt)用法举例
  7. springboot学习,实现原理技术点汇总
  8. Matlab Tricks(三十) —— 任意区间的均匀分布
  9. C++程序设计【一】之 C++ 语言简介
  10. html 图片移动动画,HTML5移动端图片左右切换动画DEMO演示
  11. QUIC/UDT/SRT
  12. mysql navicat授权_Mysql授权允许远程访问解决Navicat for MySQL连接mysql提示客户端不支持服务器请求的身份验证协议;考虑升级MySQL客户端...
  13. 清华大学出来的工资有多高?| 文末送书
  14. UVA 10105 Polynomial Coefficients
  15. Windbg内核调试(大杂烩)
  16. Android 电源键事件流程分析
  17. HDU - 4489 The King’s Ups and Downs (排列组合+dp)
  18. Linux系统如何PING地址,Linux下指定源ip进行ping操作的方法
  19. 磕磕绊绊的全景相机之路
  20. DruidCP源码阅读8 -- removeAbandoned机制

热门文章

  1. 微信小程序----switch组件(开关选择器)
  2. CCF python 折点计数
  3. 永恒之塔 服务器维护,游戏运行给力永恒之塔更换顶级服务器
  4. [系统安全] 二十七.WannaCry勒索病毒分析 (3)蠕虫传播机制解析及IDA和OD逆向
  5. 鸿蒙幼儿园名称分析,好听的幼儿园名字大全
  6. 各球手机制式(频率)
  7. 对python中的list元素计数
  8. php fastcgi进程启动,php fastcgi 启动脚本
  9. Js之跳出循环(for/forEach)
  10. mac环境下VSCODE 全局搜索无效的问题