史上最全!押題率90%的 Android 中高階工程師面試複習大綱及真題答案整理(終章)
緣起
轉眼間2020就接近尾聲了,年後有跳槽想法的小夥伴們心裡應該也有自己的決定了。金三銀四青銅五,總不能到跳槽的黃金期再開始複習吧。沒辦法,都是兄弟,寵著!2020年度Android中高階面試複習大全奉上。
篇幅過長,預計分三篇文章講解,好兄弟們記得點個關注或者點贊Mark插個眼,後續不容錯過哦
上一篇Java基礎,計算機網路相關面試題點這裡:
史上最全!押題率90%的 Android 中高階工程師面試複習大綱及真題答案整理(上篇)
上一篇Android基礎夯實99題,點這裡
史上最全!押題率90%的 Android 中高階工程師面試複習大綱及真題答案整理(中篇)
上篇Android高階面試題,點這裡
4、跨程序通訊。
Android中程序和執行緒的關係?區別?
- 執行緒是CPU排程的最小單元,同時執行緒是一種有限的系統資源;而程序一般指一個執行單元,在PC和移動裝置上指一個程式或者一個應用。
- 一般來說,一個App程式至少有一個程序,一個程序至少有一個執行緒(包含與被包含的關係),通俗來講就是,在App這個工廠裡面有一個程序,執行緒就是裡面的生產線,但主執行緒(即主生產線)只有一條,而子執行緒(即副生產線)可以有多個。
- 程序有自己獨立的地址空間,而程序中的執行緒共享此地址空間,都可以併發執行。
如何開啟多程序?應用是否可以開啟N個程序?
在AndroidManifest中給四大元件指定屬性android:process開啟多程序模式,在記憶體允許的條件下可以開啟N個程序。
為何需要IPC?多程序通訊可能會出現的問題?
所有執行在不同程序的四大元件(Activity、Service、Receiver、ContentProvider)共享資料都會失敗,這是由於Android為每個應用分配了獨立的虛擬機器,不同的虛擬機器在記憶體分配上有不同的地址空間,這會導致在不同的虛擬機器中訪問同一個類的物件會產生多份副本。比如常用例子(通過開啟多程序獲取更大記憶體空間、兩個或者多個應用之間共享資料、微信全家桶)。
一般來說,使用多程序通訊會造成如下幾方面的問題:
- 靜態成員和單例模式完全失效:獨立的虛擬機器造成。
- 執行緒同步機制完全失效:獨立的虛擬機器造成。
- SharedPreferences的可靠性下降:這是因為Sp不支援兩個程序併發進行讀寫,有一定機率導致資料丟失。
- Application會多次建立:Android系統在建立新的程序時會分配獨立的虛擬機器,所以這個過程其實就是啟動一個應用的過程,自然也會建立新的Application。
Android中IPC方式、各種方式優缺點?
講講AIDL?如何優化多模組都使用AIDL的情況?
AIDL(Android Interface Definition Language,Android介面定義語言):如果在一個程序中要呼叫另一個程序中物件的方法,可使用AIDL生成可序列化的引數,AIDL會生成一個服務端物件的代理類,通過它客戶端可以實現間接呼叫服務端物件的方法。
AIDL的本質是系統提供了一套可快速實現Binder的工具。關鍵類和方法:
- AIDL介面:繼承IInterface。
- Stub類:Binder的實現類,服務端通過這個類來提供服務。
- Proxy類:服務端的本地代理,客戶端通過這個類呼叫服務端的方法。
- asInterface():客戶端呼叫,將服務端返回的Binder物件,轉換成客戶端所需要的AIDL介面型別的物件。如果客戶端和服務端位於同一程序,則直接返回Stub物件本身,否則返回系統封裝後的Stub.proxy物件。
- asBinder():根據當前呼叫情況返回代理Proxy的Binder物件。
- onTransact():執行在服務端的Binder執行緒池中,當客戶端發起跨程序請求時,遠端請求會通過系統底層封裝後交由此方法來處理。
- transact():執行在客戶端,當客戶端發起遠端請求的同時將當前執行緒掛起。之後呼叫服務端的onTransact()直到遠端請求返回,當前執行緒才繼續執行。
當有多個業務模組都需要AIDL來進行IPC,此時需要為每個模組建立特定的aidl檔案,那麼相應的Service就會很多。必然會出現系統資源耗費嚴重、應用過度重量級的問題。解決辦法是建立Binder連線池,即將每個業務模組的Binder請求統一轉發到一個遠端Service中去執行,從而避免重複建立Service。
工作原理:每個業務模組建立自己的AIDL介面並實現此介面,然後向服務端提供自己的唯一標識和其對應的Binder物件。服務端只需要一個Service並提供一個queryBinder介面,它會根據業務模組的特徵來返回相應的Binder物件,不同的業務模組拿到所需的Binder物件後就可以進行遠端方法的呼叫了。
為什麼選擇Binder?
為什麼選用Binder,在討論這個問題之前,我們知道Android也是基於Linux核心,Linux現有的程序通訊手段有以下幾種:
- 管道:在建立時分配一個page大小的記憶體,快取區大小比較有限;
- 訊息佇列:資訊複製兩次,額外的CPU消耗;不合適頻繁或資訊量大的通訊;
- 共享記憶體:無須複製,共享緩衝區直接附加到程序虛擬地址空間,速度快;但程序間的同步問題作業系統無法實現,必須各程序利用同步工具解決;
- 套接字:作為更通用的介面,傳輸效率低,主要用於不同機器或跨網路的通訊;
- 訊號量:常作為一種鎖機制,防止某程序正在訪問共享資源時,其他程序也訪問該資源。因此,主要作為程序間以及同一程序內不同執行緒之間的同步手段。 不適用於資訊交換,更適用於程序中斷控制,比如非法記憶體訪問,殺死某個程序等;
既然有現有的IPC方式,為什麼重新設計一套Binder機制呢。主要是出於以上三個方面的考量:
- 1、效率:傳輸效率主要影響因素是記憶體拷貝的次數,拷貝次數越少,傳輸速率越高。從Android程序架構角度分析:對於訊息佇列、Socket和管道來說,資料先從傳送方的快取區拷貝到核心開闢的快取區中,再從核心快取區拷貝到接收方的快取區,一共兩次拷貝,如圖:
而對於Binder來說,資料從傳送方的快取區拷貝到核心的快取區,而接收方的快取區與核心的快取區是對映到同一塊實體地址的,節省了一次資料拷貝的過程,如圖:
共享記憶體不需要拷貝,Binder的效能僅次於共享記憶體。
- 2、穩定性:上面說到共享記憶體的效能優於Binder,那為什麼不採用共享記憶體呢,因為共享記憶體需要處理併發同步問題,容易出現死鎖和資源競爭,穩定性較差。Socket雖然是基於C/S架構的,但是它主要是用於網路間的通訊且傳輸效率較低。Binder基於C/S架構 ,Server端與Client端相對獨立,穩定性較好。
- 3、安全性:傳統Linux IPC的接收方無法獲得對方程序可靠的UID/PID,從而無法鑑別對方身份;而Binder機制為每個程序分配了UID/PID,且在Binder通訊時會根據UID/PID進行有效性檢測。
Binder機制的作用和原理?
Linux系統將一個程序分為使用者空間和核心空間。對於程序之間來說,使用者空間的資料不可共享,核心空間的資料可共享,為了保證安全性和獨立性,一個程序不能直接操作或者訪問另一個程序,即Android的程序是相互獨立、隔離的,這就需要跨程序之間的資料通訊方式。普通的跨程序通訊方式一般需要2次記憶體拷貝,如下圖所示:
一次完整的 Binder IPC 通訊過程通常是這樣:
- 首先 Binder 驅動在核心空間建立一個數據接收快取區。
- 接著在核心空間開闢一塊核心快取區,建立核心快取區和核心中資料接收快取區之間的對映關係,以及核心中資料接收快取區和接收程序使用者空間地址的對映關係。
- 傳送方程序通過系統呼叫 copyfromuser() 將資料 copy 到核心中的核心快取區,由於核心快取區和接收程序的使用者空間存在記憶體對映,因此也就相當於把資料傳送到了接收程序的使用者空間,這樣便完成了一次程序間的通訊。
Binder框架中ServiceManager的作用?
Binder框架 是基於 C/S 架構的。由一系列的元件組成,包括 Client、Server、ServiceManager、Binder驅動,其中 Client、Server、Service Manager 執行在使用者空間,Binder 驅動執行在核心空間。如下圖所示:
- Server&Client:伺服器&客戶端。在Binder驅動和Service Manager提供的基礎設施上,進行Client-Server之間的通訊。
- ServiceManager(如同DNS域名伺服器)服務的管理者,將Binder名字轉換為Client中對該Binder的引用,使得Client可以通過Binder名字獲得Server中Binder實體的引用。
- Binder驅動(如同路由器):負責程序之間binder通訊的建立,計數管理以及資料的傳遞互動等底層支援。
最後,結合Android跨程序通訊:圖文詳解 Binder機制 的總結圖來綜合理解一下:
Binder 的完整定義
- 從程序間通訊的角度看,Binder 是一種程序間通訊的機制;
- 從 Server 程序的角度看,Binder 指的是 Server 中的 Binder 實體物件;
- 從 Client 程序的角度看,Binder 指的是 Binder 代理物件,是 Binder 實體物件的一個遠端代理;
- 從傳輸過程的角度看,Binder 是一個可以跨程序傳輸的物件;Binder 驅動會對這個跨越程序邊界的物件對一點點特殊處理,自動完成代理物件和本地物件之間的轉換。
手寫實現簡化版AMS(AIDL實現)
與Binder相關的幾個類的職責:
- IBinder:跨程序通訊的Base介面,它聲明瞭跨程序通訊需要實現的一系列抽象方法,實現了這個介面就說明可以進行跨程序通訊,Client和Server都要實現此介面。
- IInterface:這也是一個Base介面,用來表示Server提供了哪些能力,是Client和Server通訊的協議。
- Binder:提供Binder服務的本地物件的基類,它實現了IBinder介面,所有本地物件都要繼承這個類。
- BinderProxy:在Binder.java這個檔案中還定義了一個BinderProxy類,這個類表示Binder代理物件它同樣實現了IBinder介面,不過它的很多實現都交由native層處理。Client中拿到的實際上是這個代理物件。
- Stub:這個類在編譯aidl檔案後自動生成,它繼承自Binder,表示它是一個Binder本地物件;它是一個抽象類,實現了IInterface介面,表明它的子類需要實現Server將要提供的具體能力(即aidl檔案中宣告的方法)。
- Proxy:它實現了IInterface介面,說明它是Binder通訊過程的一部分;它實現了aidl中宣告的方法,但最終還是交由其中的mRemote成員來處理,說明它是一個代理物件,mRemote成員實際上就是BinderProxy。
aidl檔案只是用來定義C/S互動的介面,Android在編譯時會自動生成相應的Java類,生成的類中包含了Stub和Proxy靜態內部類,用來封裝資料轉換的過程,實際使用時只關心具體的Java介面類即可。為什麼Stub和Proxy是靜態內部類呢?這其實只是為了將三個類放在一個檔案中,提高程式碼的聚合性。通過上面的分析,我們其實完全可以不通過aidl,手動編碼來實現Binder的通訊,下面我們通過編碼來實現ActivityManagerService:
1、首先定義IActivityManager介面:
public interface IActivityManager extends IInterface {
//binder描述符
String DESCRIPTOR = "android.app.IActivityManager";
//方法編號
int TRANSACTION_startActivity = IBinder.FIRST_CALL_TRANSACTION + 0;
//宣告一個啟動activity的方法,為了簡化,這裡只傳入intent引數
int startActivity(Intent intent) throws RemoteException;
}
2、然後,實現ActivityManagerService側的本地Binder物件基類:
// 名稱隨意,不一定叫Stub
public abstract class ActivityManagerNative extends Binder implements IActivityManager {
public static IActivityManager asInterface(IBinder obj) {
if (obj == null) {
return null;
}
IActivityManager in = (IActivityManager) obj.queryLocalInterface(IActivityManager.DESCRIPTOR);
if (in != null) {
return in;
}
//代理物件,見下面的程式碼
return new ActivityManagerProxy(obj);
}
@Override
public IBinder asBinder() {
return this;
}
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code) {
// 獲取binder描述符
case INTERFACE_TRANSACTION:
reply.writeString(IActivityManager.DESCRIPTOR);
return true;
// 啟動activity,從data中反序列化出intent引數後,直接呼叫子類startActivity方法啟動activity。
case IActivityManager.TRANSACTION_startActivity:
data.enforceInterface(IActivityManager.DESCRIPTOR);
Intent intent = Intent.CREATOR.createFromParcel(data);
int result = this.startActivity(intent);
reply.writeNoException();
reply.writeInt(result);
return true;
}
return super.onTransact(code, data, reply, flags);
}
}
3、接著,實現Client側的代理物件:
public class ActivityManagerProxy implements IActivityManager {
private IBinder mRemote;
public ActivityManagerProxy(IBinder remote) {
mRemote = remote;
}
@Override
public IBinder asBinder() {
return mRemote;
}
@Override
public int startActivity(Intent intent) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
int result;
try {
// 將intent引數序列化,寫入data中
intent.writeToParcel(data, 0);
// 呼叫BinderProxy物件的transact方法,交由Binder驅動處理。
mRemote.transact(IActivityManager.TRANSACTION_startActivity, data, reply, 0);
reply.readException();
// 等待server執行結束後,讀取執行結果
result = reply.readInt();
} finally {
data.recycle();
reply.recycle();
}
return result;
}
}
4、最後,實現Binder本地物件(IActivityManager介面):
public class ActivityManagerService extends ActivityManagerNative {
@Override
public int startActivity(Intent intent) throws RemoteException {
// 啟動activity
return 0;
}
}
簡化版的ActivityManagerService到這裡就已經實現了,剩下就是Client只需要獲取到AMS的代理物件IActivityManager就可以通訊了。
簡單講講 binder 驅動吧?
從 Java 層來看就像訪問本地介面一樣,客戶端基於 BinderProxy 服務端基於 IBinder 物件,從 native 層來看來看客戶端基於 BpBinder 到 ICPThreadState 到 binder 驅動,服務端由 binder 驅動喚醒 IPCThreadSate 到 BbBinder 。跨程序通訊的原理最終是要基於核心的,所以最會會涉及到 binder_open 、binder_mmap 和 binder_ioctl這三種系統呼叫。
跨程序傳遞大記憶體資料如何做?
binder 肯定是不行的,因為對映的最大記憶體只有 1M-8K,可以採用 binder + 匿名共享記憶體的形式,像跨程序傳遞大的 bitmap 需要開啟系統底層的 ashmem 機制。
請按順序仔細閱讀下列文章提升對Binder機制的理解程度:
老羅Binder機制分析系列或Android系統原始碼情景分析Binder章節
5、Android系統啟動流程是什麼?(提示:init程序 -> Zygote程序 –> SystemServer程序 –> 各種系統服務 –> 應用程序)
Android系統啟動的核心流程如下:
- 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啟動後會將已安裝應用的快捷圖示顯示到系統桌面上。
系統是怎麼幫我們啟動找到桌面應用的?
通過意圖,PMS 會解析所有 apk 的 AndroidManifest.xml ,如果解析過會存到 package.xml 中不會反覆解析,PMS 有了它就能找到了。
6、啟動一個程式,可以主介面點選圖示進入,也可以從一個程式中跳轉過去,二者有什麼區別?
是因為啟動程式(主介面也是一個app),發現了在這個程式中存在一個設定為的activity, 所以這個launcher會把icon提出來,放在主介面上。當用戶點選icon的時候,發出一個Intent:
Intent intent = mActivity.getPackageManager().getLaunchIntentForPackage(packageName);
mActivity.startActivity(intent);
跳過去可以跳到任意允許的頁面,如一個程式可以下載,那麼真正下載的頁面可能不是首頁(也有可能是首頁),這時還是構造一個Intent,startActivity。這個intent中的action可能有多種view,download都有可能。系統會根據第三方程式向系統註冊的功能,為你的Intent選擇可以開啟的程式或者頁面。所以唯一的一點 不同的是從icon的點選啟動的intent的action是相對單一的,從程式中跳轉或者啟動可能樣式更多一些。本質是相同的。
7、AMS家族重要術語解釋。
1.ActivityManagerServices,簡稱AMS,服務端物件,負責系統中所有Activity的生命週期。
2.ActivityThread,App的真正入口。當開啟App之後,呼叫main()開始執行,開啟訊息迴圈佇列,這就是傳說的UI執行緒或者叫主執行緒。與ActivityManagerService一起完成Activity的管理工作。
3.ApplicationThread,用來實現ActivityManagerServie與ActivityThread之間的互動。在ActivityManagerSevice需要管理相關Application中的Activity的生命週期時,通過ApplicationThread的代理物件與ActivityThread通訊。
4.ApplicationThreadProxy,是ApplicationThread在伺服器端的代理,負責和客戶端的ApplicationThread通訊。AMS就是通過該代理與ActivityThread進行通訊的。
5.Instrumentation,每一個應用程式只有一個Instrumetation物件,每個Activity內都有一個對該物件的引用,Instrumentation可以理解為應用程序的管家,ActivityThread要建立或暫停某個Activity時,都需要通過Instrumentation來進行具體的操作。
6.ActivityStack,Activity在AMS的棧管理,用來記錄經啟動的Activity的先後關係,狀態資訊等。通過ActivtyStack決定是否需要啟動新的程序。
7.ActivityRecord,ActivityStack的管理物件,每個Acivity在AMS對應一個ActivityRecord,來記錄Activity狀態以及其他的管理資訊。其實就是伺服器端的Activit物件的映像。
8.TaskRecord,AMS抽象出來的一個“任務”的概念,是記錄ActivityRecord的棧,一個“Task”包含若干個ActivityRecord。AMS用TaskRecord確保Activity啟動和退出的順序。如果你清楚Activity的4種launchMode,那麼對這概念應該不陌生。
8、App啟動流程(Activity的冷啟動流程)。
點選應用圖示後會去啟動應用的Launcher Activity,如果Launcer Activity所在的程序沒有建立,還會建立新程序,整體的流程就是一個Activity的啟動流程。
Activity的啟動流程圖(放大可檢視)如下所示:
整個流程涉及的主要角色有:
- Instrumentation: 監控應用與系統相關的互動行為。
- AMS:元件管理排程中心,什麼都不幹,但是什麼都管。
- ActivityStarter:Activity啟動的控制器,處理Intent與Flag對Activity啟動的影響,具體說來有:1 尋找符合啟動條件的Activity,如果有多個,讓使用者選擇;2 校驗啟動引數的合法性;3 返回int引數,代表Activity是否啟動成功。
- ActivityStackSupervisior:這個類的作用你從它的名字就可以看出來,它用來管理任務棧。
- ActivityStack:用來管理任務棧裡的Activity。
- ActivityThread:最終幹活的人,Activity、Service、BroadcastReceiver的啟動、切換、排程等各種操作都在這個類裡完成。
注:這裡單獨提一下ActivityStackSupervisior,這是高版本才有的類,它用來管理多個ActivityStack,早期的版本只有一個ActivityStack對應著手機螢幕,後來高版本支援多屏以後,就有了多個ActivityStack,於是就引入了ActivityStackSupervisior用來管理多個ActivityStack。
整個流程主要涉及四個程序:
- 呼叫者程序,如果是在桌面啟動應用就是Launcher應用程序。
- ActivityManagerService等待所在的System Server程序,該程序主要執行著系統服務元件。
- Zygote程序,該程序主要用來fork新程序。
- 新啟動的應用程序,該程序就是用來承載應用執行的程序了,它也是應用的主執行緒(新建立的程序就是主執行緒),處理元件生命週期、介面繪製等相關事情。
有了以上的理解,整個流程可以概括如下:
- 1、點選桌面應用圖示,Launcher程序將啟動Activity(MainActivity)的請求以Binder的方式傳送給了AMS。
- 2、AMS接收到啟動請求後,交付ActivityStarter處理Intent和Flag等資訊,然後再交給ActivityStackSupervisior/ActivityStack 處理Activity進棧相關流程。同時以Socket方式請求Zygote程序fork新程序。
- 3、Zygote接收到新程序建立請求後fork出新程序。
- 4、在新程序裡建立ActivityThread物件,新建立的程序就是應用的主執行緒,在主執行緒裡開啟Looper訊息迴圈,開始處理建立Activity。
- 5、ActivityThread利用ClassLoader去載入Activity、建立Activity例項,並回調Activity的onCreate()方法,這樣便完成了Activity的啟動。
最後,再看看另一幅啟動流程圖來加深理解:
9、ActivityThread工作原理。
10、說下四大元件的啟動過程,四大元件的啟動與銷燬的方式。
廣播發送和接收的原理了解嗎?
- 繼承BroadcastReceiver,重寫onReceive()方法。
- 通過Binder機制向ActivityManagerService註冊廣播。
- 通過Binder機制向ActivityMangerService傳送廣播。
- ActivityManagerService查詢符合相應條件的廣播(IntentFilter/Permission)的BroadcastReceiver,將廣播發送到BroadcastReceiver所在的訊息佇列中。
- BroadcastReceiver所在訊息佇列拿到此廣播後,回撥它的onReceive()方法。
11、AMS是如何管理Activity的?
12、理解Window和WindowManager。
1.Window用於顯示View和接收各種事件,Window有三種型:應用Window(每個Activity對應一個Window)、子Widow(不能單獨存在,附屬於特定Window)、系統window(toast和狀態列)
2.Window分層級,應用Window在1-99、子Window在1000-1999、系統Window在2000-2999.WindowManager提供了增改View的三個功能。
3.Window是個抽象概念:每一個Window對應著一個ViewRootImpl,Window通過ViewRootImpl來和View建立聯絡,View是Window存在的實體,只能通過WindowManager來訪問Window。
4.WindowManager的實現是WindowManagerImpl,其再委託WindowManagerGlobal來對Window進行操作,其中有四種List分別儲存對應的View、ViewRootImpl、WindowManger.LayoutParams和正在被刪除的View。
5.Window的實體是存在於遠端的WindowMangerService,所以增刪改Window在本端是修改上面的幾個List然後通過ViewRootImpl重繪View,通過WindowSession(每Window個對應一個)在遠端修改Window。
6.Activity建立Window:Activity會在attach()中建立Window並設定其回撥(onAttachedToWindow()、dispatchTouchEvent()),Activity的Window是由Policy類建立PhoneWindow實現的。然後通過Activity#setContentView()呼叫PhoneWindow的setContentView。
13、WMS是如何管理Window的?
14、大體說清一個應用程式安裝到手機上時發生了什麼?
APK的安裝流程如下所示:
複製APK到/data/app目錄下,解壓並掃描安裝包。
資源管理器解析APK裡的資原始檔。
解析AndroidManifest檔案,並在/data/data/目錄下建立對應的應用資料目錄。
然後對dex檔案進行優化,並儲存在dalvik-cache目錄下。
將AndroidManifest檔案解析出的四大元件資訊註冊到PackageManagerService中。
安裝完成後,傳送廣播。
15、Android的打包流程?(即描述清點選 Android Studio 的 build 按鈕後發生了什麼?)apk裡有哪些東西?簽名演算法的原理?
apk打包流程
Android的包檔案APK分為兩個部分:程式碼和資源,所以打包方面也分為資源打包和程式碼打包兩個方面,下面就來分析資源和程式碼的編譯打包原理。
APK整體的的打包流程如下圖所示:
具體說來:
- 通過AAPT工具進行資原始檔(包括AndroidManifest.xml、佈局檔案、各種xml資源等)的打包,生成R.java檔案。
- 通過AIDL工具處理AIDL檔案,生成相應的Java檔案。
- 通過Java Compiler編譯R.java、Java介面檔案、Java原始檔,生成.class檔案。
- 通過dex命令,將.class檔案和第三方庫中的.class檔案處理生成classes.dex,該過程主要完成Java位元組碼轉換成Dalvik位元組碼,壓縮常量池以及清除冗餘資訊等工作。
- 通過ApkBuilder工具將資原始檔、DEX檔案打包生成APK檔案。
- 通過Jarsigner工具,利用KeyStore對生成的APK檔案進行簽名。
- 如果是正式版的APK,還會利用ZipAlign工具進行對齊處理,對齊的過程就是將APK檔案中所有的資原始檔距離檔案的起始距位置都偏移4位元組的整數倍,這樣通過記憶體對映訪問APK檔案的速度會更快,並且會減少其在裝置上執行時的記憶體佔用。
apk組成
- dex:最終生成的Dalvik位元組碼。
- res:存放資原始檔的目錄。
- asserts:額外建立的資原始檔夾。
- lib:如果存在的話,存放的是ndk編出來的so庫。
- META-INF:存放簽名信息
MANIFEST.MF(清單檔案):其中每一個資原始檔都有一個SHA-256-Digest簽名,MANIFEST.MF檔案的SHA256(SHA1)並base64編碼的結果即為CERT.SF中的SHA256-Digest-Manifest值。
CERT.SF(待簽名檔案):除了開頭處定義的SHA256(SHA1)-Digest-Manifest值,後面幾項的值是對MANIFEST.MF檔案中的每項再次SHA256並base64編碼後的值。
CERT.RSA(簽名結果檔案):其中包含了公鑰、加密演算法等資訊。首先對前一步生成的MANIFEST.MF使用了SHA256(SHA1)-RSA演算法,用開發者私鑰簽名,然後在安裝時使用公鑰解密。最後,將其與未加密的摘要資訊(MANIFEST.MF檔案)進行對比,如果相符,則表明內容沒有被修改。
- androidManifest:程式的全域性清單配置檔案。
- resources.arsc:編譯後的二進位制資原始檔。
簽名演算法的原理
為什麼要簽名?
- 確保Apk來源的真實性。
- 確保Apk沒有被第三方篡改。
什麼是簽名?
在Apk中寫入一個“指紋”。指紋寫入以後,Apk中有任何修改,都會導致這個指紋無效,Android系統在安裝Apk進行簽名校驗時就會不通過,從而保證了安全性。
數字摘要
對一個任意長度的資料,通過一個Hash演算法計算後,都可以得到一個固定長度的二進位制資料,這個資料就稱為“摘要”。
補充:
- 雜湊演算法的基礎原理:將資料(如一段文字)運算變為另一固定長度值。
- SHA-1:在密碼學中,SHA-1(安全雜湊演算法1)是一種加密雜湊函式,它接受輸入併產生一個160 位(20 位元組)雜湊值,稱為訊息摘要 。
- MD5:MD5訊息摘要演算法(英語:MD5 Message-Digest Algorithm),一種被廣泛使用的密碼雜湊函式,可以產生出一個128位(16位元組)的雜湊值(hash value),用於確保資訊傳輸完整一致。
- SHA-2:名稱來自於安全雜湊演算法2(英語:Secure Hash Algorithm 2)的縮寫,一種密碼雜湊函式演算法標準,其下又可再分為六個不同的演算法標準,包括了:SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。
特徵:
- 唯一性
- 固定長度:比較常用的Hash演算法有MD5和SHA1,MD5的長度是128拉,SHA1的長度是160位。
- 不可逆性
簽名和校驗的主要過程
簽名就是在摘要的基礎上再進行一次加密,對摘要加密後的資料就可以當作數字簽名。
簽名過程:
- 1、計算摘要:通過Hash演算法提取出原始資料的摘要。
- 2、計算簽名:再通過基於金鑰(私鑰)的非對稱加密演算法對提取出的摘要進行加密,加密後的資料就是簽名信息。
- 3、寫入簽名:將簽名信息寫入原始資料的簽名區塊內。
校驗過程:
- 1、首先用同樣的Hash演算法從接收到的資料中提取出摘要。
- 2、解密簽名:使用傳送方的公鑰對數字簽名進行解密,解密出原始摘要。
- 3、比較摘要:如果解密後的資料和提取的摘要一致,則校驗通過;如果資料被第三方篡改過,解密後的資料和摘要將會不一致,則校驗不通過。
數字證書
如何保證公鑰的可靠性呢?答案是數字證書,數字證書是身份認證機構(Certificate Authority)頒發的,包含了以下資訊:
- 證書頒發機構
- 證書頒發機構簽名
- 證書繫結的伺服器域名
- 證書版本、有效期
- 簽名使用的加密演算法(非對稱演算法,如RSA)
- 公鑰等
接收方收到訊息後,先向CA驗證證書的合法性,再進行簽名校驗。
注意:Apk的證書通常是自簽名的,也就是由開發者自己製作,沒有向CA機構申請。Android在安裝Apk時並沒有校驗證書本身的合法性,只是從證書中提取公鑰和加密演算法,這也正是對第三方Apk重新簽名後,還能夠繼續在沒有安裝這個Apk的系統中繼續安裝的原因。
keystore和證書格式
keystore檔案中包含了私鑰、公鑰和數字證書。根據編碼不同,keystore檔案分為很多種,Android使用的是Java標準keystore格式JKS(Java Key Storage),所以通過Android Studio匯出的keystore檔案是以.jks結尾的。
keystore使用的證書標準是X.509,X.509標準也有多種編碼格式,常用的有兩種:pem(Privacy Enhanced Mail)和der(Distinguished Encoding Rules)。jks使用的是der格式,Android也支援直接使用pem格式的證書進行簽名。
兩種證書編碼格式的區別:
- DER(Distinguished Encoding Rules)
二進位制格式,所有型別的證書和私鑰都可以儲存為der格式。
- PEM(Privacy Enhanced Mail)
base64編碼,內容以-----BEGIN xxx----- 開頭,以-----END xxx----- 結尾。
jarsigner和apksigner的區別
Android提供了兩種對Apk的簽名方式,一種是基於JAR的簽名方式,另一種是基於Apk的簽名方式,它們的主要區別在於使用的簽名檔案不一樣:jarsigner使用keystore檔案進行簽名;apksigner除了支援使用keystore檔案進行簽名外,還支援直接指定pem證書檔案和私鑰進行簽名。
在簽名時,除了要指定keystore檔案和密碼外,也要指定alias和key的密碼,這是為什麼呢?
keystore是一個金鑰庫,也就是說它可以儲存多對金鑰和證書,keystore的密碼是用於保護keystore本身的,一對金鑰和證書是通過alias來區分的。所以jarsigner是支援使用多個證書對Apk進行簽名的,apksigner也同樣支援。
Android Apk V1 簽名原理
- 1、解析出 CERT.RSA 檔案中的證書、公鑰,解密 CERT.RSA 中的加密資料。
- 2、解密結果和 CERT.SF 的指紋進行對比,保證 CERT.SF 沒有被篡改。
- 3、而 CERT.SF 中的內容再和 MANIFEST.MF 指紋對比,保證 MANIFEST.MF 檔案沒有被篡改。
- 4、MANIFEST.MF 中的內容和 APK 所有檔案指紋逐一對比,保證 APK 沒有被篡改。
16、說下安卓虛擬機器和java虛擬機器的原理和不同點?(JVM、Davilk、ART三者的原理和區別)
JVM 和Dalvik虛擬機器的區別
JVM:.java -> javac -> .class -> jar -> .jar
架構: 堆和棧的架構.
DVM:.java -> javac -> .class -> dx.bat -> .dex
架構: 暫存器(cpu上的一塊快取記憶體)
Android2個虛擬機器的區別(一個5.0之前,一個5.0之後)
什麼是Dalvik:Dalvik是Google公司自己設計用於Android平臺的Java虛擬機器。Dalvik虛擬機器是Google等廠商合作開發的Android移動裝置平臺的核心組成部分之一,它可以支援已轉換為.dex(即Dalvik Executable)格式的Java應用程式的執行,.dex格式是專為Dalvik應用設計的一種壓縮格式,適合記憶體和處理器速度有限的系統。Dalvik經過優化,允許在有限的記憶體中同時執行多個虛擬機器的例項,並且每一個Dalvik應用作為獨立的Linux程序執行。獨立的程序可以防止在虛擬機器崩潰的時候所有程式都被關閉。
什麼是ART:Android作業系統已經成熟,Google的Android團隊開始將注意力轉向一些底層元件,其中之一是負責應用程式執行的Dalvik執行時。Google開發者已經花了兩年時間開發更快執行效率更高更省電的替代ART執行時。ART代表Android Runtime,其處理應用程式執行的方式完全不同於Dalvik,Dalvik是依靠一個Just-In-Time(JIT)編譯器去解釋位元組碼。開發者編譯後的應用程式碼需要通過一個直譯器在使用者的裝置上執行,這一機制並不高效,但讓應用能更容易在不同硬體和架構上執行。ART則完全改變了這套做法,在應用安裝的時候就預編譯位元組碼為機器語言,這一機制叫Ahead-Of-Time(AOT)編譯。在移除解釋程式碼這一過程後,應用程式執行將更有效率,啟動更快。
ART優點:
- 系統性能的顯著提升。
- 應用啟動更快、執行更快、體驗更流暢、觸感反饋更及時。
- 更長的電池續航能力。
- 支援更低的硬體。
ART缺點:
- 更大的儲存空間佔用,可能會增加10%-20%。
- 更長的應用安裝時間。
ART和Davlik中垃圾回收的區別?
17、安卓採用自動垃圾回收機制,請說下安卓記憶體管理的原理?
開放性問題:如何設計垃圾回收演算法?
18、Android中App是如何沙箱化的,為何要這麼做?
19、一個圖片在app中呼叫R.id後是如何找到的?
20、JNI
Java呼叫C++
- 在Java中宣告Native方法(即需要呼叫的本地方法)
- 編譯上述 Java原始檔javac(得到 .class檔案) 3。 通過 javah 命令匯出JNI的標頭檔案(.h檔案)
- 使用 Java需要互動的原生代碼 實現在 Java中宣告的Native方法
- 編譯.so庫檔案
- 通過Java命令執行 Java程式,最終實現Java呼叫原生代碼
C++呼叫Java
-
從classpath路徑下搜尋ClassMethod這個類,並返回該類的Class物件。
-
獲取類的預設構造方法ID。
-
查詢例項方法的ID。
-
建立該類的例項。
-
呼叫物件的例項方法。
JNIEXPORT void JNICALL Java_com_study_jnilearn_AccessMethod_callJavaInstaceMethod (JNIEnv *env, jclass cls) { jclass clazz = NULL; jobject jobj = NULL; jmethodID mid_construct = NULL; jmethodID mid_instance = NULL; jstring str_arg = NULL; // 1、從classpath路徑下搜尋ClassMethod這個類,並返回該類的Class物件 clazz = (*env)->FindClass(env, "com/study/jnilearn/ClassMethod"); if (clazz == NULL) { printf("找不到'com.study.jnilearn.ClassMethod'這個類"); return; } // 2、獲取類的預設構造方法ID mid_construct = (*env)->GetMethodID(env,clazz, "<init>","()V"); if (mid_construct == NULL) { printf("找不到預設的構造方法"); return; } // 3、查詢例項方法的ID mid_instance = (*env)->GetMethodID(env, clazz, "callInstanceMethod", "(Ljava/lang/String;I)V"); if (mid_instance == NULL) { return; } // 4、建立該類的例項 jobj = (*env)->NewObject(env,clazz,mid_construct); if (jobj == NULL) { printf("在com.study.jnilearn.ClassMethod類中找不到callInstanceMethod方法"); return; } // 5、呼叫物件的例項方法 str_arg = (*env)->NewStringUTF(env,"我是例項方法"); (*env)->CallVoidMethod(env,jobj,mid_instance,str_arg,200); // 刪除區域性引用 (*env)->DeleteLocalRef(env,clazz); (*env)->DeleteLocalRef(env,jobj); (*env)->DeleteLocalRef(env,str_arg); }
如何在jni中註冊native函式,有幾種註冊方式?
so 的載入流程是怎樣的,生命週期是怎樣的?
這個要從 java 層去看原始碼分析,是從 ClassLoader 的 PathList 中去找到目標路徑載入的,同時 so 是通過 mmap 載入對映到虛擬空間的。生命週期載入庫和解除安裝庫時分別呼叫 JNI_OnLoad 和 JNI_OnUnload() 方法。
21、請介紹一下NDK?
5、其他高頻面試題
1、如何保證一個後臺服務不被殺死?(相同問題:如何保證service在後臺不被kill?)比較省電的方式是什麼?
保活方案
1、AIDL方式單程序、雙程序方式保活Service。(基於onStartCommand() return START_STICKY)
START_STICKY 在執行onStartCommand後service程序被kill後,那將保留在開始狀態,但是不保留那些傳入的intent。不久後service就會再次嘗試重新建立,因為保留在開始狀態,在建立 service後將保證呼叫onstartCommand。如果沒有傳遞任何開始命令給service,那將獲取到null的intent。
除了華為此方案無效以及未更改底層的廠商不起作用外(START_STICKY欄位就可以保持Service不被殺)。此方案可以與其他方案混合使用
2、降低oom_adj的值(提升service程序優先順序):
Android中的程序是託管的,當系統程序空間緊張的時候,會依照優先順序自動進行程序的回收。Android將程序分為6個等級,它們按優先順序順序由高到低依次是:
- 1.前臺程序 (Foreground process)
- 2.可見程序 (Visible process)
- 3.服務程序 (Service process)
- 4.後臺程序 (Background process)
- 5.空程序 (Empty process)
當service執行在低記憶體的環境時,將會kill掉一些存在的程序。因此程序的優先順序將會很重要,可以使用startForeground 將service放到前臺狀態。這樣在低記憶體時被kill的機率會低一些。
-
常駐通知欄(可通過啟動另外一個服務關閉Notification,不對oom_adj值有影響)。
-
使用”1畫素“的Activity覆蓋在getWindow()的view上。
此方案無效果
- 迴圈播放無聲音訊(黑科技,7.0下殺不掉)。
成功對華為手機保活。小米8下也成功突破20分鐘
- 3、監聽鎖屏廣播:使Activity始終保持前臺。
- 4、使用自定義鎖屏介面:覆蓋了系統鎖屏介面。
- 5、通過android:process屬性來為Service建立一個程序。
- 6、跳轉到系統白名單介面讓使用者自己新增app進入白名單。
復活方案
1、onDestroy方法裡重啟service
service + broadcast 方式,就是當service走onDestory的時候,傳送一個自定義的廣播,當收到廣播的時候,重新啟動service。
2、JobScheduler:原理類似定時器,5.0,5.1,6.0作用很大,7.0時候有一定影響(可以在電源管理中給APP授權)。
只對5.0,5.1、6.0起作用。
3、推送互相喚醒復活:極光、友盟、以及各大廠商的推送。
4、同派系APP廣播互相喚醒:比如今日頭條系、阿里系。
此外還可以監聽系統廣播判斷Service狀態,通過系統的一些廣播,比如:手機重啟、介面喚醒、應用狀態改變等等監聽並捕獲到,然後判斷我們的Service是否還存活。
結論:高版本情況下可以使用彈出通知欄、雙程序、無聲音樂提高後臺服務的保活概率。
2、Android動畫框架實現原理。
Animation 框架定義了透明度,旋轉,縮放和位移幾種常見的動畫,而且控制的是整個View。實現原理:
每次繪製檢視時,View 所在的 ViewGroup 中的 drawChild 函式獲取該View 的 Animation 的 Transformation 值,然後呼叫canvas.concat(transformToApply.getMatrix()),通過矩陣運算完成動畫幀,如果動畫沒有完成,繼續呼叫 invalidate() 函式,啟動下次繪製來驅動動畫,動畫過程中的幀之間間隙時間是繪製函式所消耗的時間,可能會導致動畫消耗比較多的CPU資源,最重要的是,動畫改變的只是顯示,並不能響應事件。
3、Activity-Window-View三者的差別?
Activity像一個工匠(控制單元),Window像窗戶(承載模型),View像窗花(顯示檢視) LayoutInflater像剪刀,Xml配置像窗花圖紙。
在Activity中呼叫attach,建立了一個Window, 建立的window是其子類PhoneWindow,在attach中建立PhoneWindow。 在Activity中呼叫setContentView(R.layout.xxx), 其中實際上是呼叫的getWindow().setContentView(), 內部呼叫了PhoneWindow中的setContentView方法。
建立ParentView:
作為ViewGroup的子類,實際是建立的DecorView(作為FramLayout的子類), 將指定的R.layout.xxx進行填充, 通過佈局填充器進行填充【其中的parent指的就是DecorView】, 呼叫ViewGroup的removeAllView(),先將所有的view移除掉,新增新的view:addView()。
4、低版本SDK如何實現高版本api?
- 1、在使用了高版本API的方法前面加一個 @TargetApi(API號)。
- 2、在程式碼上用版本判斷來控制不同版本使用不同的程式碼。
5、說說你對Context的理解?
6、Android的生命週期和啟動模式
由A啟動B Activity,A為棧內複用模式,B為標準模式,然後再次啟動A或者殺死B,說說A,B的生命週期變化,為什麼?
Activity的啟動模式有哪些?棧裡是A-B-C,先想直接到A,BC都清理掉,有幾種方法可以做到?這幾種方法產生的結果是有幾個A的例項?
7、ListView和RecyclerView系列
RecyclerView和ListView有什麼區別?區域性重新整理?前者使用時多重type場景下怎麼避免滑動卡頓。懶載入怎麼實現,怎麼優化滑動體驗。
ListView、RecyclerView區別?
一、使用方面:
ListView的基礎使用:
- 繼承重寫 BaseAdapter 類
- 自定義 ViewHolder 和 convertView 一起完成複用優化工作
RecyclerView 基礎使用關鍵點同樣有兩點:
- 繼承重寫 RecyclerView.Adapter 和 RecyclerView.ViewHolder
- 設定佈局管理器,控制佈局效果
RecyclerView 相比 ListView 在基礎使用上的區別主要有如下幾點:
- ViewHolder 的編寫規範化了
- RecyclerView 複用 Item 的工作 Google 全幫你搞定,不再需要像 ListView 那樣自己呼叫 setTag
- RecyclerView 需要多出一步 LayoutManager 的設定工作
二、佈局方面:
RecyclerView 支援 線性佈局、網格佈局、瀑布流佈局 三種,而且同時還能夠控制橫向還是縱向滾動。
三、API提供方面:
ListView 提供了 setEmptyView ,addFooterView 、 addHeaderView.
RecyclerView 供了 notifyItemChanged 用於更新單個 Item View 的重新整理,我們可以省去自己寫區域性更新的工作。
四、動畫效果:
RecyclerView 在做區域性重新整理的時候有一個漸變的動畫效果。繼承 RecyclerView.ItemAnimator 類,並實現相應的方法,再呼叫 RecyclerView的 setItemAnimator(RecyclerView.ItemAnimator animator) 方法設定完即可實現自定義的動畫效果。
五、監聽 Item 的事件:
ListView 提供了單擊、長按、選中某個 Item 的監聽設定。
RecyclerView與ListView快取機制的不同
想改變listview的高度,怎麼做?
listview跟recyclerview上拉載入的時候分別應該如何處理?
如何自己實現RecyclerView的側滑刪除?
RecyclerView的ItemTouchHelper的實現原理
8、如何實現一個推送,訊息推送原理?推送到達率的問題?
一:客戶端不斷的查詢伺服器,檢索新內容,也就是所謂的pull 或者輪詢方式。
二:客戶端和伺服器之間維持一個TCP/IP長連線,伺服器向客戶端push。
9、動態許可權系列。
動態許可權適配方案,許可權組的概念
Runtime permission,如何把一個預置的app預設給它許可權?不要授權。
10、自定義View系列。
Canvas的底層機制,繪製框架,硬體加速是什麼原理,canvas lock的緩衝區是怎麼回事?
雙指縮放拖動大圖
TabLayout中如何讓當前標籤永遠位於螢幕中間
TabLayout如何設定指示器的寬度包裹內容?
自定義View如何考慮機型適配?
- 合理使用warp_content,match_parent。
- 儘可能地使用RelativeLayout。
- 針對不同的機型,使用不同的佈局檔案放在對應的目錄下,android會自動匹配。
- 儘量使用點9圖片。
- 使用與密度無關的畫素單位dp,sp。
- 引入android的百分比佈局。
- 切圖的時候切大解析度的圖,應用到佈局當中,在小解析度的手機上也會有很好的顯示效果。
11、對谷歌新推出的Room架構。
12、沒有給許可權如何定位,特定機型定位失敗,如何解決?
13、Debug跟Release的APK的區別?
14、android檔案儲存,各版本儲存位置的許可權控制的演進,外部儲存,內部儲存
15、有什麼提高編譯速度的方法?
16、Scroller原理。
Scroller執行流程裡面的三個核心方法
mScroller.startScroll();
mScroller.computeScrollOffset();
view.computeScroll();
1、在mScroller.startScroll()中為滑動做了一些初始化準備,比如:起始座標,滑動的距離和方向以及持續時間(有預設值),動畫開始時間等。
2、mScroller.computeScrollOffset()方法主要是根據當前已經消逝的時間來計算當前的座標點。因為在mScroller.startScroll()中設定了動畫時間,那麼在computeScrollOffset()方法中依據已經消逝的時間就很容易得到當前時刻應該所處的位置並將其儲存在變數mCurrX和mCurrY中。除此之外該方法還可判斷動畫是否已經結束。
17、Hybrid系列。
webwiew瞭解?怎麼實現和javascript的通訊?相互雙方的通訊。@JavascriptInterface在?版本有bug,除了這個還有其他呼叫android方法的方案嗎?
Android中Java和JavaScript互動
webView.addJavaScriptInterface(new Object(){xxx}, "xxx");
1
答案:可以使用WebView控制元件執行JavaScript指令碼,並且可以在JavaScript中執行Java程式碼。要想讓WebView控制元件執行JavaScript,需要呼叫WebSettings.setJavaScriptEnabled方法,程式碼如下:
WebView webView = (WebView)findViewById(R.id.webview);
WebSettings webSettings = webView.getSettings();
//設定WebView支援JavaScript
webSettings.setJavaScriptEnabled(true);
webView.setWebChromeClient(new WebChromeClient());
JavaScript呼叫Java方法需要使用WebView.addJavascriptInterface方法設定JavaScript呼叫的Java方法,程式碼如下:
webView.addJavascriptInterface(new Object()
{
//JavaScript呼叫的方法
public String process(String value)
{
//處理程式碼
return result;
}
}, "demo"); //demo是Java物件對映到JavaScript中的物件名
可以使用下面的JavaScript程式碼呼叫process方法,程式碼如下:
<script language="javascript">
function search()
{
//呼叫searchWord方法
result.innerHTML = "<font color='red'>" + window.demo.process('data') + "</font>";
}
18、如果在當前執行緒內使用Handler postdelayed 兩個訊息,一個延遲5s,一個延遲10s,然後使當前執行緒sleep 5秒,以上訊息的執行時間會如何變化?
答:照常執行
擴充套件:sleep時間<=5 對兩個訊息無影響,5< sleep時間 <=10 對第一個訊息有影響,第一個訊息會延遲到sleep後執行,sleep時間>10 對兩個時間都有影響,都會延遲到sleep後執行。
19、Android中程序記憶體的分配,能不能自己分配定額記憶體?
20、下拉狀態列是不是影響activity的生命週期,如果在onStop的時候做了網路請求,onResume的時候怎麼恢復
21、Android長連線,怎麼處理心跳機制。
長連線:長連線是建立連線之後, 不主動斷開. 雙方互相傳送資料, 發完了也不主動斷開連線, 之後有需要傳送的資料就繼續通過這個連線傳送.
心跳包:其實主要是為了防止NAT超時,客戶端隔一段時間就主動發一個數據,探測連線是否斷開。
伺服器處理心跳包:假如客戶端心跳間隔是固定的, 那麼伺服器在連線閒置超過這個時間還沒收到心跳時, 可以認為對方掉線, 關閉連線. 如果客戶端心跳會動態改變, 應當設定一個最大值, 超過這個最大值才認為對方掉線. 還有一種情況就是伺服器通過TCP連線主動給客戶端發訊息出現寫超時, 可以直接認為對方掉線.
22、CrashHandler實現原理?
獲取app crash的資訊儲存在本地然後在下一次開啟app的時候傳送到伺服器。
23、SurfaceView和View的最本質的區別?
SurfaceView是在一個新起的單獨執行緒中可以重新繪製畫面,而view必須在UI的主執行緒中更新畫面。
在UI的主執行緒中更新畫面可能會引發問題,比如你更新的時間過長,那麼你的主UI執行緒就會被你正在畫的函式阻塞。那麼將無法響應按鍵、觸屏等訊息。當使用SurfaceView由於是在新的執行緒中更新畫面所以不會阻塞你的UI主執行緒。但這也帶來了另外一個問題,就是事件同步。比如你觸屏了一下,你需要在SurfaceView中的thread處理,一般就需要有一個event queue的設計來儲存touchevent,這會稍稍複雜一點,因為涉及到執行緒安全。
24、Android程式執行時許可權與檔案系統許可權
1、Linux 檔案系統許可權。不同的使用者對檔案有不同的讀寫執行許可權。在android系統中,system和應用程式是分開的,system裡的資料是不可更改的。
2、Android中有3種許可權,程序許可權UserID,簽名,應用申明許可權。每次安裝時,系統根據包名為應用分配唯一的userID,不同的userID執行在不同的程序裡,程序間的記憶體是獨立的,不可以相互訪問,除非通過特定的Binder機制。
Android提供瞭如下的一種機制,可以使兩個apk打破前面講的這種壁壘。
在AndroidManifest.xml中利用sharedUserId屬性給不同的package分配相同的userID,通過這樣做,兩個package可以被當做同一個程式,系統會分配給兩個程式相同的UserID。當然,基於安全考慮,兩個package需要有相同的簽名,否則沒有驗證也就沒有意義了。
25、曲面屏的適配。
26、TextView呼叫setText方法的內部執行流程。
27、怎麼控制另外一個程序的View顯示(RemoteView)?
28、如何實現右滑finish activity?
29、如何在整個系統層面實現介面的圓角效果。(即所有的APP開啟介面都會是圓角)
30、非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。
31、如何解決git衝突?
32、單元測試有沒有做過,說說熟悉的單元測試框架?
首先,Android測試主要分為三個方面:
- 單元測試(Junit4、Mockito、PowerMockito、Robolectric)
- UI測試(Espresso、UI Automator)
- 壓力測試(Monkey)
WanAndroid專案和XXX專案中使用用到了單元測試和部分自動化UI測試,其中單元測試使用的是Junit4+Mockito+PowerMockito+Robolectric。下面我分別簡單介紹下這些測試框架:
1、Junit4:
使用@Test註解指定一個方法為一個測試方法,除此之外,還有如下常用註解@BeforeClass->@Before->@Test->@After->@AfterClass以及@Ignore。
Junit4的主要測試方法就是斷言,即assertEquals()方法。然後,你可以通過實現TestRule介面的方式重寫apply()方法去自定義Junit Rule,這樣就可以在執行測試方法的前後做一些通用的初始化或釋放資源等工作,接著在想要的測試類中使用@Rule註解宣告使用JsonChaoRule即可。(注意被@Rule註解的變數必須是final的。最後,我們直接執行對應的單元測試方法或類,如果你想要一鍵執行專案中所有的單元測試類,直接點選執行Gradle Projects下的app/Tasks/verification/test即可,它會在module下的build/reports/tests/下生成對應的index.html報告。
Junit4它的優點是速度快,支援程式碼覆蓋率如jacoco等程式碼質量的檢測工具。缺點就是無法單獨對Android UI,一些類進行操作,與原生Java有一些差異。
2、Mockito:
可以使用mock()方法模擬各種各樣的物件,以替代真正的物件做出希望的響應。除此之外,它還有很多驗證方法呼叫的方式如Mockit.when(呼叫方法).thenReturn(驗證的返回值)、verfiy(模擬物件).驗證方法等等。
這裡有一點要補充下:簡單的測試會使整體的程式碼更簡潔,更可讀、更可維護。如果你不能把測試寫的很簡單,那麼請在測試時重構你的程式碼。
最後,對於Mockito來說,它的優點是有各種各樣的方式去驗證"模仿物件"的互動或驗證發生的某些行為。而它的缺點就是不支援mock匿名類、final類、static方法private方法。
3、PowerMockito:
因此,為了解決Mockito的缺陷,PoweMockito出現了,它擴充套件了Mockito,支援mock匿名類、final類、static方法、private方法。只要使用它提供的api如PowerMockito.mockStatic()去mock含靜態方法或欄位的類,PowerMockito.suppress(PowerMockito.method(類.class, 方法名)即可。
4、Robolectric
前面3種我們說的都是Java相關的單元測試方法,如果想在Java單元測試裡面進行Android單元測試,還得使用Robolectric,它提供了一套能執行在JVM的Android程式碼。它提供了一系列類似ShadowToast.getLatestToast()、ShadowApplication.getInstance()這種方式來獲取Android平臺對應的物件。可以看到它的優點就是支援大部分Android平臺依賴類的底層引用與模擬。缺點就是在非同步測試的情況下有些問題,這是可以結合Mockito來將非同步轉為同步即可解決。
最後,自動化UI測試專案中我使用的是Expresso,它提供了一系列類似onView().check().perform()的方式來實現點選、滑動、檢測頁面顯示等自動化的UI測試效果,這裡在我的WanAndroid專案下的BasePageTest基類裡面封裝了一系列通用的方法,有興趣可以去看看。
33、Jenkins持續整合。
34、工作中有沒有用過或者寫過什麼工具?指令碼,外掛等等;比如:多人協同開發可能對一些相同資源都各自放了一份,有沒有方法自動檢測這種重複之類的。
35、如何繞過9.0限制?
如何限制?
- 1、阻止java反射和JNI。
- 2、當獲取方法或Field時進行檢測。
- 3、怎麼檢測?
區分出是系統呼叫還是開發者呼叫:
根據堆疊,回溯Class,檢視ClassLoader是否是BootStrapClassLoader。
區分後,再區分是否是hidden api:
Method,Field都有access_flag,有一些備用欄位,hidden資訊儲存其中。
如何繞過?
1、不用反射:
利用一個fakelib,例如寫一個android.app.ActivityThread#currentActivityThread空實現,直接呼叫;
2、偽裝系統呼叫:
jni修改一個class的classloder為BootStrapClassLoader,麻煩。
利用系統方法去反射:
利用原反射,即:getDeclaredMethod這個方法是系統的方法,通過getDeclaredmethod反射去執行hidden api。
3、修改Method,Field中儲存hidden資訊的欄位:
利用jni去修改。
36、對檔案描述符怎麼理解?
37、如何實現程序安全寫檔案?
面試資料PDF電子書
快速入手通道:(點這裡)百度網盤下載!誠意滿滿!!!
Android中高階面試題集解析電子書(1294頁)
快速入手通道:(點這裡)百度網盤下載!誠意滿滿!!!
聽說一鍵三連的粉絲都面試成功了?如果本篇部落格對你有幫助,請支援下小編哦
Android高階面試精選題、架構師進階實戰文件傳送門:我的GitHub
整理不易,覺得有幫助的朋友可以幫忙點贊分享支援一下小編~
你的支援,我的動力;祝各位前程似錦,offer不斷!!!
參考
https://juejin.cn/post/6844904079169159175
https://juejin.cn/post/6905226221357891592
https://juejin.cn/post/6888222422760488974