在上一篇文章Android 面試之必問Java基礎一文中,咱們介紹了Java面試的一些常見的基礎面試題,下面咱們來介紹Android開發的一些必問知識點。java
正常狀況系,Activity會經歷以下幾個階段:android
對於生命週期,一般還會問以下的一些問題:git
Activity的啓動模式有四種:Standard、SingleTop、SingleTask和SingleInstance。github
在理解Activity的啓動流程以前,先讓咱們來看一下Android系統啓動流程。總的來講,Android系統啓動流程的主要經歷init進程 -> Zygote進程 –> SystemServer進程 –> 各類系統服務 –> 應用進程等階段。面試
參考:
Android系統啓動流程之init進程啓動
Android系統啓動流程之Zygote進程啓動
Android系統啓動流程之SystemServer進程啓動
Android系統啓動流程之Launcher進程啓動json
Launcher進程啓動後,就會調用Activity的啓動了。首先,Launcher會調用ActivityTaskManagerService,而後ActivityTaskManagerService會調用ApplicationThread,而後ApplicationThread再經過ActivityThread啓動Activity,完整的分析能夠參考Android 之 Activity啓動流程canvas
Fragment,是Android 3.0(API 11)提出的,爲了兼容低版本,support-v4庫中也開發了一套Fragment API,最低兼容Android 1.6,若是要在最新的版本中使用Fragment,須要引入AndroidX的包。小程序
相比Activity,Fragment具備以下一些特色:segmentfault
Fragment有以下幾個核心的類:數組
Fragment必須是依存於Activity而存在的,所以Activity的生命週期會直接影響到Fragment的生命週期。相比Activity的生命週期,Fragment的生命週期以下所示。
以下圖所示。
下面是Activity的生命週期和Fragment的各個生命週期方法的對應關係。
首先,在Fragment中定義接口,並讓Activity實現該接口,以下所示。
public interface OnFragmentInteractionListener { void onItemClick(String str); }
而後,在Fragment的onAttach()中,將參數Context強轉爲OnFragmentInteractionListener對象傳遞過去。
public void onAttach(Context context) { super.onAttach(context); if (context instanceof OnFragmentInteractionListener) { mListener = (OnFragmentInteractionListener) context; } else { throw new RuntimeException(context.toString() + " must implement OnFragmentInteractionListener"); } }
在建立Fragment的時候,能夠經過setArguments(Bundle bundle)方式將值傳遞給Activity,以下所示。
public static Fragment newInstance(String str) { FragmentTest fragment = new FragmentTest(); Bundle bundle = new Bundle(); bundle.putString(ARG_PARAM, str); fragment.setArguments(bundle);//設置參數 return fragment; }
Service的啓動方式主要有兩種,分別是startService和bindService。
其中,StartService使用的是同一個Service,所以onStart()會執行屢次,onCreate()只執行一次,onStartCommand()也會執行屢次。使用bindService啓動時,onCreate()與onBind()都只會調用一次。
使用startService啓動時是單獨開一個服務,與Activity沒有任何關係,而bindService方式啓動時,Service會和Activity進行綁定,當對應的activity銷燬時,對應的Service也會銷燬。
下圖是startService和bindService兩種方式啓動Service的示意圖。
public class TestOneService extends Service{ @Override public void onCreate() { Log.i("Kathy","onCreate - Thread ID = " + Thread.currentThread().getId()); super.onCreate(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i("Kathy", "onStartCommand - startId = " + startId + ", Thread ID = " + Thread.currentThread().getId()); return super.onStartCommand(intent, flags, startId); } @Nullable @Override public IBinder onBind(Intent intent) { Log.i("Kathy", "onBind - Thread ID = " + Thread.currentThread().getId()); return null; } @Override public void onDestroy() { Log.i("Kathy", "onDestroy - Thread ID = " + Thread.currentThread().getId()); super.onDestroy(); } }
bindService啓動的服務和調用者之間是典型的Client-Server模式。調用者是client,Service則是Server端。Service只有一個,但綁定到Service上面的Client能夠有一個或不少個。bindService啓動服務的生命週期與其綁定的client息息相關。
1,首先,在Service的onBind()方法中返回IBinder類型的實例。
2,onBInd()方法返回的IBinder的實例須要可以返回Service實例自己。
如今,因爲系統API的限制,一些常見的不被殺死Service方式已通過時,好比下面是以前的一些方式。
調用Context.startService方式啓動Service時,若是Android面臨內存匱乏,可能會銷燬當前運行的Service,待內存充足時能夠重建Service。而Service被Android系統強制銷燬並再次重建的行爲依賴於Service的onStartCommand()方法的返回值,常見的返回值有以下一些。
START_NOT_STICKY:若是返回START_NOT_STICKY,表示當Service運行的進程被Android系統強制殺掉以後,不會從新建立該Service。
START_STICKY:若是返回START_STICKY,表示Service運行的進程被Android系統強制殺掉以後,Android系統會將該Service依然設置爲started狀態(即運行狀態),可是再也不保存onStartCommand方法傳入的intent對象,即獲取不到intent的相關信息。
START_REDELIVER_INTENT:若是返回START_REDELIVER_INTENT,表示Service運行的進程被Android系統強制殺掉以後,與返回START_STICKY的狀況相似,Android系統會將再次從新建立該Service,並執行onStartCommand回調方法,可是不一樣的是,Android系統會再次將Service在被殺掉以前最後一次傳入onStartCommand方法中的Intent再次保留下來並再次傳入到從新建立後的Service的onStartCommand方法中,這樣咱們就能讀取到intent參數。
BroadcastReceiver,廣播接收者,它是一個系統全局的監聽器,用於監聽系統全局的Broadcast消息,因此它能夠很方便的進行系統組件之間的通訊。BroadcastReceiver屬於系統級的監聽器,它擁有本身的進程,只要存在與之匹配的Broadcast被以Intent的形式發送出來,BroadcastReceiver就會被激活。
和其餘的四大組件同樣,BroadcastReceiver也有本身獨立的聲明週期,可是它又和Activity、Service不一樣。當在系統註冊一個BroadcastReceiver以後,每次系統以一個Intent的形式發佈Broadcast的時候,系統都會建立與之對應的BroadcastReceiver廣播接收者實例,並自動觸發它的onReceive()方法,當onReceive()方法被執行完成以後,BroadcastReceiver的實例就會被銷燬。
從不一樣的緯度區分,BroadcastReceiver能夠分爲不一樣的類別。
廣播的註冊分爲靜態註冊和動態註冊。靜態註冊是在Mainfest清單文件中進行註冊,好比。
<receiver android:name=".MyBroadcastReceiver" android:exported="true"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED"/> <action android:name="android.intent.action.INPUT_METHOD_CHANGED" /> </intent-filter> </receiver>
動態註冊是在代碼中,使用registerReceiver方法代碼進行註冊,好比。
val br: BroadcastReceiver = MyBroadcastReceiver() val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION).apply { addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED) } registerReceiver(br, filter)
而後,咱們使用sendBroadcast方法發送廣播。
Intent().also { intent -> intent.setAction("com.example.broadcast.MY_NOTIFICATION") intent.putExtra("data", "Notice me senpai!") sendBroadcast(intent) }
發送廣播的時候,咱們會添加一個發送的標識,那麼接收的時候使用這個標識接收便可。接收廣播須要繼承BroadcastReceiver,並重寫onReceive回調方法接收廣播數據。
private const val TAG = "MyBroadcastReceiver" class MyBroadcastReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { StringBuilder().apply { append("Action: ${intent.action}\n") append("URI: ${intent.toUri(Intent.URI_INTENT_SCHEME)}\n") toString().also { log -> Log.d(TAG, log) Toast.makeText(context, log, Toast.LENGTH_LONG).show() } } } }
ContentProvider是Android四大組件之一,不過平時使用的機會比較少。若是你看過它的底層源碼,那麼就應該知道ContentProvider是經過Binder進行數據共享。所以,若是咱們須要對第三方應用提供數據,能夠考慮使用ContentProvider實現。
Android自己的View體系很是龐大的,若是要徹底弄懂View的原理是很困難的,咱們這裏撿一些比較重要的概念來給你們講解。
Android View自己的繪製流程須要通過measure測量、layout佈局、draw繪製三個過程,最終纔可以將其繪製出來並展現在用戶面前。
首先,咱們看一下Android的MeasureSpec,Android的MeasureSpec分爲3中模式,分別是EXACTLY、AT_MOST 和 UNSPECIFIED,含義以下。
相關文章:談談對 MeasureSpec 的理解
Android的事件分發由dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent三個方法構成。
在Android系統中,擁有事件傳遞處理能力的類有如下三種:
在事件分發中,有時候會問:ACTION_CANCEL何時觸發,觸摸button而後滑動到外部擡起會觸發點擊事件嗎,再滑動回去擡起會麼?
對於這個問題,咱們須要明白如下內容:
Android的MotionEvent事件主要有如下幾個:
下面是事件的舉例:點擊屏幕後鬆開,事件序列爲 DOWN -> UP,點擊屏幕滑動鬆開,事件序列爲 DOWN -> MOVE -> ...> MOVE -> UP。同時,getX/getY 返回相對於當前View左上角的座標,getRawX/getRawY 返回相對於屏幕左上角的座標。TouchSlop是系統所能識別出的被認爲滑動的最小距離,不一樣設備值可能不相同,可經過ViewConfiguration.get(getContext()).getScaledTouchSlop() 獲取。
首先,來看一下Activity中setContentView的源代碼。
public void setContentView(@LayoutRes int layoutResID) { //將xml佈局傳遞到Window當中 getWindow().setContentView(layoutResID); initWindowDecorActionBar(); }
能夠看到, Activity的 setContentView實質是將 View傳遞到 Window的 setContentView()方法中, Window的 setContenView會在內部調用 installDecor()方法建立 DecorView,代碼以下。
public void setContentView(int layoutResID) { if (mContentParent == null) { //初始化DecorView以及其內部的content installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { ............... } else { //將contentView加載到DecorVoew當中 mLayoutInflater.inflate(layoutResID, mContentParent); } ............... } private void installDecor() { ............... if (mDecor == null) { //實例化DecorView mDecor = generateDecor(-1); ............... } } else { mDecor.setWindow(this); } if (mContentParent == null) { //獲取Content mContentParent = generateLayout(mDecor); } ............... } protected DecorView generateDecor(int featureId) { ............... return new DecorView(context, featureId, this, getAttributes()); }
經過 generateDecor()的new一個 DecorView,而後調用 generateLayout()獲取 DecorView中 content,最終經過 inflate將 Activity視圖添加到 DecorView中的 content中,但此時 DecorView還未被添加到 Window中。添加操做須要藉助 ViewRootImpl。
ViewRootImpl的做用是用來銜接 WindowManager和 DecorView,在 Activity被建立後會經過 WindowManager將 DecorView添加到 PhoneWindow中而且建立 ViewRootImpl實例,隨後將 DecorView與 ViewRootImpl進行關聯,最終經過執行 ViewRootImpl的 performTraversals()開啓整個View樹的繪製。
Android的Draw過程能夠分爲六個步驟:
涉及到的代碼以下:
public void draw(Canvas canvas) { ... // 步驟一:繪製View的背景 drawBackground(canvas); ... // 步驟二:若是須要的話,保持canvas的圖層,爲fading作準備 saveCount = canvas.getSaveCount(); ... canvas.saveLayer(left, top, right, top + length, null, flags); ... // 步驟三:繪製View的內容 onDraw(canvas); ... // 步驟四:繪製View的子View dispatchDraw(canvas); ... // 步驟五:若是須要的話,繪製View的fading邊緣並恢復圖層 canvas.drawRect(left, top, right, top + length, p); ... canvas.restoreToCount(saveCount); ... // 步驟六:繪製View的裝飾(例如滾動條等等) onDrawForeground(canvas) }
invalidate()與postInvalidate()都用於刷新View,主要區別是invalidate()在主線程中調用,若在子線程中使用須要配合handler;而postInvalidate()可在子線程中直接調用。
進程(Process) 是計算機中的程序關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操做系統結構的基礎。
當一個程序第一次啓動的時候,Android會啓動一個LINUX進程和一個主線程。默認的狀況下,全部該程序的組件都將在該進程和線程中運行。 同時,Android會爲每一個應用程序分配一個單獨的LINUX用戶。Android會盡可能保留一個正在運行進程,只在內存資源出現不足時,Android會嘗試中止一些進程從而釋放足夠的資源給其餘新的進程使用, 也能保證用戶正在訪問的當前進程有足夠的資源去及時地響應用戶的事件。
咱們能夠將一些組件運行在其餘進程中,而且能夠爲任意的進程添加線程。組件運行在哪一個進程中是在manifest文件裏設置的,其中<Activity>,<Service>,<receiver>和<provider>都有一個process屬性來指定該組件運行在哪一個進程之中。咱們能夠設置這個屬性,使得每一個組件運行在它們本身的進程中,或是幾個組件共同享用一個進程,或是不共同享用。<application>元素也有一個process屬性,用來指定全部的組件的默認屬性。
Android中的全部組件都在指定的進程中的主線程中實例化的,對組件的系統調用也是由主線程發出的。每一個實例不會創建新的線程。對系統調用進行響應的方法——例如負責執行用戶動做的View.onKeyDown()和組件的生命週期函數——都是運行在這個主線程中的。這意味着當系統調用這個組件時,這個組件不能長時間的阻塞主線程。例如進行網絡操做時或是更新UI時,若是運行時間較長,就不能直接在主線程中運行,由於這樣會阻塞這個進程中其餘的組件,咱們能夠將這樣的組件分配到新建的線程中或是其餘的線程中運行。
按照生命週期的不一樣,Android的進程能夠分爲前臺進程、後臺進程、可見進程、服務進程和空進程等。
前臺進程是用戶當前正在使用的進程,一些前臺進程能夠在任什麼時候候都存在,當內存低的時候前臺進程也可能被銷燬。對於這種狀況下,設備會進行內存調度,停止一些前臺進程來保持對用戶交互的響應。
若是有如下的情形,那麼它就是前臺進程:
可見進程指的是不包含前臺組件,可是會在屏幕上顯示一個可見的進程。
若是有以下的一種情形,那就是可見進程:
經過startService() 方法啓動的Service,這個Service沒有上面的兩種進程重要,通常會隨着應用的生命週期。
通常來講,使用 startService() 方法啓動的服務且不屬於上述兩個更高類別進程的就是服務進程。
包含目前對用戶不可見的 Activity 的進程(已調用 Activity 的 onStop() 方法)。一般會有不少後臺進程在運行,所以它們會保存在 LRU (最近最少使用)列表中,以確保包含用戶最近查看的 Activity 的進程最後一個被終止。
不含任何活動應用組件的進程。保留這種進程的的惟一目的是用做緩存,以縮短下次在其中運行組件所需的啓動時間。 爲使整體系統資源在進程緩存和底層內核緩存之間保持平衡,系統每每會終止這些進程。
首先,進程通常指一個執行單元,在移動設備上就是一個程序或應用,咱們在Android中所說的多進程(IPC)通常指一個應用包含多個進程。之因此要使用多進程有兩方面緣由:某些模塊因爲特殊的需求要運行在單獨的進程;增長應用可用的內存空間。
在Android中開啓多進程只有一種方法,就是在AndroidManifest.xml中註冊Service、Activity、Receiver、ContentProvider時指定android:process屬性,以下所示。
<service android:name=".MyService" android:process=":remote"> </service> <activity android:name=".MyActivity" android:process="com.shh.ipctest.remote2"> </activity>
能夠看到,MyService和MyActivity指定的android:process屬性值有所不一樣,它們的區別以下:
不過,開啓多進程會引起以下問題,必須引發注意:
對於前兩個問題,能夠這麼理解,在Android中,系統會爲每一個應用或進程分配獨立的虛擬機,不一樣的虛擬機天然佔有不一樣的內存地址空間,因此同一個類的對象會產生不一樣的副本,致使共享數據失敗,必然也不能實現線程的同步。
因爲SharedPreferences底層採用讀寫XML的文件的方式實現,多進程併發的的讀寫極可能致使數據異常。
Application被屢次建立和前兩個問題相似,系統在分配多個虛擬機時至關於把同一個應用從新啓動屢次,必然會致使 Application 屢次被建立,爲了防止在 Application
中出現無用的重複初始化,可以使用進程名來作過濾,只讓指定進程的才進行全局初,以下所示。
public class MyApplication extends Application{ @Override public void onCreate() { super.onCreate(); String processName = "com.xzh.ipctest"; if (getPackageName().equals(processName)){ // do some init } } }
目前,Android中支持的多進程通訊方式主要有如下幾種:
Serializable實例:
import java.io.Serializable; class serializableObject implements Serializable { String name; public serializableObject(String name) { this.name = name; } public String getName() { return name; } }
Parcelable實例:
import android.os.Parcel; import android.os.Parcelable; class parcleObject implements Parcelable { private String name; protected parcleObject(Parcel in) { this.name = in.readString(); } public parcleObject(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public static final Creator<parcleObject> CREATOR = new Creator<parcleObject>() { @Override public parcleObject createFromParcel(Parcel in) { return new parcleObject(in); } @Override public parcleObject[] newArray(int size) { return new parcleObject[size]; } }; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(this.name); } }
使用Parcelable時,通常須要用到如下幾個方法:
Window 是一個抽象類,它的具體實現是 PhoneWindow。WindowManager 是外界訪問 Window 的入口,Window 的具體實現位於 WindowManagerService 中,WindowManager 和 WindowManagerService 的交互是一個 IPC 過程。Android 中全部的視圖都是經過 Window 來呈現,所以 Window 實際是 View 的直接管理者。
依據做用的不一樣,Window能夠分爲以下幾種:
Window 是一個抽象的概念,每個 Window 對應着一個 View 和一個 ViewRootImpl。Window 實際是不存在的,它是以 View 的形式存在。對 Window 的訪問必須經過 WindowManager,WindowManager 的實現類是 WindowManagerImpl,源碼以下:
@Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); } @Override public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.updateViewLayout(view, params); } @Override public void removeView(View view) { mGlobal.removeView(view, false); }
WindowManagerImpl 沒有直接實現 Window 的三大操做,而是所有交給 WindowManagerGlobal 處理,WindowManagerGlobal 以工廠的形式向外提供本身的實例,涉及的代碼以下:
// 添加 public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ··· // 子 Window 的話須要調整一些佈局參數 final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; if (parentWindow != null) { parentWindow.adjustLayoutParamsForSubWindow(wparams); } else { ··· } ViewRootImpl root; View panelParentView = null; synchronized (mLock) { // 新建一個 ViewRootImpl,並經過其 setView 來更新界面完成 Window 的添加過程 ··· root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); // do this last because it fires off messages to start doing things try { root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up. if (index >= 0) { removeViewLocked(index, true); } throw e; } } } // 刪除 @UnsupportedAppUsage public void removeView(View view, boolean immediate) { ··· synchronized (mLock) { int index = findViewLocked(view, true); View curView = mRoots.get(index).getView(); removeViewLocked(index, immediate); ··· } } private void removeViewLocked(int index, boolean immediate) { ViewRootImpl root = mRoots.get(index); View view = root.getView(); if (view != null) { InputMethodManager imm = InputMethodManager.getInstance(); if (imm != null) { imm.windowDismissed(mViews.get(index).getWindowToken()); } } boolean deferred = root.die(immediate); if (view != null) { view.assignParent(null); if (deferred) { mDyingViews.add(view); } } } // 更新 public void updateViewLayout(View view, ViewGroup.LayoutParams params) { ··· final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params; view.setLayoutParams(wparams); synchronized (mLock) { int index = findViewLocked(view, true); ViewRootImpl root = mRoots.get(index); mParams.remove(index); mParams.add(index, wparams); root.setLayoutParams(wparams, false); } }
談Android的消息機制主要就是Handler機制。
Handler 有兩個主要用途:
Android 規定訪問 UI 只能在主線程中進行,由於 Android 的 UI 控件不是線程安全的,多線程併發訪問會致使 UI 控件處於不可預期的狀態。爲何系統不對 UI 控件的訪問加上鎖機制?缺點有兩個:加鎖會讓 UI 訪問的邏輯變得複雜;其次鎖機制會下降 UI 訪問的效率。若是子線程訪問 UI,那麼程序就會拋出異常。爲了保證線程安全,ViewRootImpl 對UI操做作了驗證,這個驗證工做是由 ViewRootImpl的 checkThread 方法完成。
void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } }
談Handler機制時,一般會包含如下三個對象:
Handler 建立的時候會採用當前線程的 Looper 來構造消息循環系統,須要注意的是,線程默認是沒有 Looper 的,直接使用 Handler 會報錯,若是須要使用 Handler 就必須爲線程建立 Looper,由於默認的 UI 主線程,也就是 ActivityThread,ActivityThread 被建立的時候就會初始化 Looper,這也是在主線程中默承認以使用 Handler 的緣由。
ThreadLocal
ThreadLocal 是一個線程內部的數據存儲類,經過它能夠在指定的線程中存儲數據,其餘線程則沒法獲取。Looper、ActivityThread 以及 AMS 中都用到了 ThreadLocal。當不一樣線程訪問同一個ThreadLocal 的 get方法,ThreadLocal 內部會從各自的線程中取出一個數組,而後再從數組中根據當前 ThreadLcoal 的索引去查找對應的value值,源碼以下:
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } ··· public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
MessageQueue
MessageQueue主要包含兩個操做:插入和讀取。讀取操做自己會伴隨着刪除操做,插入和讀取對應的方法分別是 enqueueMessage 和 next。MessageQueue 內部實現並非用的隊列,實際上經過一個單鏈表的數據結構來維護消息列表。next 方法是一個無限循環的方法,若是消息隊列中沒有消息,那麼 next 方法會一直阻塞。當有新消息到來時,next 方法會放回這條消息並將其從單鏈表中移除,源碼以下。
boolean enqueueMessage(Message msg, long when) { ··· synchronized (this) { ··· msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) { // New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; needWake = mBlocked; } else { // Inserted within the middle of the queue. Usually we don't have to wake // up the event queue unless there is a barrier at the head of the queue // and the message is the earliest asynchronous message in the queue. needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } // We can assume mPtr != 0 because mQuitting is false. if (needWake) { nativeWake(mPtr); } } return true; } ··· Message next() { // Return here if the message loop has already quit and been disposed. // This can happen if the application tries to restart a looper after quit // which is not supported. ··· for (;;) { ··· synchronized (this) { // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; } ··· } // Run the idle handlers. // We only ever reach this code block during the first iteration. for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // release the reference to the handler boolean keep = false; try { keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); } if (!keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } // Reset the idle handler count to 0 so we do not run them again. pendingIdleHandlerCount = 0; // While calling an idle handler, a new message could have been delivered // so go back and look again for a pending message without waiting. nextPollTimeoutMillis = 0; } }
Looper
Looper 會不停地從 MessageQueue 中 查看是否有新消息,若是有新消息就會馬上處理,不然會一直阻塞,Looper源碼。
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
可經過 Looper.prepare() 爲當前線程建立一個 Looper,默認狀況下,Activity會建立一個Looper對象。
除了 prepare 方法外,Looper 還提供了 prepareMainLooper 方法,主要是給 ActivityThread 建立 Looper 使用,本質也是經過 prepare 方法實現的。因爲主線程的 Looper 比較特殊,因此 Looper 提供了一個 getMainLooper 方法來獲取主線程的 Looper。
同時,Looper 提供了 quit 和 quitSafely 來退出一個 Looper,兩者的區別是:quit 會直接退出 Looper,而 quitSafly 只是設定一個退出標記,而後把消息隊列中的已有消息處理完畢後才安全地退出。Looper 退出後,經過 Handler 發送的消息會失敗,這個時候 Handler 的 send 方法會返回 false,所以在不須要的時候須要及時終止 Looper。
Handler
Handler 的工做主要包含消息的發送和接收的過程。消息的發送能夠經過 post/send 的一系列方法實現,post 最終也是經過send來實現的。
在Android開發中,常常會遇到長列表的問題,所以不少時候,就會涉及到RecyclerView的優化問題。對於列表卡頓的緣由,一般有以下一些:
notifyDataSetChanged
若是數據須要全局刷新時,可使用notifyDataSetChanged;對於增長或減小數據,可使用局部刷新,以下所示。
void onNewDataArrived(List<News> news) { List<News> oldNews = myAdapter.getItems(); DiffResult result = DiffUtil.calculateDiff(new MyCallback(oldNews, news)); myAdapter.setNews(news); result.dispatchUpdatesTo(myAdapter); }
RecycleView嵌套
在實際開發中,常常會看到豎直滾動的RecycleView嵌套一個橫向滾動的RecycleView的場景。因爲單個RecycleView都擁有獨立的itemView對象池,對於嵌套的狀況,能夠設置共享對象池,以下。
class OuterAdapter extends RecyclerView.Adapter<OuterAdapter.ViewHolder> { RecyclerView.RecycledViewPool mSharedPool = new RecyclerView.RecycledViewPool(); ... @Override public void onCreateViewHolder(ViewGroup parent, int viewType) { // inflate inner item, find innerRecyclerView by ID… LinearLayoutManager innerLLM = new LinearLayoutManager(parent.getContext(), LinearLayoutManager.HORIZONTAL); innerRv.setLayoutManager(innerLLM); innerRv.setRecycledViewPool(mSharedPool); return new OuterAdapter.ViewHolder(innerRv); }
嵌套層級過深
經過Systemtrace工具能夠檢測Layout的性能,若是耗時太長或者調用次數過多,須要考察一下是否過分使用RelativeLayout或者嵌套多層LinearLayout,每層Layout都會致使其child屢次的measure/layout。
對象分配和垃圾回收
雖然Android 5.0上使用ART來減小GC停頓時間,但仍然會形成卡頓。儘可能避免在循環內建立對象致使GC。要知道,建立對象須要分配內存,而這個時機會檢查內存是否足夠來決定需不須要進行GC。
除了上面的典型場景外,RecyclerView的優化還須要注意如下幾點: