Android 面試之必問Android基礎

在上一篇文章Android 面試之必問Java基礎一文中,咱們介紹了Java面試的一些常見的基礎面試題,下面咱們來介紹Android開發的一些必問知識點。java

1,Activity

1.1 生命週期

正常狀況系,Activity會經歷以下幾個階段:android

  • onCreate:表示Activity正在被建立。
  • onRestart:表示Activity正在被從新啓動。
  • onStart:表示Activity正在被啓動,這時已經可見,但沒有出如今前臺沒法進行交互。
  • onResume:表示Activity已經可見,而且處於前臺。
  • onPause:表示Activity正在中止(可作一次保存狀態中止動畫等非耗時操做)。
  • onStop:表示Activity即將中止(可進行重量級回收工做)。
  • onDestroy:表示Activity即將被銷燬。

在這裏插入圖片描述
對於生命週期,一般還會問以下的一些問題:git

  • 第一次啓動:onCreate->onStart->onResume;
  • 打開新的Activity或者返回桌面:onPause->onStop。若是打開新的Activity爲透明主題,則不會調用onStop;
  • 當回到原來Activity時:onRestart->onStart->onResume;
  • 當按下返回鍵:onPause->onStop->onDestroy

1.2 啓動模式

Activity的啓動模式有四種:Standard、SingleTop、SingleTask和SingleInstance。github

  • Standard:標準模式,也是默認模式。每次啓動都會建立一個全新的實例。
  • SingleTop:棧頂複用模式。這種模式下若是Activity位於棧頂,不會新建實例。onNewIntent會被調用,接收新的請求信息,不會再低啊用onCreate和onStart。
  • SingleTask:棧內複用模式。升級版singleTop,若是棧內有實例,則複用,並會將該實例之上的Activity所有清除。
  • SingleInstance:系統會爲它建立一個單獨的任務棧,而且這個實例獨立運行在一個 task中,這個task只有這個實例,不容許有別的Activity 存在(能夠理解爲手機內只有一個)。

1.3 啓動流程

在理解Activity的啓動流程以前,先讓咱們來看一下Android系統啓動流程。總的來講,Android系統啓動流程的主要經歷init進程 -> Zygote進程 –> SystemServer進程 –> 各類系統服務 –> 應用進程等階段。面試

  1. 啓動電源以及系統啓動:當電源按下時引導芯片從預約義的地方(固化在ROM)開始執行,加載引導程序BootLoader到RAM,而後執行。
  2. 引導程序BootLoader:BootLoader是在Android系統開始運行前的一個小程序,主要用於把系統OS拉起來並運行。
  3. Linux內核啓動:當內核啓動時,設置緩存、被保護存儲器、計劃列表、加載驅動。當其完成系統設置時,會先在系統文件中尋找init.rc文件,並啓動init進程。
  4. init進程啓動:初始化和啓動屬性服務,而且啓動Zygote進程。
  5. Zygote進程啓動:建立JVM併爲其註冊JNI方法,建立服務器端Socket,啓動SystemServer進程。
  6. SystemServer進程啓動:啓動Binder線程池和SystemServiceManager,而且啓動各類系統服務。
  7. Launcher啓動:被SystemServer進程啓動的AMS會啓動Launcher,Launcher啓動後會將已安裝應用的快捷圖標顯示到系統桌面上。

參考:
Android系統啓動流程之init進程啓動
Android系統啓動流程之Zygote進程啓動
Android系統啓動流程之SystemServer進程啓動
Android系統啓動流程之Launcher進程啓動json

Launcher進程啓動後,就會調用Activity的啓動了。首先,Launcher會調用ActivityTaskManagerService,而後ActivityTaskManagerService會調用ApplicationThread,而後ApplicationThread再經過ActivityThread啓動Activity,完整的分析能夠參考Android 之 Activity啓動流程canvas

2,Fragment

2.1 簡介

Fragment,是Android 3.0(API 11)提出的,爲了兼容低版本,support-v4庫中也開發了一套Fragment API,最低兼容Android 1.6,若是要在最新的版本中使用Fragment,須要引入AndroidX的包。小程序

