Android高级-架构师-Handler Message源码分析及手写实现1

我们都用过Handler,也知道handler有两种使用方式,

目录

* 1、Handler内存泄露测试
* 2、为什么不能在子线程创建Handler
* 3、textView.setText()只能在主线程执行,这句话是错误!
* 4、new Handler()两种写法有什么区别?
* 5、ThreadLocal用法和原理

1:实现方式

1:第一种

    private Handler handler1 = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            startActivity(new Intent(MainActivity.this, PersonalActivity.class));
            return false;
        }
    });

第二种

    // 这是谷歌备胎的api,不推荐使用
    private Handler handler2 = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            textView.setText(msg.obj.toString());
        }
    };

2:内存泄露问题

摧毁activity

    private void test() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                                // 1、Handler内存泄露测试(假象)
                SystemClock.sleep(1000); // 销毁Activity  线程休眠
                Message message = new Message();
                message.what=3;
                handler1.sendMessage(message);
//        
                
            }
        }).start();
    }

虽然打印了activity 的ondestory()方法,还是继续了跳转的动作。

所以我们需要在onDestory中

   @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.i("NetEase ", "onDestroy");

         handler1.removeMessages(3);
        handler1 = null;
    }

如果handler不置为空,还是会跳转,如果置空以后就可以了。

3:为什么不能在子线程中创建handler

如果我们这样写

   private void test() {
        new Thread(new Runnable() {
            @Override
            public void run() {

                new Handler();

            }
        }).start();
    }
01-02 07:31:06.201 27860-27882/com.netease.handler.sample E/AndroidRuntime: FATAL EXCEPTION: Thread-2
                                                                            Process: com.netease.handler.sample, PID: 27860
                                                                            java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-2,5,main] that has not called Looper.prepare()
                                                                                at android.os.Handler.<init>(Handler.java:207)
                                                                                at android.os.Handler.<init>(Handler.java:119)
                                                                                at com.netease.handler.sample.MainActivity$3.run(MainActivity.java:60)
                                                                                at java.lang.Thread.run(Thread.java:919)

为什么会报这个错呢?因为app启动时通过AcitivityThread的main()为程序入口,

  public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

        // 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();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");

      //重点看这个方法
        Looper.prepareMainLooper();
   /**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static void prepareMainLooper() {
    //追踪这个方法
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

继续追踪

   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));
    }

在这个方法中,他创建了 Looper,这个Looper是主线程的Looper,这个sThreadLocal是存在ThreadLocalMap中,

比如set方法

   public void set(T value) {
        Thread t = Thread.currentThread();
//这个map集合,new完的Looper放到了Map中,key为当前的Thread
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

然后我们再看new Handler方法,

   */
    public Handler() {
        this(null, false);
    }
   public Handler(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());
            }
        }

//在创建Handler中会去取一个myLooper()
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

他是直接去map中去取 

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

因为程序启动的时候是通过主线程为key,放入了一个looper,现在用子线程为key去取looper,勘定取不到,所以才会报异常。

4:TextView.setTextView只能在主线程执行吗?我们试一下

 

  private void test() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                textView.setText("我在测试~~~~");

            }
        }).start();
    }

为什么呢?之前一直强调只能在主线程中执行。

换成Toast

  private void test() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                //textView.setText("我在测试~~~~");
                Toast.makeText(MainActivity.this,"我在测试",Toast.LENGTH_SHORT).show();

            }
        }).start();
    }
E/AndroidRuntime: FATAL EXCEPTION: Thread-2
                                                                            Process: com.netease.handler.sample, PID: 28357
                                                                            java.lang.RuntimeException: Can't toast on a thread that has not called Looper.prepare()
                                                                                at android.widget.Toast$TN.<init>(Toast.java:407)
                                                                                at android.widget.Toast.<init>(Toast.java:121)
                                                                                at android.widget.Toast.makeText(Toast.java:286)
                                                                                at android.widget.Toast.makeText(Toast.java:276)
                                                                                at com.netease.handler.sample.MainActivity$3.run(MainActivity.java:60)
                                                                                at java.lang.Thread.run(Thread.java:919)

再换一下:

    private void test() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(1000);
               textView.setText("我在测试~~~~");
               // Toast.makeText(MainActivity.this,"我在测试",Toast.LENGTH_SHORT).show();

            }
        }).start();
    }

闪退:

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

为什么呢??

我们进入TextView中:

找到这样的方法

  if (mLayout != null) {
            checkForRelayout();
        }
    /**
     * Check whether entirely new text requires a new view layout
     * or merely a new text layout.
     */
    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)) {
          
    //省略...


            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();
        }
    }

都执行了这两个方法

  requestLayout();
  invalidate();

