深入理解Handler(上)

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创建方法一 使用Callback
    private Handler handler1=new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message msg) {
            return false;
        }
    });
    //handler创建方法二 重写handlerMessage()方法
    private Handler handler2=new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            textView.setText(msg.obj.toString());
        }
    };

    @Override
    protected 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">

    <TextView
        android: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创建方法一 使用Callback
    private Handler handler1=new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message msg) {
            startActivity(new Intent(MainActivity.this,SecondActivity.class));
            return false;
        }
    });
    //handler创建方法二 重写handlerMessage()方法
    private Handler handler2=new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            textView.setText(msg.obj.toString());
        }
    };

    @Override
    protected 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秒 并且销毁Activity
                handler1.sendMessage(message);//跳转到第二个界面
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }

    @Override
    protected 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 {

    @Override
    protected 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">

    <TextView
        android: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秒 并且销毁Activity
           if (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 interception
    AndroidOs.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 certificates
    final 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(new
                LogPrinter(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 hierarchy
        ViewRootImpl 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() {
    @Override
    public boolean handleMessage(@NonNull Message msg) {
        startActivity(new Intent(MainActivity.this,SecondActivity.class));
        return false;
    }
});
//handler创建方法二 重写handlerMessage()方法
private Handler handler2=new Handler(){
    @Override
    public 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
        @Override
        protected 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);
    else
        createMap(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。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值