相比Activity,Fragment具備以下一些特色:segmentfault

  • 模塊化(Modularity):咱們沒必要把全部代碼所有寫在Activity中,而是把代碼寫在各自的Fragment中。
  • 可重用(Reusability):多個Activity能夠重用一個Fragment。
  • 可適配(Adaptability):根據硬件的屏幕尺寸、屏幕方向,可以方便地實現不一樣的佈局,這樣用戶體驗更好。

Fragment有以下幾個核心的類:數組

  • Fragment:Fragment的基類,任何建立的Fragment都須要繼承該類。
  • FragmentManager:管理和維護Fragment。他是抽象類,具體的實現類是FragmentManagerImpl。
  • FragmentTransaction:對Fragment的添加、刪除等操做都須要經過事務方式進行。他是抽象類,具體的實現類是BackStackRecord。

2.2 生命週期

Fragment必須是依存於Activity而存在的,所以Activity的生命週期會直接影響到Fragment的生命週期。相比Activity的生命週期,Fragment的生命週期以下所示。

  • onAttach():Fragment和Activity相關聯時調用。若是不是必定要使用具體的宿主 Activity 對象的話,可使用這個方法或者getContext()獲取 Context 對象,用於解決Context上下文引用的問題。同時還能夠在此方法中能夠經過getArguments()獲取到須要在Fragment建立時須要的參數。
  • onCreate():Fragment被建立時調用。
  • onCreateView():建立Fragment的佈局。
  • onActivityCreated():當Activity完成onCreate()時調用。
  • onStart():當Fragment可見時調用。
  • onResume():當Fragment可見且可交互時調用。
  • onPause():當Fragment不可交互但可見時調用。
  • onStop():當Fragment不可見時調用。
  • onDestroyView():當Fragment的UI從視圖結構中移除時調用。
  • onDestroy():銷燬Fragment時調用。
  • onDetach():當Fragment和Activity解除關聯時調用。

以下圖所示。
在這裏插入圖片描述
下面是Activity的生命週期和Fragment的各個生命週期方法的對應關係。
在這裏插入圖片描述

2.3 與Activity傳遞數據

2.3.1 Fragment向Activity傳遞數據

首先,在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");
    }
}

2.3.2 Activity向Fragment傳遞數據

在建立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;
    }

3, Service

3.1 啓動方式

Service的啓動方式主要有兩種,分別是startService和bindService。

其中,StartService使用的是同一個Service,所以onStart()會執行屢次,onCreate()只執行一次,onStartCommand()也會執行屢次。使用bindService啓動時,onCreate()與onBind()都只會調用一次。

使用startService啓動時是單獨開一個服務,與Activity沒有任何關係,而bindService方式啓動時,Service會和Activity進行綁定,當對應的activity銷燬時,對應的Service也會銷燬。

3.2 生命週期

下圖是startService和bindService兩種方式啓動Service的示意圖。
在這裏插入圖片描述

3.2.1 startService

  • onCreate():若是service沒被建立過,調用startService()後會執行onCreate()回調;若是service已處於運行中,調用startService()不會執行onCreate()方法。
  • onStartCommand():屢次執行了Context的startService()方法,那麼Service的onStartCommand()方法也會相應的屢次調用。
  • onBind():Service中的onBind()方法是抽象方法,Service類自己就是抽象類,因此onBind()方法是必須重寫的,即便咱們用不到。
    onDestory():在銷燬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();
    }
}

3.2.2 bindService

bindService啓動的服務和調用者之間是典型的Client-Server模式。調用者是client,Service則是Server端。Service只有一個,但綁定到Service上面的Client能夠有一個或不少個。bindService啓動服務的生命週期與其綁定的client息息相關。

1,首先,在Service的onBind()方法中返回IBinder類型的實例。
2,onBInd()方法返回的IBinder的實例須要可以返回Service實例自己。

3.3 Service不被殺死

如今,因爲系統API的限制,一些常見的不被殺死Service方式已通過時,好比下面是以前的一些方式。

3.3.1, onStartCommand方式中,返回START_STICKY。

調用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參數。

4, BroadcastReceiver

4.1 BroadcastReceiver是什麼

BroadcastReceiver,廣播接收者,它是一個系統全局的監聽器,用於監聽系統全局的Broadcast消息,因此它能夠很方便的進行系統組件之間的通訊。BroadcastReceiver屬於系統級的監聽器,它擁有本身的進程,只要存在與之匹配的Broadcast被以Intent的形式發送出來,BroadcastReceiver就會被激活。

