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的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,每個耗時操作都會以工作佇列的方式在IntentService
中onHandlerIntent()
回撥方法中執行,並且每次只會執行一個工作執行緒。
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;
sDefaultExecutor
是SerialExecutor
的一個例項,而且它是個靜態變數。也就是說,一個程序裡面所有AsyncTask物件都共享同一個SerialExecutor
物件。