Bootstrap

如何彻底搞懂handler?答案:自己写一个

在深度学习理解handler的时候,发现其实逻辑通了,但是理解的不深刻,所以,纸上谈兵不如现场实战!!以下的目录顺序就是实际中的逻辑顺序!加油!,兄弟们。


实现一个自己的Handler机制

(1)MyMessage

首先实现MyMessageQueue,当然在这之前我们要实现MyMeessage类

public class MyMessage {

    public MyHandler target;

}
(2)MyHandler

MyHandler持有当前线程的Looper,在创建的时候根据当前线程获得,如果当前线程没有Looper则报错。在sendMessage方法中将Message的target赋值,然后入队。

public abstract class MyHandler {
private MyLooper looper;

private MyMessageQueue queue;

public MyHandler() {
    looper = MyLooper.getLooper();
    if (looper == null) {
        throw new RuntimeException("current thread does not have looper");
    }
    queue = looper.queue;
}

public void sendMessage(MyMessage message) {
    message.target = MyHandler.this;
    queue.add(message);
}

public abstract void handleMessage(MyMessage message);
}
(3)MyMessageQueue

为了方便,我只写了一个Handler成员变量
接下来就是MyMessageQueue的实现

public class MyMessageQueue {
private volatile List<MyMessage> queue;

public MyMessageQueue() {
    queue = new ArrayList<>();
}

public void add(MyMessage message) {
    queue.add(message);
}

/**
 * 取出队头的消息并出队,队列位空的时候阻塞
 * @return
 */
public MyMessage next() {
    while (true) {
        if (!queue.isEmpty()) {
            break;
        }
    }
    MyMessage message = null;
    synchronized (MyMessageQueue.this) {
        message = queue.get(0);
        queue.remove(0);
    }
    return message;
 }
}
(4)MyLooper
  1. 代码很简单,有两个方法,入队和出队,对列为空的时候出队方法next()会被阻塞直到有新的消息进来。
  2. MyLooper的实现依赖ThreadLocal,我们提供prepare()、getLooper()、loop()三个静态方法,在内部维护一个静态的ThreadLocal< MyLooper >变量,通过这个变量设置或获取当前线程的Looper实例。
  3. 在Looper内部维护一个MessageQueue变量,在loop()方法中无限循环查询queue,有消息就调用Message.target.handleMessage()。但是我们这里在取出一条消息处理之后就跳出循环了,这是为什么呢?不要着急,下面会详细解释,虽然只取一条,但是也足够展示在子线程更新UI的效果了。(真实中是无限循环的,本次只是为了展示效果,只设置了一次)
public class MyLooper {
private static ThreadLocal<MyLooper> sLooper = new ThreadLocal<>();

public MyMessageQueue queue;

public MyLooper() {
    queue = new MyMessageQueue();
}

public static void prepare() {
    if (sLooper.get() == null) {
        sLooper.set(new MyLooper());
    }
}

public static MyLooper getLooper() {
    return sLooper.get();
}

public static void loop() {
    if (sLooper.get() == null) {
        throw new RuntimeException("no looper exist");
    }
    while (true) {
        MyMessage myMessage = sLooper.get().queue.next();
        //Log.d("Debug", myMessage.toString());
        myMessage.target.handleMessage(myMessage);
        break;
    }
}
}
(5)效果展示

好了,现在我们在布局文件中放一个TextView显示Handler

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

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

</RelativeLayout>
(6)MainActivity
public class MainActivity extends AppCompatActivity {

    private TextView textView;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        textView = (TextView) findViewById(R.id.text);
    
        MyLooper.prepare();
    
        final MyHandler myHandler = new MyHandler() {
            @Override
            public void handleMessage(MyMessage message) {
                textView.setText("Handler");
            }
        };
    
        new Thread(new Runnable() {
            @Override
            public void run() {
                myHandler.sendMessage(new MyMessage());
            }
        }).start();
        MyLooper.loop();
    }

}
(7)疑点解惑

虽然我们完成了子线程更新UI,但是还是有一些问题的,假设我们将Looper的loop方法设为无限循环,而不是取到一条消息就退出循环,那会怎么样?其实都不用试,想一下就知道那主线程就被阻塞了,无法响应用户操作,然后报ANR错误。这样另一个问题就出来了,那为什么主线程原本的Looper不会阻塞呢?他的源码里也是无限循环啊?接下来根据源码来说明一下这个问题。
大家都知道java程序有一个入口点public static void main(String[] args),那既然Android是用java写的,大家一直就不疑惑入口点在哪吗?其实android程序的入口点在ActivityThread这个类当中,代码如下

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

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

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

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


;