和其餘的四大組件同樣,BroadcastReceiver也有本身獨立的聲明週期,可是它又和Activity、Service不一樣。當在系統註冊一個BroadcastReceiver以後,每次系統以一個Intent的形式發佈Broadcast的時候,系統都會建立與之對應的BroadcastReceiver廣播接收者實例,並自動觸發它的onReceive()方法,當onReceive()方法被執行完成以後,BroadcastReceiver的實例就會被銷燬。

從不一樣的緯度區分,BroadcastReceiver能夠分爲不一樣的類別。

  • 系統廣播/非系統廣播
  • 全局廣播/本地廣播
  • 無序廣播/有序廣播/粘性廣播

4.2 基本使用

4.2.1 註冊廣播

廣播的註冊分爲靜態註冊和動態註冊。靜態註冊是在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)

4.2.2 發送廣播

而後,咱們使用sendBroadcast方法發送廣播。

Intent().also { intent ->
    intent.setAction("com.example.broadcast.MY_NOTIFICATION")
    intent.putExtra("data", "Notice me senpai!")
    sendBroadcast(intent)
}

4.2.3 接收廣播

發送廣播的時候,咱們會添加一個發送的標識,那麼接收的時候使用這個標識接收便可。接收廣播須要繼承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()
            }
        }
    }
}

5, ContentProvider

ContentProvider是Android四大組件之一,不過平時使用的機會比較少。若是你看過它的底層源碼,那麼就應該知道ContentProvider是經過Binder進行數據共享。所以,若是咱們須要對第三方應用提供數據,能夠考慮使用ContentProvider實現。

6,Android View知識點

Android自己的View體系很是龐大的,若是要徹底弄懂View的原理是很困難的,咱們這裏撿一些比較重要的概念來給你們講解。

6.1 測量流程

Android View自己的繪製流程須要通過measure測量、layout佈局、draw繪製三個過程,最終纔可以將其繪製出來並展現在用戶面前。

首先,咱們看一下Android的MeasureSpec,Android的MeasureSpec分爲3中模式,分別是EXACTLY、AT_MOST 和 UNSPECIFIED,含義以下。

  • MeasureSpec.EXACTLY:精確模式,在這種模式下,尺寸的值是多少組件的長或寬就是多少。
  • MeasureSpec.AT_MOST:最大模式,由父組件可以給出的最大的空間決定。
  • MeasureSpec.UNSPECIFIED:未指定模式,當前組件能夠隨便使用空間,不受限制。

相關文章:談談對 MeasureSpec 的理解

6.2 事件分發

Android的事件分發由dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent三個方法構成。

  • dispatchTouchEvent:方法返回值爲true,表示事件被當前視圖消費掉;返回爲super.dispatchTouchEvent表示繼續分發該事件,返回爲false表示交給父類的onTouchEvent處理。
  • onInterceptTouchEvent:方法返回值爲true,表示攔截這個事件並交由自身的onTouchEvent方法進行消費;返回false表示不攔截,須要繼續傳遞給子視圖。若是return super.onInterceptTouchEvent(ev), 事件攔截分兩種狀況:即一種是有子View的狀況,另外一種是沒有子View的狀況。
    若是該View存在子View且點擊到了該子View,則不攔截,繼續分發
    給子View 處理,此時至關於return false。若是該View沒有子View或者有子View可是沒有點擊中子View,則交由該View的onTouchEvent響應,此時至關於return true。
  • onTouchEvent:方法返回值爲true表示當前視圖能夠處理對應的事件;返回值爲false表示當前視圖不處理這個事件,它會被傳遞給父視圖的onTouchEvent方法進行處理。若是return super.onTouchEvent(ev),事件處理分爲兩種狀況,即本身消費仍是仍是向上傳遞。

在Android系統中,擁有事件傳遞處理能力的類有如下三種:

  • Activity:擁有分發和消費兩個方法。
  • ViewGroup:擁有分發、攔截和消費三個方法。
  • View:擁有分發、消費兩個方法。

在事件分發中,有時候會問:ACTION_CANCEL何時觸發,觸摸button而後滑動到外部擡起會觸發點擊事件嗎,再滑動回去擡起會麼?

