Android多线程消息处理机制(一) Looper、Thread专题

在处理android多线程消息机制时,会因为Handler而了解到有Looper这么个东西。有众多高手解释道:Looper是消息泵。它内部维护了一个消息队列,即MessageQueue。Looper的职责就是负责抽取MessageQueue中的消息让他去找宿主。
对于一些普通的程序开发工作而言,只需要了解Handler的用法即可,几乎接触不到Looper,更别说是MessageQueue。但是如果想以后能写出独特优秀的程序或深入了解别人写的框架,了解Looper的原理是必须的。
今天,我们一起来分析Looper的原理和用法,从源码开始分析吧。说到这,有人可能会说“哥们,能否别这么矫情,有必要千篇一律都开篇分析源码吗?”,我想说不要喷,你不看别人的源码,怎么知道其原理?光看别人告诉你的。万一别人理解的是错的呢?
开始吧,朋友。

先来一段Looper类头部的解释说明:

/**
  * 这是用于运行线程的消息循环的类。默认的线程没有与它们关联的消息循环;
  * 在运行循环的线程中,调动prepare方法可以创建一个Looper对象, 然后调动loop方法,循环处理它的
  * 消息队列中的消息,知道循环退出为止。
  * 与消息循环相关的大多数交互是通过Handler类处理的。
  * 下面是一个Looper线程上线的经典例子,
  * 使用prepare和loop两个方法来初始化一个含有Looper的线程,简称Looper线程。
  *
  * <pre>
  *  class LooperThread extends Thread {
  *      public Handler mHandler;
  *
  *      public void run() {
  *          Looper.prepare();
  *
  *          mHandler = new Handler() {
  *              public void handleMessage(Message msg) {
  *                  // process incoming messages here
  *              }
  *          };
  *
  *          Looper.loop();
  *      }
  *  }</pre>
  */

通过上面的这段解释和代码示例,了解到:
1、Looper是用来运行消息循环的。
2、默认创建的线程是没有Looper的,必须在线程中调用Looper.prepare()和Looper.loop()方法来创建Looper。
3、在这个消息循环中,有一个内置的MessageQueue消息队列。如果消息队列中没有消息,则该线程休眠,一旦有消息时,Looper开始工作。整个过程知道调用quit()方法终止。
4、一个Looper对象对应一个内置的消息队列,一对一关系。
*5、Main线程为什么可以修改处理消息,其实是因为Main线程中开启了Looper的消息循环:
此处暂时淡化更新UI的问题,最后再讨论。


Activity部分源码:

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
        Window.OnWindowDismissedCallback {
    
	
	ActivityThread mMainThread;
	
	
	// we must have a handler before the FragmentController is constructed
        final Handler mHandler = new Handler();
	
	
        //...
	
}

ActivityThread部分源码:

public final class ActivityThread {

    final Looper mLooper = Looper.myLooper();

    public static void main(String[] args) {
        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");
    }

}

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


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


