1. 程式人生 > >2018年Android面試題整理

2018年Android面試題整理

橫豎屏切換時Activity的生命週期變化?

1.如果自己沒有配置android:ConfigChanges,這時預設讓系統處理,就會重建Activity,此時Activity的生命週期會走一遍。

onSaveInstanceState() 與onRestoreIntanceState() 
資源相關的系統配置發生改變或者資源不足:例如螢幕旋轉,當前Activity會銷燬,並且在onStop之前回調onSaveInstanceState儲存資料,在重新建立Activity的時候在onStart之後回撥onRestoreInstanceState。其中Bundle資料會傳到onCreate(不一定有資料)和onRestoreInstanceState(一定有資料)。 使用者或者程式設計師主動去銷燬一個Activity的時候不會回撥,其他情況都會呼叫,來儲存介面資訊。如程式碼中finish()或使用者按下back,不會回撥。

2.如果設定 android:configChanges="orientation|keyboardHidden|screenSize">

,此時Activity的生命週期不會重走一遍,Activity不會重建,只會回撥onConfigurationChanged方法。

activity的startActivity和context的startActivity區別

(1)從Activity中啟動新的Activity時可以直接mContext.startActivity(intent)就好,

(2)如果從其他Context中啟動Activity則必須給intent設定Flag:

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) ; 
mContext.startActivity(intent);

介紹下Android應用程式啟動過程

整個應用程式的啟動過程要執行很多步驟,但是整體來看,主要分為以下五個階段:

   一. :Launcher通過Binder程序間通訊機制通知ActivityManagerService,它要啟動一個Activity;

   二.:ActivityManagerService通過Binder程序間通訊機制通知Launcher進入Paused狀態;

   三.:Launcher通過Binder程序間通訊機制通知ActivityManagerService,它已經準備就緒進入Paused狀態,於是ActivityManagerService就建立一個新的程序,用來啟動一個ActivityThread例項,即將要啟動的Activity就是在這個ActivityThread例項中執行;

   四. :ActivityThread通過Binder程序間通訊機制將一個ApplicationThread型別的Binder物件傳遞給ActivityManagerService,以便以後ActivityManagerService能夠通過這個Binder物件和它進行通訊;

   五 :ActivityManagerService通過Binder程序間通訊機制通知ActivityThread,現在一切準備就緒,它可以真正執行Activity的啟動操作了。

如何保證Service不被殺死?

  • 提供程序優先順序,降低程序被殺死的概率 方法一:監控手機鎖屏解鎖事件,在螢幕鎖屏時啟動1個畫素的 Activity,在使用者解鎖時將 Activity 銷燬掉。 方法二:啟動前臺service。 方法三:提升service優先順序: 在AndroidManifest.xml檔案中對於intent-filter可以通過android:priority = "1000"這個屬性設定最高優先順序,1000是最高值,如果數字越小則優先順序越低,同時適用於廣播。
  • 在程序被殺死後,進行拉活 方法一:註冊高頻率廣播接收器,喚起程序。如網路變化,解鎖螢幕,開機等 方法二:雙程序相互喚起。 方法三:依靠系統喚起。 方法四:onDestroy方法裡重啟service:service +broadcast 方式,就是當service走ondestory的時候,傳送一個自定義的廣播,當收到廣播的時候,重新啟動service;
  • 依靠第三方 根據終端不同,在小米手機(包括 MIUI)接入小米推送、華為手機接入華為推送;其他手機可以考慮接入騰訊信鴿或極光推送與小米推送做 A/B Test。

簡述下Acitivty任務棧和使用方法

任務棧是一種後進先出的結構。位於棧頂的Activity處於焦點狀態,當按下back按鈕的時候,棧內的Activity會一個一個的出棧,並且呼叫其onDestory()方法。如果棧內沒有Activity,那麼系統就會回收這個棧,每個APP預設只有一個棧,以APP的包名來命名. 1、standard:預設模式:每次啟動都會建立一個新的activity物件,放到目標任務棧中

2、singleTop:判斷當前的任務棧頂是否存在相同的activity物件,如果存在,則直接使用,如果不存在,那麼建立新的activity物件放入棧中

3、singleTask:在任務棧中會判斷是否存在相同的activity,如果存在,那麼會清除該activity之上的其他activity物件顯示,如果不存在,則會建立一個新的activity放入棧頂

4、singleIntance:會在一個新的任務棧中建立activity,並且該任務棧種只允許存在一個activity例項,其他呼叫該activity的元件會直接使用該任務棧種的activity物件