對於這個問題,咱們須要明白如下內容:

  • 通常ACTION_CANCEL和ACTION_UP都做爲View一段事件處理的結束。若是在父View中攔截ACTION_UP或ACTION_MOVE,在第一次父視圖攔截消息的瞬間,父視圖指定子視圖不接受後續消息了,同時子視圖會收到ACTION_CANCEL事件。
  • 若是觸摸某個控件,可是又不是在這個控件的區域上擡起,也會出現ACTION_CANCEL。

在這裏插入圖片描述
在這裏插入圖片描述

  • ViewGroup 默認不攔截任何事件。ViewGroup 的 onInterceptTouchEvent 方法默認返回 false。
  • View 沒有 onInterceptTouchEvent 方法,一旦有點擊事件傳遞給它,onTouchEvent 方法就會被調用。
  • View 在可點擊狀態下,onTouchEvent 默認會消耗事件。
  • ACTION_DOWN 被攔截了,onInterceptTouchEvent 方法執行一次後,就會留下記號(mFirstTouchTarget == null)那麼日後的 ACTION_MOVE 和 ACTION_UP 都會攔截。`

6.3 MotionEvent

Android的MotionEvent事件主要有如下幾個:

  • ACTION_DOWN 手指剛接觸到屏幕
  • ACTION_MOVE 手指在屏幕上移動
  • ACTION_UP 手機從屏幕上鬆開的一瞬間
  • ACTION_CANCEL 觸摸事件取消

下面是事件的舉例:點擊屏幕後鬆開,事件序列爲 DOWN -> UP,點擊屏幕滑動鬆開,事件序列爲 DOWN -> MOVE -> ...> MOVE -> UP。同時,getX/getY 返回相對於當前View左上角的座標,getRawX/getRawY 返回相對於屏幕左上角的座標。TouchSlop是系統所能識別出的被認爲滑動的最小距離,不一樣設備值可能不相同,可經過ViewConfiguration.get(getContext()).getScaledTouchSlop() 獲取。

6.4 Activity、Window、DecorView之間關係

首先,來看一下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樹的繪製。

6.5 Draw 繪製流程

Android的Draw過程能夠分爲六個步驟:

  1. 首先,繪製View的背景;
  2. 若是須要的話,保持canvas的圖層,爲fading作準備;
  3. 而後,繪製View的內容;
  4. 接着,繪製View的子View;
  5. 若是須要的話,繪製View的fading邊緣並恢復圖層;
  6. 最後,繪製View的裝飾(例如滾動條等等)。

涉及到的代碼以下:

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

6.6 Requestlayout,onlayout,onDraw,DrawChild區別與聯繫

  • requestLayout():會致使調用 measure()過程 和 layout()過程,將會根據標誌位判斷是否須要ondraw。
  • onLayout():若是該View是ViewGroup對象,須要實現該方法,對每一個子視圖進行佈局。
  • onDraw():繪製視圖自己 (每一個View都須要重載該方法,ViewGroup不須要實現該方法)。
  • drawChild():去從新回調每一個子視圖的draw()方法。

6.7 invalidate() 和 postInvalidate()的區別

invalidate()與postInvalidate()都用於刷新View,主要區別是invalidate()在主線程中調用,若在子線程中使用須要配合handler;而postInvalidate()可在子線程中直接調用。

7,Android進程

7.1 概念

進程(Process) 是計算機中的程序關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操做系統結構的基礎。

當一個程序第一次啓動的時候,Android會啓動一個LINUX進程和一個主線程。默認的狀況下,全部該程序的組件都將在該進程和線程中運行。 同時,Android會爲每一個應用程序分配一個單獨的LINUX用戶。Android會盡可能保留一個正在運行進程,只在內存資源出現不足時,Android會嘗試中止一些進程從而釋放足夠的資源給其餘新的進程使用, 也能保證用戶正在訪問的當前進程有足夠的資源去及時地響應用戶的事件。

咱們能夠將一些組件運行在其餘進程中,而且能夠爲任意的進程添加線程。組件運行在哪一個進程中是在manifest文件裏設置的,其中<Activity>,<Service>,<receiver>和<provider>都有一個process屬性來指定該組件運行在哪一個進程之中。咱們能夠設置這個屬性,使得每一個組件運行在它們本身的進程中,或是幾個組件共同享用一個進程,或是不共同享用。<application>元素也有一個process屬性,用來指定全部的組件的默認屬性。

Android中的全部組件都在指定的進程中的主線程中實例化的,對組件的系統調用也是由主線程發出的。每一個實例不會創建新的線程。對系統調用進行響應的方法——例如負責執行用戶動做的View.onKeyDown()和組件的生命週期函數——都是運行在這個主線程中的。這意味着當系統調用這個組件時,這個組件不能長時間的阻塞主線程。例如進行網絡操做時或是更新UI時,若是運行時間較長,就不能直接在主線程中運行,由於這樣會阻塞這個進程中其餘的組件,咱們能夠將這樣的組件分配到新建的線程中或是其餘的線程中運行。

7.2 進程生命週期

按照生命週期的不一樣,Android的進程能夠分爲前臺進程、後臺進程、可見進程、服務進程和空進程等。

前臺進程

前臺進程是用戶當前正在使用的進程,一些前臺進程能夠在任什麼時候候都存在,當內存低的時候前臺進程也可能被銷燬。對於這種狀況下,設備會進行內存調度,停止一些前臺進程來保持對用戶交互的響應。

若是有如下的情形,那麼它就是前臺進程:

  1. 託管用戶正在交互的 Activity(已調用 Activity 的 onResume() 方法)
  2. 託管某個 Service,後者綁定到用戶正在交互的 Activity
  3. 託管正在「前臺」運行的 Service(服務已調用 startForeground())
  4. 託管正執行一個生命週期回調的 Service(onCreate()、onStart() 或 onDestroy())
  5. 託管正執行其 onReceive() 方法的 BroadcastReceiver
可見進程

可見進程指的是不包含前臺組件,可是會在屏幕上顯示一個可見的進程。

若是有以下的一種情形,那就是可見進程:

  1. 託管不在前臺、但仍對用戶可見的 Activity(已調用其 onPause() 方法)。例如,若是 re前臺 Activity啓動了一個對話框,容許在其後顯示上一 Activity,則有可能會發生這種狀況。
  2. 託管綁定到可見(或前臺)Activity 的 Service。
服務進程

經過startService() 方法啓動的Service,這個Service沒有上面的兩種進程重要,通常會隨着應用的生命週期。

通常來講,使用 startService() 方法啓動的服務且不屬於上述兩個更高類別進程的就是服務進程。

後臺進程

包含目前對用戶不可見的 Activity 的進程(已調用 Activity 的 onStop() 方法)。一般會有不少後臺進程在運行,所以它們會保存在 LRU (最近最少使用)列表中,以確保包含用戶最近查看的 Activity 的進程最後一個被終止。

空進程

不含任何活動應用組件的進程。保留這種進程的的惟一目的是用做緩存,以縮短下次在其中運行組件所需的啓動時間。 爲使整體系統資源在進程緩存和底層內核緩存之間保持平衡,系統每每會終止這些進程。

7.3 多進程

首先,進程通常指一個執行單元,在移動設備上就是一個程序或應用,咱們在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屬性值有所不一樣,它們的區別以下:

  • :remote:以:開頭是一種簡寫,系統會在當前進程名前附件當前包名,完整的進程名爲:com.shh.ipctest:remote,同時以:開頭的進程屬於當前應用的私有進程,其它應用的組件不能和它跑在同一進程。
  • com.shh.ipctest.remote2:這是完整的命名方式,不會附加包名,其它應用若是和該進程的ShareUID、簽名相同,則能夠和它跑在同一個進程,實現數據共享。

不過,開啓多進程會引起以下問題,必須引發注意:

  • 靜態成員和單例模式失效
  • 線程同步機制失效
  • SharedPreferences可靠性下降
  • Application被屢次建立

對於前兩個問題,能夠這麼理解,在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
        }
    }
}

7.4 多進程通訊方式

目前,Android中支持的多進程通訊方式主要有如下幾種:

  • AIDL:功能強大,支持進程間一對多的實時併發通訊,並可實現 RPC (遠程過程調用)。
  • Messenger:支持一對多的串行實時通訊, AIDL 的簡化版本。
  • Bundle:四大組件的進程通訊方式,只能傳輸 Bundle 支持的數據類型。
  • ContentProvider:強大的數據源訪問支持,主要支持 CRUD 操做,一對多的進程間數據共享,例如咱們的應用訪問系統的通信錄數據。
  • BroadcastReceiver:即廣播,但只能單向通訊,接收者只能被動的接收消息。
    文件共享:在非高併發狀況下共享簡單的數據。
  • Socket:經過網絡傳輸數據。

8,序列化

8.1 Parcelable 與 Serializable

  • Serializable 使用 I/O 讀寫存儲在硬盤上,而 Parcelable 是直接在內存中讀寫。
  • Serializable 會使用反射,序列化和反序列化過程須要大量 I/O 操做, Parcelable 自已實現封送和解封(marshalled &unmarshalled)操做不須要用反射,數據也存放在 Native 內存中,效率要快不少。

8.2 示例

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時,通常須要用到如下幾個方法:

  • createFromParcel(Parcel in):從序列化後的對象中建立原始對象。
  • newArray(int size):建立指定長度的原始對象數組。
  • User(Parcel in) 從序列化後的對象中建立原始對象。
  • writeToParcel(Parcel dest, int flags):將當前對象寫入序列化結構中,其中 flags 標識有兩種值:0 或者 1。爲 1 時標識當前對象須要做爲返回值返回,不能當即釋放資源,幾乎全部狀況都爲 0。
  • describeContents:返回當前對象的內容描述。若是含有文件描述符,返回 1,不然返回 0,幾乎全部狀況都返回 0。

9,Window

9.1 基本概念

Window 是一個抽象類,它的具體實現是 PhoneWindow。WindowManager 是外界訪問 Window 的入口,Window 的具體實現位於 WindowManagerService 中,WindowManager 和 WindowManagerService 的交互是一個 IPC 過程。Android 中全部的視圖都是經過 Window 來呈現,所以 Window 實際是 View 的直接管理者。

依據做用的不一樣,Window能夠分爲以下幾種:

  • Application Window:對應着一個 Activity;
  • Sub Window: 不能單獨存在,只能附屬在父 Window 中,如 Dialog 等;
  • System Window:須要權限聲明,如 Toast 和 系統狀態欄等;

9.2 內部機制

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

10,消息機制

談Android的消息機制主要就是Handler機制。

10.1 Handler 機制

Handler 有兩個主要用途:

  • 安排 Message 和 runnables 在未來的某個時刻執行;
  • 將要在不一樣於本身的線程上執行的操做排入隊列。(在多個線程併發更新UI的同時保證線程安全。)

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機制時,一般會包含如下三個對象:

  • Message:Handler 接收和處理的消息對象。
  • MessageQueue:Message 的隊列,先進先出,每個線程最多能夠擁有一個。
  • Looper:消息泵,是 MessageQueue 的管理者,會不斷從 MessageQueue 中取出消息,並將消息分給對應的 Handler 處理,每一個線程只有一個 Looper。

Handler 建立的時候會採用當前線程的 Looper 來構造消息循環系統,須要注意的是,線程默認是沒有 Looper 的,直接使用 Handler 會報錯,若是須要使用 Handler 就必須爲線程建立 Looper,由於默認的 UI 主線程,也就是 ActivityThread,ActivityThread 被建立的時候就會初始化 Looper,這也是在主線程中默承認以使用 Handler 的緣由。

10.2 工做原理

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來實現的。

11, RecyclerView優化

在Android開發中,常常會遇到長列表的問題,所以不少時候,就會涉及到RecyclerView的優化問題。對於列表卡頓的緣由,一般有以下一些:

11.1 卡頓場景

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。

11.2 其餘優化策略

除了上面的典型場景外,RecyclerView的優化還須要注意如下幾點:

  • 升級 RecycleView 版本到 25.1.0 及以上使用 Prefetch 功能;
  • 經過重寫 RecyclerView.onViewRecycled(holder) 來回收資源;
  • 若是 Item 高度是固定的話,可使用 RecyclerView.setHasFixedSize(true); 來避免
    requestLayout 浪費資源;
  • 對 ItemView 設置監聽器,不要對每一個 Item 都調用 addXxListener,應該你們公用一個 XxListener,根據 ID 來進行不一樣的操做,優化了對象的頻繁建立帶來的資源消耗;
  • 儘可能不要對ViewHolder使用有透明度改變的繪製;
  • 增長預加載策略。
相關文章
相關標籤/搜索