Looper.prepare()方法源码:
/** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    public static void prepare() {
        prepare(true);
    }

搜嘎,在Activity中之所以可以处理消息,是因为其中已经初始化了Handler和一个含有Looper的线程。发送到UI的所有消息都由这个LooperThread专门处理。故像我前面分析的,普通的app开发工作中,我们多需要和UI交互处理,正好在Activity中已经有一个Looper线程了,因此在子线程中不需要再创建Looper循环了。也就是说一般我们只需要会用handler就够了,很少要你去处理Looper和MessageQueue。然而事已至此了,还是来看看Looper的源码,最后会附上一个例子,看看一个Looper线程的功效如何。

//这是个最终类,不能被继承
public final class Looper {
   
    //sThreadLocal.get()方法会返回null,除非你调用了prepare()方法。
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;  // guarded by Looper.class

    //这就是传说中的消息队列,有Looper自己管理
    final MessageQueue mQueue;
    final Thread mThread;


     /** 初始化当前线程的looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    public static void prepare() {
        prepare(true);
    }

    //每个线程只能创建一个looper对象
    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,标记其为应用程序的main looper。
     * 应用程序的主looper是由Android环境的创建, 所以你没有必要自己主动的调用此方法。
     * 请查看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();
        }
    }

    /**
     * 获取application UIThread的looper,加了线程类锁
     */
    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }

    /**
     * 在当前线程中运行消息队列循环,直到调用quit()方法结束循环。
     */
    public static void loop() {
        final Looper me = myLooper();//获取当前线程自己的looper
        if (me == null) {//如果looper为null,可能是prepare()方法没有调用
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        // 确保这个线程的身份是本地进程
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();


        //死循环,消息调度
        for (;;) {
            Message msg = queue.next(); // 此方法可能会阻塞
            if (msg == null) {
                // 如果没有消息关联到这个消息队列中时就退出
                return;
            }

            
            //此处是让message去找自己的宿主,后期
            msg.target.dispatchMessage(msg);

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }

    /**
     * 返回当前线程的looper对象,如果当前线程还没有关联上looper,可能会返回null。
     * 例如,当前线程还没有执行到Looper.prepare()方法时,你就调用了myLooper()方法。
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

    /**
     * 返回与当前线程关联的MessageQueue对象。
     * 调用此方法前必须确保当前线程已经运行了一个looper,
     * 否则必定NullPointerException登门拜访
     */
    public static @NonNull MessageQueue myQueue() {
        return myLooper().mQueue;
    }

    //在构造Looper的时候,内部构造了一个MessageQueue对象
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

    /**
     * 如果当前线程是looper线程,则返回true,否则返回false.
     */
    public boolean isCurrentThread() {
        return Thread.currentThread() == mThread;
    }

    /**
     * Control logging of messages as they are processed by this Looper.  If
     * enabled, a log message will be written to <var>printer</var>
     * at the beginning and ending of each message dispatch, identifying the
     * target Handler and message contents.
     *
     * @param printer A Printer object that will receive log messages, or
     * null to disable message logging.
     */
    public void setMessageLogging(@Nullable Printer printer) {
        mLogging = printer;
    }

    /**
     * 退出looper循环.
     * <p>
     * Causes the {@link #loop} method to terminate without processing any
     * more messages in the message queue.
     * </p><p>
     * Any attempt to post messages to the queue after the looper is asked to quit will fail.
     * For example, the {@link Handler#sendMessage(Message)} method will return false.
     * </p><p class="note">
     * Using this method may be unsafe because some messages may not be delivered
     * before the looper terminates.  Consider using {@link #quitSafely} instead to ensure
     * that all pending work is completed in an orderly manner.
     * </p>
     *
     * @see #quitSafely
     */
    public void quit() {
        mQueue.quit(false);
    }

    /**
     * 安全的退出消息循环
     * <p>
     * Causes the {@link #loop} method to terminate as soon as all remaining messages
     * in the message queue that are already due to be delivered have been handled.
     * However pending delayed messages with due times in the future will not be
     * delivered before the loop terminates.
     * </p><p>
     * Any attempt to post messages to the queue after the looper is asked to quit will fail.
     * For example, the {@link Handler#sendMessage(Message)} method will return false.
     * </p>
     */
    public void quitSafely() {
        mQueue.quit(true);
    }

    /**
     * 返回与当前线程关联的looper
     *
     * @return The looper's thread.
     */
    public @NonNull Thread getThread() {
        return mThread;
    }

    /**
     * 返回当前线程的looper的MessageQueue
     *
     * @return The looper's message queue.
     */
    public @NonNull MessageQueue getQueue() {
        return mQueue;
    }

    /**
     * Dumps the state of the looper for debugging purposes.
     *
     * @param pw A printer to receive the contents of the dump.
     * @param prefix A prefix to prepend to each line which is printed.
     */
    public void dump(@NonNull Printer pw, @NonNull String prefix) {
        pw.println(prefix + toString());
        mQueue.dump(pw, prefix + "  ");
    }

    @Override
    public String toString() {
        return "Looper (" + mThread.getName() + ", tid " + mThread.getId()
                + ") {" + Integer.toHexString(System.identityHashCode(this)) + "}";
    }
}

在阅读的过程中,重点关注代码中的中文注释,很多关键点都有说明,不要错过。
接下来做一个简单的looper线程实验,看看LooperThread的用:



MainActivity代码:

public class MainActivity extends Activity implements OnClickListener {
	private Button uiBtn, looperBtn, quitBtn;
	private TextView show;

	private MyLooperThread looperThread;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		uiBtn = (Button) findViewById(R.id.uiBtn);
		looperBtn = (Button) findViewById(R.id.looperBtn);
		quitBtn = (Button) findViewById(R.id.quitBtn);

		show = (TextView) findViewById(R.id.show);
		uiBtn.setOnClickListener(this);
		looperBtn.setOnClickListener(this);
		quitBtn.setOnClickListener(this);

		// 创建和启动looper线程
		looperThread = new MyLooperThread(this,show);
		looperThread.start();
	}

	@Override
	public void onClick(View v) {
		Button btn = (Button) v;
		switch (v.getId()) {
		case R.id.uiBtn:
			// 在ui中自己修改textview
			show.setText(btn.getText());
			break;

		case R.id.quitBtn:
			// 终止looper线程
			looperThread.getLooper().quit();
			break;

		case R.id.looperBtn:

			// 获取looper中的handler,发送消息让handler处理,更新ui
			looperThread.getHandler().obtainMessage(200, btn.getText()).sendToTarget();

			break;

		default:
			break;
		}
	}
}

