編輯:關於Android編程
Android 開發過程中,遇到了冗長的耗時的操作,亦或是為了使得代碼結構更加清晰,或者是要動態的更新UI。一言不合就上Handler,這裡不討論java編程時的一些多線程模型,只探討一下Android中提供給開發者使用的Handler。
關於Handler/Looper/Message之間的關系,上一篇博文有較為詳細的描述。這裡打算說人話,對照代碼來解釋一遍。
new Handler對象
Handler myHandler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_WHAT: //do something break; } super.handleMessage(msg); } };
自定義Thread
class myThread implements Runnable { public void run() { while (!Thread.currentThread().isInterrupted()) { Message message = new Message(); message.what = MESSAGE_WHAT; myHandler.sendMessage(message); try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } }
通過上面兩步,通常情況下,我們就可以正常的使用Handler了。但,這不是本文的目的;不是告訴你用法就行了,再說了這些東西Google一下立馬就出來很多,說不定比我的還好。打算在深入一下,到底為什麼可以這樣做!
使用Handler作為子線程更新UI可能是Handler用法中最為常用、經典的了。但是問題是為什麼要這麼做?為什麼是我?其他人呢(其他方式)?
如果不在UI線程中更新,而新開啟一個線程處理邏輯,然後在子線程中更新UI線程;會面臨線程安全問題,再說了,Android壓根兒就不讓你這麼高。吧所有的操作都放在UI線程中到不是不可能,但當遇到高耗時的操作時,你能等得了?程序會崩潰的呀。。所以,還是Handler大法好。常見的更新UI的方法有如下幾種
Handler.post(Runnable) Handler.sendMessage() View.post(Runnable) AsyncTask Activity.runOnUiThread()
會用到的文件
//frameworks/base/core/java/android/app/ActivityThread.java //frameworks/base/core/java/android/app/Activity.java //frameworks/base/core/java/android/os/Looper.java //frameworks/base/core/java/android/os/Handler.java //frameworks/base/core/java/android/os/HandlerThread.java //frameworks/base/core/java/android/os/Message.java //frameworks/base/core/java/android/view/View.java //frameworks/base/core/java/android/view/ViewRootImpl.java //frameworks/base/core/java/android/view/WindowManagerGlobal.java
有關其他的方式,這兒不多說啦。主要看下View.post(Runnable)方法
/** *
Causes the Runnable to be added to the message queue. * The runnable will be run on the user interface thread.
* * @param action The Runnable that will be executed. * * @return Returns true if the Runnable was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. * * @see #postDelayed * @see #removeCallbacks */ public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return attachInfo.mHandler.post(action); } // Assume that post will succeed later ViewRootImpl.getRunQueue().post(action); return true; }看不論是attachInfo.mHandler.post(action)還是ViewRootImpl.getRunQueue().post(action)最後都調用的是Handler.post getRunQueue()最終的實現
//Execute enqueued actions on every traversal in case a detached view enqueued an action getRunQueue().executeActions(mAttachInfo.mHandler);
如果是主線程,直接更新;如果不是,使用Handler更新。
/** * Runs the specified action on the UI thread. If the current thread is the UI * thread, then the action is executed immediately. If the current thread is * not the UI thread, the action is posted to the event queue of the UI thread. * * @param action the action to run on the UI thread */ public final void runOnUiThread(Runnable action) { if (Thread.currentThread() != mUiThread) { mHandler.post(action); } else { action.run(); } }
通過上一篇文章,我們知道Handler和Looper之間是1對1的關系。問題是,我們在自定義的代碼中實現了Handler,可是Looper在哪兒?這裡區分一下UI線程(主線程)、非UI線程
這種ActivityThread.java中的main函數。看到了Looper.prepareMainLooper();和Looper.loop()了吧!不多說
public static void main(String[] args) { //...... Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } // End of event ActivityThreadMain. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }
子線程基本上可以看做是自定義的線程;這就屬於千變萬化的了,你不能在指望ActivityThread幫你干活了,此時就要自己動手豐衣足食。怎麼搞呢!當然是參照線程的東西了(ActivityThread中的實現)。
第一步: prepare()
第二部: loop()
為啥不是prepareMainLooper 而是 prepare() The main looper for your application is created by the Android environment, so you should never need to call this function yourself
典型代碼如下所示:
package cp.com.clarify; import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; //import android.support.v7.app.AlertDialog; import android.graphics.Color; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; import android.view.View; import android.widget.Button; public class MainActivity extends /*AppCompatActivity*/ Activity{ private final static String TAG="MainActivity"; private TestThread mTestThread; Intent intent; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); intent = new Intent(getApplicationContext(), Main2Activity.class); mTestThread = new TestThread(); mTestThread.start(); mTestThread.getHandler().sendEmptyMessage(1); final Button mBtn = (Button)findViewById(R.id.button2); new Thread(new Runnable() { @Override public void run() { Log.e(TAG,""+this); Log.e(TAG,"-----------------> set toolbar to RED <------------------------"); mBtn.setBackgroundColor(Color.RED); } }).start(); new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(5000);//注意這裡啊,1000000點傷害。。 } catch (InterruptedException e) { e.printStackTrace(); } Log.e(TAG,"-----------------> set toolbal to Green <------------------------"); Log.e(TAG,""+this); mBtn.setBackgroundColor(Color.GREEN); } }).start(); } class TestThread extends Thread { private Handler mHandler; private final Object mLock = new Object(); public void run() { Looper.prepare(); synchronized (mLock) { mHandler = new Handler(){ @Override public void handleMessage(Message msg) { //..... switch(msg.what){ case 1: Log.d(TAG,"================"); break; default: break; } } }; mLock.notifyAll(); } Looper.loop(); } public Handler getHandler() { synchronized (mLock) { if (mHandler == null) { try { mLock.wait(); } catch (InterruptedException e) { } } return mHandler; } } public void exit() { getHandler().post(new Runnable(){ public void run() { Looper.myLooper().quit(); }}); } } @Override protected void onResume() { Log.e(TAG,"-----------------> onResume <------------------------"); super.onResume(); } @Override protected void onStop() { Log.e(TAG,"-----------------> onStop <------------------------"); super.onStop(); } @Override protected void onPostCreate(Bundle savedInstanceState) { Log.e(TAG,"-----------------> onPostCreate <------------------------"); super.onPostCreate(savedInstanceState); } @Override protected void onPostResume() { Log.e(TAG,"-----------------> onPostResume <------------------------"); super.onPostResume(); } @Override protected void onDestroy() { Log.e(TAG,"-----------------> onDestroy <------------------------"); super.onDestroy(); } @Override protected void onPause() { Log.e(TAG,"-----------------> onPause <------------------------"); super.onPause(); } @Override protected void onRestart() { Log.e(TAG,"-----------------> onRestart <------------------------"); super.onRestart(); } }
最後的運行結果顯示:Button的顏色是紅色的;這說明:子線程是可以更新UI的,但是過了5s之後程序掛了,這好像有說明子線程是不能更新UI的,出現了如下報錯;到底是要鬧哪樣???
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6556) at android.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:942) at android.view.ViewGroup.invalidateChild(ViewGroup.java:5084) at android.view.View.invalidateInternal(View.java:12724) at android.view.View.invalidate(View.java:12660) at android.view.View.invalidateDrawable(View.java:16805) at android.widget.TextView.invalidateDrawable(TextView.java:5408) at android.graphics.drawable.Drawable.invalidateSelf(Drawable.java:385) at android.graphics.drawable.ColorDrawable.setColor(ColorDrawable.java:136) at android.view.View.setBackgroundColor(View.java:17196) at cp.com.clarify.MainActivity$3.run(MainActivity.java:76) at java.lang.Thread.run(Thread.java:818)
分析一下這份log我們發現,異常是在ViewRootImpl.java的checkThread中拋出的;是不是有點兒詭異?第一個Thread竟然沒有checkThread,第二個Thread竟然checkTread?來來跟著我的思路,這是不是說明,第一個Thread的時候壓根兒就沒有ViewRootImpl,到了第二個Thread(mBtn.setBackgroundColor(Color.GREEN);)的時候ViewRootImpl給創建了!O(∩_∩)O哈哈~機智如我!!
搜了一下這方面的資料。發現還真是這樣的。。
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume{ // If we are getting ready to gc after going to the background, well // we are back active so skip it. // TODO Push resumeArgs into the activity for consideration if (r != null) { final Activity a = r.activity; if (localLOGV) Slog.v( TAG, "Resume " + r + " started activity: " + a.mStartedActivity + ", hideForNow: " + r.hideForNow + ", finished: " + a.mFinished); // If the window hasn't yet been added to the window manager, // and this guy didn't finish itself or start another activity, // then go ahead and add the window. if (r.window == null && !a.mFinished && willBeVisible) { r.window = r.activity.getWindow(); View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); //這裡。。這裡 start ViewManager wm = a.getWindowManager(); //這裡。。這裡 end WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit; if (a.mVisibleFromClient) { a.mWindowAdded = true; wm.addView(decor, l);//這裡。。這裡。。 } // If the window has already been added, but during resume // we started another activity, then don't yet make the // window visible. } else if (!willBeVisible) { if (localLOGV) Slog.v( TAG, "Launch " + r + " mStartedActivity set"); r.hideForNow = true; }
看下addView的實現
WindowManagerGlobal.java
root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams);
好了。。
至於 View, Windows, WindowManager, WindowManagerImpl, View,DecorView, ViewGroup, View,ViewRoot,ViewGroup,ViewRoot,WindowManagerGlobal DecorView之間的關系,抽時間在分析。
本人使用Win8系統時間久了系統垃圾一大堆 ,後來重裝了Win8系統,再用ADT(adt-bunlde-windows),總會出現ddms初始化錯誤,logcat也無法
最近在機頂盒上做一個gridview,其焦點需要在item的子控件上,但gridview的焦點默認在item上,通過android:descendantFocusabil
-當窗口工具通過Analyse | Inspect Code方式打開以後,你可以通過這種方式訪問此窗口工具。-當點擊關閉按鈕關閉工具窗口以後,只能通過Analyze |
一、debounce僅在過了一段指定的時間還沒發射數據時才發射一個數據,Debounce操作符會過濾掉發射速率過快的數據項。注:這個操作符會會接著最後一項數據發射原始Ob