方法一: 使用android:launchMode="standard|singleInstance|single Task|singleTop"來控制Acivity任務棧。 
方法二: Intent Flags:

Intent intent=new Intent();
intent.setClass(MainActivity.this, MainActivity2.class);
intent.addFlags(Intent. FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);

Flags有很多,比如:
Intent.FLAG_ACTIVITY_NEW_TASK 相當於singleTask
Intent. FLAG_ACTIVITY_CLEAR_TOP 相當於singleTop

Context相關問題

Activity和Service以及Application的Context是不一樣的,Activity繼承自ContextThemeWraper.其他的繼承自ContextWrapper.
每一個Activity和Service以及Application的Context都是一個新的ContextImpl物件 getApplication()用來獲取Application例項的,但是這個方法只有在Activity和Service中才能呼叫的到。那麼也許在絕大多數情況下我們都是在Activity或者Service中使用Application的,但是如果在一些其它的場景,比如BroadcastReceiver中也想獲得Application的例項,這時就可以藉助getApplicationContext()方法.
getApplicationContext()比getApplication()方法的作用域會更廣一些,任何一個Context的例項,只要呼叫getApplicationContext()方法都可以拿到我們的Application物件。 

Context的數量等於Activity的個數 + Service的個數 + 1,這個1為Application.
那Broadcast Receiver,Content Provider呢?Broadcast Receiver,Content Provider並不是Context的子類,他們所持有的Context都是其他地方傳過去的,所以並不計入Context總數。

怎麼在Service中建立Dialog對話方塊

1.在我們取得Dialog物件後,需給它設定型別,即:
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT)

2.在Manifest中加上許可權:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

View篇

非UI執行緒可以更新UI嗎?

可以
當訪問UI時,ViewRootImpl會呼叫checkThread方法去檢查當前訪問UI的執行緒是哪個,如果不是UI執行緒則會丟擲異常 執行onCreate方法的那個時候ViewRootImpl還沒建立,無法去檢查當前執行緒.ViewRootImpl的建立在onResume方法回撥之後.

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

非UI執行緒是可以重新整理UI的,前提是它要擁有自己的ViewRoot,即更新UI的執行緒和建立ViewRoot是同一個,或者在執行checkThread()前更新UI.

解決ScrollView巢狀ListView和GridView衝突的方法

重寫ListView的onMeasure方法,來自定義高度:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, expandSpec);
    }

主要考察對MeasureSpec的三種模式的理解,相關文章.

自定義View優化策略

為了加速你的view,對於頻繁呼叫的方法,需要儘量減少不必要的程式碼。先從onDraw開始,需要特別注意不應該在這裡做記憶體分配的事情,因為它會導致GC,從而導致卡頓。在初始化或者動畫間隙期間做分配記憶體的動作。不要在動畫正在執行的時候做記憶體分配的事情。

你還需要儘可能的減少onDraw被呼叫的次數,大多數時候導致onDraw都是因為呼叫了invalidate().因此請儘量減少呼叫invaildate()的次數。如果可能的話,儘量呼叫含有4個引數的invalidate()方法而不是沒有引數的invalidate()。沒有引數的invalidate會強制重繪整個view。

另外一個非常耗時的操作是請求layout。任何時候執行requestLayout(),會使得Android UI系統去遍歷整個View的層級來計算出每一個view的大小。如果找到有衝突的值,它會需要重新計算好幾次。另外需要儘量保持View的層級是扁平化的,這樣對提高效率很有幫助。

如果你有一個複雜的UI,你應該考慮寫一個自定義的ViewGroup來執行他的layout操作。與內建的view不同,自定義的view可以使得程式僅僅測量這一部分,這避免了遍歷整個view的層級結構來計算大小。這個PieChart 例子展示瞭如何繼承ViewGroup作為自定義view的一部分。PieChart 有子views,但是它從來不測量它們。而是根據他自身的layout法則,直接設定它們的大小。

執行緒篇

Handler、Message、Looper、MessageQueue

一、相關概念的解釋
主執行緒(UI執行緒)
定義:當程式第一次啟動時,Android會同時啟動一條主執行緒(Main Thread) 作用:主執行緒主要負責處理與UI相關的事件

Message(訊息) 
定義:Handler接收和處理的訊息物件(Bean物件)
作用:通訊時相關資訊的存放和傳遞

ThreadLocal 
定義:執行緒內部的資料儲存類
作用:負責儲存和獲取本執行緒的Looper