MyLooperThread,含有Looper的线程代码:

/**
 * 用来处理looper循环的线程 用于测试,在looper线程中看能否修改ui
 *
 * @author duke
 */
public class MyLooperThread extends Thread {
    private static final String TAG = MyLooperThread.class.getSimpleName();
    // 子线程的looper
    private Looper myLooper;
    // 子线程的handler
    private Handler mHandler;

    // 用于测试的textview
    private TextView testView;

    private Activity activity;

    public Looper getLooper() {
        return myLooper;
    }

    public Handler getHandler() {
        return mHandler;
    }

    public MyLooperThread(Context context, TextView view) {
        this.activity = (Activity) context;
        testView = view;
    }

    @Override
    public void run() {
        super.run();
        // 调用了此方法后,当前线程拥有了一个looper对象
        Looper.prepare();
        Log.v(TAG, "消息循环开始");

        if (myLooper == null) {
            while (myLooper == null) {
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 调用此方法获取当前线程的looper对象
                myLooper = Looper.myLooper();
            }
        }

        // 当前handler与当前线程的looper关联
        mHandler = new Handler(myLooper) {
            @Override
            public void handleMessage(Message msg) {
                Log.v(TAG, "处理消息:" + msg.obj);

                //this thread's TextView
                addTextViewInChildThread().setText(String.valueOf(msg.obj));

                //main thread's TextView
                //testView.setText(String.valueOf(msg.obj));
            }
        };
        Looper.loop();
        Log.v(TAG, "looper消息循环结束,线程终止");
    }

    /**
     * 创建TextView
     * http://blog.csdn.net/imyfriend/article/details/6877959
     * @return
     */
    private TextView addTextViewInChildThread() {
        TextView textView = new TextView(activity);

        //ViewGroup.LayoutParams txparams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 

ViewGroup.LayoutParams.WRAP_CONTENT);
        //textView.setLayoutParams(txparams);

        textView.setBackgroundColor(Color.GRAY);
        textView.setGravity(Gravity.CENTER);
        textView.setTextSize(20);

        WindowManager windowManager = activity.getWindowManager();
        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.MATCH_PARENT,//width of textView
                WindowManager.LayoutParams.WRAP_CONTENT,//height of textView
                0, 0,//x,y of textView in screen
                WindowManager.LayoutParams.FIRST_SUB_WINDOW,
                WindowManager.LayoutParams.TYPE_TOAST,
                PixelFormat.TRANSPARENT);
        windowManager.addView(textView, params);

        return textView;
    }
}

xml页面代码:

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

    <Button
        android:id="@+id/uiBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="UI线程修改文本" />

    <Button
        android:id="@+id/looperBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/uiBtn"
        android:text="Looper线程修改文本" />

    <Button
        android:id="@+id/quitBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/looperBtn"
        android:text="Looper quit()" />

    <TextView
        android:id="@+id/show"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/quitBtn"
        android:gravity="center"
        android:text="显示结果"
        android:textSize="30sp" />

</RelativeLayout>

说明:
上例中,主要是自定义一个线程,在线程中调用prepare()和loop()方法,启动消息循环,让当前线程成为looper线程。那么这个线程就专门负责消息处理了。在其内部定义了一个handler对象,关联当前looper。在主线程中创建和启动looper线程对象,获取looper线程的handler发送消息。消息发出后就有handler关联的looper从其关联的MessageQueue中抽取并,派遣到宿主handler执行。由于handler是在looper线程中创建的,且有实现方法handleMessage,故在handleMessage方法执行消息。这也就是looper线程的用法,比较少见。上例中还有一个疑问,到底哪些线程可以更新ui?有人说是主线程,有人说只要是looper线程都可以。
这个问题,上例中隐约说明了:
1、从MainActivity中传递了textView的引用到looperThread中,在looperThread中尝试修改textView的text属性。以前我在较低的版本试过没问题;现在再是,仍然可以修改,只不过会抛异常CalledFromWrongThreadException,故我强行try catch了。
2、参考文章 http://blog.csdn.net/imyfriend/article/details/6877959和根据具体异常信息的提示,发现跟ui创建的位置有关。如果ui是在main线程创建的,则在子线程中不可以更改此ui;如果在含有looper的子线程中创建的ui,则可以任意修改。参见上例,针对这个问题以及ViewRoot问题,若有高见欢迎探讨。由于篇幅问题,不在此多聊了。




  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值