requestLayout是View中的方法

  /**
     * Call this when something has changed which has invalidated the
     * layout of this view. This will schedule a layout pass of the view
     * tree. This should not be called while the view hierarchy is currently in a layout
     * pass ({@link #isInLayout()}. If layout is happening, the request may be honored at the
     * end of the current layout pass (and then layout will run again) or after the current
     * frame is drawn and the next layout occurs.
     *
     * <p>Subclasses which override this method should call the superclass method to
     * handle possible request-during-layout errors correctly.</p>
     */
    @CallSuper
    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:
public final class ViewRootImpl implements ViewParent,
mParent  
protected ViewParent mParent;ViewParent是个接口
      mParent.requestLayout();

追踪:

ViewRootImpl #requestLayout()

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
  void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

这个异常就是TextView.setTextView中出现的异常,但是如果  invalidate();  在    checkThread(); 方法执行前就执行完,那么就不会报异常。所以,TextView.setTextView只能在主线程执行这句话是错的  ,因为太过于绝对。

5:new Handler两种写法有什么区别?

上面有两种实现方式

我们先看如何去取消息:

ActivityThread#main()方法

  public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

        // 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();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        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.loop();

  public static void loop() {
        final Looper me = myLooper();
//省略

//关注这个循环
       for (;;) {
//取消息
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long traceTag = me.mTraceTag;
            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
            if (thresholdOverride > 0) {
                slowDispatchThresholdMs = thresholdOverride;
                slowDeliveryThresholdMs = thresholdOverride;
            }
            final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
            final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

            final boolean needStartTime = logSlowDelivery || logSlowDispatch;
            final boolean needEndTime = logSlowDispatch;

            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }

            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            try {
                 //通过handler去分发消息
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (logSlowDelivery) {
                if (slowDeliveryDetected) {
                    if ((dispatchStart - msg.when) <= 10) {
                        Slog.w(TAG, "Drained");
                        slowDeliveryDetected = false;
                    }
                } else {
                    if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                            msg)) {
                        // Once we write a slow delivery log, suppress until the queue drains.
                        slowDeliveryDetected = true;
                    }
                }
            }
           
//省略
       

            msg.recycleUnchecked();
        }
    }
msg.target就是handler
    
    /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            //先关注这个方法  先判断mCallBack,如果不等于null那么就继续执行
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

其实没啥区别,当(mCallback.handleMessage(msg))为false的时候,就会去执行下面的handlerMessage方法,下面的就是一个可以重写的方法。所以把第二种方法叫备胎方法。

6:ThreadLocal用法和原理

正常的

Looper#中的ThreadLocal是Looper类型,且是static类型的
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

测试下面常用的源码中的方法

ThreadLocal# set方法

   public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

 

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    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();
    }

 

    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;
    }
 protected T initialValue() {
        return null;
    }

 Demo

public class ThreadLocalTest {

    @Test
    public void test() {
        // 创建本地线程(主线程)---简单测试
        final ThreadLocal<String> threadLocal = new ThreadLocal<String>() {
            @Override
            protected String initialValue() {
                // 重写初始化方法,默认返回null,如果ThreadLocalMap拿不到值再调用初始化方法
                return "冯老师";
            }
        };

        // 从ThreadLocalMap中获取String值,key是主线程
        System.out.println("主线程threadLocal:" + threadLocal.get());

        //--------------------------thread-0
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                // 从ThreadLocalMap中获取key:thread-0的值?没有,拿不到值再调用初始化方法
                String value1 = threadLocal.get();
                System.out.println("thread-0:" + threadLocal.get()); // 冯老师

                // ThreadLocalMap存入:key:thread-0  value:"熊老师"
                threadLocal.set("熊老师");
                String value2 = threadLocal.get(); // 可以get到了
                System.out.println("thread-0  set  >>> " + threadLocal.get()); // 熊老师

                // 使用完成建议remove(),避免大量无意义的内存占用
                threadLocal.remove();
            }
        });
        thread.start();

        //--------------------------thread-1
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 从ThreadLocalMap中获取key:thread-1的值?没有,拿不到值再调用初始化方法
                String value1 = threadLocal.get();
                System.out.println("thread-1:" + threadLocal.get()); // 冯老师

                // ThreadLocalMap存入:key:thread-1  value:"彭老师"
                threadLocal.set("彭老师");
                String value2 = threadLocal.get(); // 可以get到了
                System.out.println("thread-1  set  >>> " + threadLocal.get()); // 彭老师

                // 使用完成建议remove(),避免大量无意义的内存占用
                threadLocal.remove();
            }
        });
        thread2.start();
    }
}

 

原理就是:注意Threadlocal最后的存取是以键值对的形式存在的,键就是当前的线程。

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值