MessageQueue(訊息佇列)
定義:採用單鏈表的資料結構來儲存訊息列表
作用:用來存放通過Handler發過來的Message,按照先進先出執行

Handler(處理者) 
定義:Message的主要處理者
作用:負責傳送Message到訊息佇列&處理Looper分派過來的Message

Looper(迴圈器) 
定義:扮演Message Queue和Handler之間橋樑的角色
作用: 訊息迴圈:迴圈取出Message Queue的Message 訊息派發:將取出的Message交付給相應的Handler

2、自己畫下圖解

3、Handler傳送訊息有哪幾種方式?

一、sendMessage(Message msg) 二、post(Ruunable r)

4、Handler處理訊息有哪幾種方式?

這個直接看dispatchMessage()原始碼:

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        //1. post()方法的處理方法
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        //2. sendMessage()方法的處理方法
        handleMessage(msg);
    }
}
//1. post()方法的最終處理方法
private static void handleCallback(Message message) {
    message.callback.run();
}
//2. sendMessage()方法的最終處理方法
public void handleMessage(Message msg) {
}

5.Message、Handler、MessageQueen、Looper的之間的關係?

首先,是這個MessageQueen,MessageQueen是一個訊息佇列,它可以儲存Handler傳送過來的訊息,其內部提供了進隊和出隊的方法來管理這個訊息佇列,其出隊和進隊的原理是採用單鏈表的資料結構進行插入和刪除的,即enqueueMessage()方法和next()方法。這裡提到的Message,其實就是一個Bean物件,裡面的屬性用來記錄Message的各種資訊。

然後,是這個Looper,Looper是一個迴圈器,它可以迴圈的取出MessageQueen中的Message,其內部提供了Looper的初始化和迴圈出去Message的方法,即prepare()方法和loop()方法。在prepare()方法中,Looper會關聯一個MessageQueen,而且將Looper存進一個ThreadLocal中,在loop()方法中,通過ThreadLocal取出Looper,使用MessageQueen的next()方法取出Message後,判斷Message是否為空,如果是則Looper阻塞,如果不是,則通過dispatchMessage()方法分發該Message到Handler中,而Handler執行handlerMessage()方法,由於handlerMessage()方法是個空方法,這也是為什麼需要在Handler中重寫handlerMessage()方法的原因。這裡要注意的是Looper只能在一個執行緒中只能存在一個。這裡提到的ThreadLocal,其實就是一個物件,用來在不同執行緒中存放對應執行緒的Looper。

最後,是這個Handler,Handler是Looper和MessageQueen的橋樑,Handler內部提供了傳送Message的一系列方法,最終會通過MessageQueen的enqueueMessage()方法將Message存進MessageQueen中。我們平時可以直接在主執行緒中使用Handler,那是因為在應用程式啟動時,在入口的main方法中已經預設為我們建立好了Looper。

6.為什麼在子執行緒中建立Handler會拋異常?

Handler的工作是依賴於Looper的,而Looper(與訊息佇列)又是屬於某一個執行緒(ThreadLocal是執行緒內部的資料儲存類,通過它可以在指定執行緒中儲存資料,其他執行緒則無法獲取到),其他執行緒不能訪問。因此Handler就是間接跟執行緒是繫結在一起了。因此要使用Handler必須要保證Handler所建立的執行緒中有Looper物件並且啟動迴圈。因為子執行緒中預設是沒有Looper的,所以會報錯。 正確的使用方法是:


  private final class WorkThread extends Thread {

        private Handler mHandler;

        public Handler getHandler() {
            return mHandler;
        }
          public void quit() {
            mHandler.getLooper().quit();
        }
        @Override
        public void run() {
            super.run();
            //建立該執行緒對應的Looper,
            // 內部實現
            // 1。new Looper()
            // 2。將1步中的lopper 放在ThreadLocal裡,ThreadLocal是儲存資料的,主要應用場景是:執行緒間資料互不影響的情況
            // 3。在1步中的Looper的建構函式中new MessageQueue();
            //其實就是建立了該執行緒對用的Looper,Looper裡建立MessageQueue來實現訊息機制
            //對訊息機制不懂得同學可以查閱資料,網上很多也講的很不錯。
            Looper.prepare();
            mHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    Log.d("WorkThread", (Looper.getMainLooper() == Looper.myLooper()) + "," + msg.what);
                }
            };
            //開啟訊息的死迴圈處理即:dispatchMessage
            Looper.loop();
            //注意這3個的順序不能顛倒
            Log.d("WorkThread", "end");
        }
    }

