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。
ThreadLocal有什么作用?和本地变量有什么区别?考查下面的代码:
所以,ThreadLocal保存的就是线程的本地变量,这个我在Thead内new一个成员变量就行了,这不是多此一举吗?事实上不是这样的,ThreadLocal可以确保保存在线程中的变量不会被另一个线程修改,而成员变量则可以被修改,这对线程安全中防止意外访问的情况发生非常有用。下面举例说明:
frameworks/base/core/java/android/os/Looper.java
注意,由于是主线程一启动就调用了Looper.prepareMainLooper(),所有其他线程都不应该调用这个函数,否则,会抛出“Themain Looper has already been prepared.”的异常。
上面的代码,是直接异步执行一个Runnable,作用相当于new Thread(new Runnable(){...}).start(),但不用再新启动一个线程,这个Runnable会在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
- Android 进阶 - Looper进程内通讯
- Android进程 Handler Message Looper
- Android进程 Handler Message Looper
- 进程间通讯[android]
- Android进程间通讯
- Android进程间通讯
- Android进程通讯
- android 非主线程内使用Looper
- android aidl 进程间通讯
- android进程间通讯方式
- android IPC 进程间通讯
- Android进程内通信
- Android进阶知识点(AsyncTask,Looper、Handler和HandlerThread)
- Android 进阶 - 进程启动分析
- 局域网内android设备发现及通讯
- ZMQ源码分析(七) --进程内通讯
- Android IPC进程间通讯机制
- Android IPC进程间通讯机制
- 心急的C小加
- 电子签章(Electronic Signature)在C#中的实现方法
- 理解 Android Build 系统
- VS2008下使用CppSQLite3访问xgs黑名单表(SQLite数据库)
- 酒店点餐系统c语言版
- Android 进阶 - Looper进程内通讯
- 营销功能总结,主要是元素操作
- java经典实例
- 酷图一
- NYOJ 303 序号互换
- teamviewer
- mysql存储过程 预处理变量
- 利用开源ZXing库,在android上进行二维码简单的编码和解码
- R语言常见问题(持续更新整理)