Android 进阶 - Looper进程内通讯

来源:互联网 发布:豆瓣fm mac下载 编辑:程序博客网 时间:2024/05/22 10:37
在Android 入门 -应用启动分析一文中,我们有两处并未详细介绍,Looper进程内通讯及Binder进程间通讯。现在我们来看看Android的进程内异步通讯机制。如果你做过界面(UI)应用,不论是Win32的还是Java的UI应用,或者是iOS的UI,都会涉及前端、后端,多线程等概念。在一个线程内的程序,是从上到下顺序执行,后面的代码必须等前面的代码执行完成才可以执行。但很多时候,一个应用需要处理多个场景,最常用的就是:后台的数据处理与前端的UI显示。在Win32应用里,一个线程处理完数据后,可以post消息给主窗口线程,显示数据。Android应用借用了这种机制,当然,你可以用自己的实现方式,但既然Android已经提供了,我们就应该直接拿来使用,这就是Looper。

1、ThreadLocal线程安全机制

要了解Looper,我们需要先深入理解Java的线程本地化存储机制(TLS),即ThreadLocal。这是Java提供的,非Android独有的,可以在任何Java程序中使用。
ThreadLocal有什么作用?和本地变量有什么区别?考查下面的代码:
public class ThreadLocalTest {final static ThreadLocal<Object> tl = new ThreadLocal<>();public static void main(String[] args) {tl.set(0);new Thread(new Runnable() {@Overridepublic void run() {System.out.println("thread:" + tl.get());}}).start();System.out.println("main: " + tl.get());}}
上面的代码中,我们将tl设置为静态变量,按正常思维,在Main中set了,在Thread中get,应该是有值的。但事实上不是,thread中是get不到这个值的。这就说明,你在哪个线程set了值,就只能在哪个线程中get。
所以,ThreadLocal保存的就是线程的本地变量,这个我在Thead内new一个成员变量就行了,这不是多此一举吗?事实上不是这样的,ThreadLocal可以确保保存在线程中的变量不会被另一个线程修改,而成员变量则可以被修改,这对线程安全中防止意外访问的情况发生非常有用。下面举例说明:
import java.util.ArrayList;import java.util.List;class MyRunnable implements Runnable {// 千万别被static关键字蒙蔽了,tl可以全局使用,但tl.set,tl.get只能是当前线程使用final static ThreadLocal<Object> tl = new ThreadLocal<>();Object t;public MyRunnable() {}@Overridepublic void run() {t = Math.random();tl.set(t);Object pt = null, ptl = null;while (Thread.currentThread().isAlive()) {if (pt == null || !pt.equals(t)) // 如果t变量变化System.out.println(this + ": t " + pt + " -> " + t);if (ptl == null || !ptl.equals(tl.get())) // 如果tl.get()变化System.out.println(this + ": tl " + ptl + "->" + tl.get());pt = t;ptl = tl.get();}}public String toString() {return Thread.currentThread().getName();}public Object getTL() {return tl.get();}public Object getT() {return t;}public void setTL(Object v) {tl.set(v);}public void setT(Object v) {t = v;}}public class ThreadLocalTest {public static void main(String[] args) {List<MyRunnable> ls = new ArrayList<MyRunnable>();for (int i = 0; i < 2; i++) {MyRunnable thread = new MyRunnable();ls.add(thread);new Thread(thread, "thread-" + i).start();}try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("--------");for (int i = 0; i < 2; i++) {MyRunnable thread = ls.get(i);thread.setT("new-" + i);thread.setTL("new-" + i);}}}
上面例子的运行结果是:
thread-0: t null -> 0.5093053224347734thread-0: tl null->0.5093053224347734thread-1: t null -> 0.7972271387384761thread-1: tl null->0.7972271387384761--------thread-0: t 0.5093053224347734 -> new-0thread-1: t 0.7972271387384761 -> new-1
可以看到,调用thread.setT方法时,会修改线程内部的t值,但调用thread.setTL时,不会修改线程内部的tl.get()值?为什么呢?其实,thread.setTL修改的是main线程的tl.get()值,thread.setTL是在main线程中运行,不是在真正的thread中运行的,所以,ThreadLocal可以确保线程内部的变量,无论在任何情况下,不被其他线程修改,这点成员变量做不到,只要引出方法,就可以被任意修改,你必须要做同步处理。网上的资料基本未说到点子上,看的我云里雾里。

2. Looper消息机制

Looper是一个可以处理消息循环的类,任何线程都可以使用Looper,来处理消息。基本使用方法如下:
class LooperThread extends Thread {    public Handler mHandler;    public void run() {        Looper.prepare();        mHandler = new Handler() {            public void handleMessage(Message msg) {                // 真正的消息在这里处理            }        };        Looper.loop();    }}
如果不准备了解内部机制的话,上面这段程序,就是一个简单的Looper实例,但上面的代码无任何意义,因为是顺序执行的,所有的处理都是在当前线程中处理,根本用不着消息这种异步处理机制。为什么呢?因为Looper.myLooper()只能得到当前线程的Looper,见代码:
frameworks/base/core/java/android/os/Looper.java
public final class Looper {    private static final String TAG = "Looper";    // sThreadLocal.get() will return null unless you've called prepare().    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();    private static Looper sMainLooper;  // guarded by Looper.class...    public static void prepare() {        prepare(true);    }    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));    }    ...    public static void prepareMainLooper() {        prepare(false);        synchronized (Looper.class) {            if (sMainLooper != null) {                throw new IllegalStateException("The main Looper has already been prepared.");            }            sMainLooper = myLooper();        }    }    /** Returns the application's main looper, which lives in the main thread of the application.     */    public static Looper getMainLooper() {        synchronized (Looper.class) {            return sMainLooper;        }    }    // 消息处理    public static void loop() {        final Looper me = myLooper();        if (me == null) {            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");        }        final MessageQueue queue = me.mQueue;        // Make sure the identity of this thread is that of the local process,        // and keep track of what that identity token actually is.        Binder.clearCallingIdentity();        final long ident = Binder.clearCallingIdentity();        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            Printer logging = me.mLogging;            if (logging != null) {                logging.println(">>>>> Dispatching to " + msg.target + " " +                        msg.callback + ": " + msg.what);            }            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.recycle();        }    }...    // 获取当前线程的Looper    public static Looper myLooper() {        return sThreadLocal.get();    }...// 构造函数    private Looper(boolean quitAllowed) {        mQueue = new MessageQueue(quitAllowed);        mThread = Thread.currentThread();    }    ...}
从上面的代码可以看出,sThreadLocal就是一个线程本地存储变量,prepare(),loop()都是处理sThreadLocal.get()得到的Looper变量,只对当前线程有效,所以,前面的代码是无法异步处理的, 也就失去了存在的价值。

那么怎么样才真正有用呢?一定要有一个线程安全的队列,可以从另一个线程发消息, 这样Looper.loop()才是有用的。所以,安卓提供了HandlerThread类,HandlerThread保存了线程的Looper,其他线程可以getLooper()来获取此Looper,并向该线程发送消息。绝大多数时候,我们应该直接使用HandlerThread类。

下面用序列图说明Looper消息机制:


从上图可以看出,消息(Message)其实是通过消息队列(MessageQueue)来中转的,Looper负责轮循消息队列中的消息,如果取到一个消息,则调用msg.target.dispatchMessage来处理消息,msg.target是一个基于Handler或其派生类的实例。调用线程则通过调用Handler.sendMessage发送消息,而Handler.sendMessage最终是用MessageQueue.enqueueMessage来完成消息的入队。

下面是与Looper相关的类图:


3. 例子

3.1 HandlerThread

这个例子很简单,创建一个基于HandlerThread的后台服务线程,接收来自主线程的按钮点击,如果是button1点击了,则打印“button1clicked”,如果是button2点击了,则打印了“button2 clicked”。
public class MainActivity extends ActionBarActivity {static class MyHandler extends Handler {public MyHandler(Looper looper) {super(looper);}@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case 1:System.out.println("button1 clicked");break;case 2:System.out.println("button2 clicked");break;}}}static class MyThread extends HandlerThread {MainActivity activity;public MyThread(MainActivity activity, String name) {super(name);this.activity = activity;}@Overrideprotected void onLooperPrepared() {// Looper准备好之后,创建Handler,并传入当前线程的looper。主线程通过此handler发送消息activity.handler = new MyHandler(Looper.myLooper());}}MyHandler handler;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 创建线程new MyThread(this, "LoopThread").start();if (savedInstanceState == null) {getSupportFragmentManager().beginTransaction().add(R.id.container, new PlaceholderFragment(this)).commit();}}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {// Inflate the menu; this adds items to the action bar if it is present.getMenuInflater().inflate(R.menu.main, menu);return true;}@Overridepublic boolean onOptionsItemSelected(MenuItem item) {// Handle action bar item clicks here. The action bar will// automatically handle clicks on the Home/Up button, so long// as you specify a parent activity in AndroidManifest.xml.int id = item.getItemId();if (id == R.id.action_settings) {return true;}return super.onOptionsItemSelected(item);}public void sendMessage(int what) {if (handler != null){handler.sendEmptyMessage(what);}}/** * A placeholder fragment containing a simple view. */public static class PlaceholderFragment extends Fragment {MainActivity activity;public PlaceholderFragment(MainActivity activity) {this.activity = activity;}@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {View rootView = inflater.inflate(R.layout.fragment_main, container, false);//侦听按钮点击事件Button button = (Button) rootView.findViewById(R.id.button1);button.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {//button1发送消息activity.sendMessage(1);}});button = (Button) rootView.findViewById(R.id.button2);button.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {//button2发送消息activity.sendMessage(2);}});return rootView;}}}
上面的程序很简单,这里不做详细解释。

3.2 ActivityThread

在Android 入门 - 应用启动分析中已经提到,当SystemServer fork一个进程之后,会进入ActivityThread.main函数。这个main函数本身有消息处理,但他用的是Looper.prepareMainLooper()。所以,在Activity应用里,我们可以随意通过Looper.getMainLooper()向主线程发送消息。
注意,由于是主线程一启动就调用了Looper.prepareMainLooper(),所有其他线程都不应该调用这个函数,否则,会抛出“Themain Looper has already been prepared.”的异常。
public class MainActivity extends ActionBarActivity {static class MyHandler extends Handler {public MyHandler() {super();}@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case 1:System.out.println("button1 clicked");break;case 2:System.out.println("button2 clicked");break;}}}// 不带参数的构造器,则取当前线程的Looper,即主线程的Looper。MyHandler handler = new MyHandler();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);if (savedInstanceState == null) {getSupportFragmentManager().beginTransaction().add(R.id.container, new PlaceholderFragment(this)).commit();}}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {// Inflate the menu; this adds items to the action bar if it is present.getMenuInflater().inflate(R.menu.main, menu);return true;}@Overridepublic boolean onOptionsItemSelected(MenuItem item) {// Handle action bar item clicks here. The action bar will// automatically handle clicks on the Home/Up button, so long// as you specify a parent activity in AndroidManifest.xml.int id = item.getItemId();if (id == R.id.action_settings) {return true;}return super.onOptionsItemSelected(item);}public void sendMessage(int what) {if (handler != null)handler.sendEmptyMessage(what);}/** * A placeholder fragment containing a simple view. */public static class PlaceholderFragment extends Fragment {MainActivity activity;public PlaceholderFragment(MainActivity activity) {this.activity = activity;}@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {View rootView = inflater.inflate(R.layout.fragment_main, container, false);Button button = (Button) rootView.findViewById(R.id.button1);button.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {activity.sendMessage(1);}});button = (Button) rootView.findViewById(R.id.button2);button.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {activity.sendMessage(2);}});return rootView;}}}

上面的代码,更为简单,由此可见,如果你想向主线程发送消息是非常简单的,new一个你自己的Handler,并在你自己的Handler里处理消息即可。

3.3 Callback例子

上面两个例子,都是需要自己派生Handle类,并重载handleMessage函数,来完成消息处理,下面说另一种实现方式:
public class MainActivity extends ActionBarActivity {static class MyCallback implements android.os.Handler.Callback {public MyCallback() {super();}@Overridepublic boolean handleMessage(Message msg) {switch (msg.what) {case 1:System.out.println("button1 clicked");return true;case 2:System.out.println("button2 clicked");return true;default:return false; // 返回未处理,Handle会继续他的默认处理}}}Handler handler = new Handler(new MyCallback());@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);if (savedInstanceState == null) {getSupportFragmentManager().beginTransaction().add(R.id.container, new PlaceholderFragment(this)).commit();}}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {// Inflate the menu; this adds items to the action bar if it is present.getMenuInflater().inflate(R.menu.main, menu);return true;}@Overridepublic boolean onOptionsItemSelected(MenuItem item) {// Handle action bar item clicks here. The action bar will// automatically handle clicks on the Home/Up button, so long// as you specify a parent activity in AndroidManifest.xml.int id = item.getItemId();if (id == R.id.action_settings) {return true;}return super.onOptionsItemSelected(item);}public void sendMessage(int what) {if (handler != null)handler.sendEmptyMessage(what);}/** * A placeholder fragment containing a simple view. */public static class PlaceholderFragment extends Fragment {MainActivity activity;public PlaceholderFragment(MainActivity activity) {this.activity = activity;}@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {View rootView = inflater.inflate(R.layout.fragment_main, container, false);Button button = (Button) rootView.findViewById(R.id.button1);button.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {activity.sendMessage(1);}});button = (Button) rootView.findViewById(R.id.button2);button.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {activity.sendMessage(2);}});return rootView;}}}
上面的代码通过Callback接口来处理消息,Callback的优先级高于Handler的handleMessage,当Callack.handleMessage返回false时,会执行Handler.handleMessage,继续处理消息。

3.4 postMessage例子

public class MainActivity extends ActionBarActivity {Handler handler = new Handler();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);if (savedInstanceState == null) {getSupportFragmentManager().beginTransaction().add(R.id.container, new PlaceholderFragment(this)).commit();}}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {// Inflate the menu; this adds items to the action bar if it is present.getMenuInflater().inflate(R.menu.main, menu);return true;}@Overridepublic boolean onOptionsItemSelected(MenuItem item) {// Handle action bar item clicks here. The action bar will// automatically handle clicks on the Home/Up button, so long// as you specify a parent activity in AndroidManifest.xml.int id = item.getItemId();if (id == R.id.action_settings) {return true;}return super.onOptionsItemSelected(item);}public void sendMessage(Runnable callback) {if (handler != null) {handler.post(callback);}}/** * A placeholder fragment containing a simple view. */public static class PlaceholderFragment extends Fragment {MainActivity activity;public PlaceholderFragment(MainActivity activity) {this.activity = activity;}@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {View rootView = inflater.inflate(R.layout.fragment_main, container, false);Button button = (Button) rootView.findViewById(R.id.button1);button.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {activity.sendMessage(new Runnable() {@Overridepublic void run() {System.out.println("button1 clicked");}});}});button = (Button) rootView.findViewById(R.id.button2);button.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {activity.sendMessage(new Runnable() {@Overridepublic void run() {System.out.println("button2 clicked");}});}});return rootView;}}}

上面的代码,是直接异步执行一个Runnable,作用相当于new Thread(new Runnable(){...}).start(),但不用再新启动一个线程,这个Runnable会在Looper线程中执行。

4. 结语

其实,在进程内的通讯对于一个程序员高手来说,很容易搞定,因为方法太多了,Looper也只是提供一个可供我们使用的方法而已,没必要受此拘束,之所以要详细了解这个,是因为Android的源码中,有许多这样的代码,如果不了解清楚,你很难搞清楚Android源码中的逻辑。
当然,既然有了Looper,我们拿来用也是对的。Handler还提供runWithScissors方法,此方法可以异步运行一个Runnable,并等待Runnable运行结束再返回,这对某些需要等待任务结束的场景非常有用,很遗憾,此方法被隐藏。有些需要的,可以仿照源码,自己再实现这个方法。
另外,本文并未详细讲述消息处理完后,如何获取消息回复,要做这点太容易了,无论是handleMessage还是Runnable的消息处理代码,都是在我们自己的代码中实现的,只要在适当的地方加点代码,就可以得到回复和处理结果了。

0 0