HandlerThread

1、HandlerThread作用

當系統有多個耗時任務需要執行時,每個任務都會開啟一個新執行緒去執行耗時任務,這樣會導致系統多次建立和銷燬執行緒,從而影響效能。為了解決這一問題,Google提供了HandlerThread,HandlerThread是線上程中建立一個Looper迴圈器,讓Looper輪詢訊息佇列,當有耗時任務進入佇列時,則不需要開啟新執行緒,在原有的執行緒中執行耗時任務即可,否則執行緒阻塞。

2、HanlderThread的優缺點

  • HandlerThread本質上是一個執行緒類,它繼承了Thread;
  • HandlerThread有自己的內部Looper物件,可以進行looper迴圈;
  • 通過獲取HandlerThread的looper物件傳遞給Handler物件,可以在handleMessage()方法中執行非同步任務。
  • 建立HandlerThread後必須先呼叫HandlerThread.start()方法,Thread會先呼叫run方法,建立Looper物件。
  • HandlerThread優點是非同步不會堵塞,減少對效能的消耗
  • HandlerThread缺點是不能同時繼續進行多工處理,需要等待進行處理,處理效率較低
  • HandlerThread與執行緒池不同,HandlerThread是一個序列佇列,背後只有一個執行緒。

IntentService

  • 它本質是一種特殊的Service,繼承自Service並且本身就是一個抽象類

  • 它可以用於在後臺執行耗時的非同步任務,當任務完成後會自動停止

  • 它擁有較高的優先順序,不易被系統殺死(繼承自Service的緣故),因此比較適合執行一些高優先順序的非同步任務 它內部通過HandlerThread和Handler實現非同步操作

  • 建立IntentService時,只需實現onHandleIntent和構造方法,onHandleIntent為非同步方法,可以執行耗時操作

  • 即使我們多次啟動IntentService,但IntentService的例項只有一個,這跟傳統的Service是一樣的,最終IntentService會去呼叫onHandleIntent執行非同步任務。

  • 當任務完成後,IntentService會自動停止,而不需要手動呼叫stopSelf()。另外,可以多次啟動IntentService,每個耗時操作都會以工作佇列的方式在IntentServiceonHandlerIntent()回撥方法中執行,並且每次只會執行一個工作執行緒。

AsyncTask

1、AsyncTask是什麼

AsyncTask是一種輕量級的非同步任務類,它可以線上程池中執行後臺任務,然後把執行的進度和最終結果傳遞給主執行緒並主執行緒中更新UI,通過AsyncTask可以更加方便執行後臺任務以及在主執行緒中訪問UI,但是AsyncTask並不適合進行特別耗時的後臺任務,對於特別耗時的任務來說,建議使用執行緒池。

2、AsyncTask使用方法

三個引數 Params:表示後臺任務執行時的引數型別,該引數會傳給AysncTask的doInBackground()方法 Progress:表示後臺任務的執行進度的引數型別,該引數會作為onProgressUpdate()方法的引數 Result:表示後臺任務的返回結果的引數型別,該引數會作為onPostExecute()方法的引數 五個方法 onPreExecute():非同步任務開啟之前回調,在主執行緒中執行 doInBackground():執行非同步任務,線上程池中執行 onProgressUpdate():當doInBackground中呼叫publishProgress時回撥,在主執行緒中執行 onPostExecute():在非同步任務執行之後回撥,在主執行緒中執行 onCancelled():在非同步任務被取消時回撥

3、AsyncTask引起的記憶體洩漏
原因:非靜態內部類持有外部類的匿名引用,導致Activity無法釋放 解決: AsyncTask內部持有外部Activity的弱引用 AsyncTask改為靜態內部類 Activity的onDestory()中呼叫AsyncTask.cancel()

4.結果丟失
螢幕旋轉或Activity在後臺被系統殺掉等情況會導致Activity的重新建立,之前執行的AsyncTask會持有一個之前Activity的引用,這個引用已經無效,這時呼叫onPostExecute()再去更新介面將不再生效。

5、AsyncTask並行or序列
AsyncTask在Android 2.3之前預設採用並行執行任務,AsyncTask在Android 2.3之後預設採用序列執行任務 如果需要在Android 2.3之後採用並行執行任務,可以呼叫AsyncTask的executeOnExecutor();

6.AsyncTask內部的執行緒池

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

sDefaultExecutorSerialExecutor的一個例項,而且它是個靜態變數。也就是說,一個程序裡面所有AsyncTask物件都共享同一個SerialExecutor物件。