如果學習Android加固與脫殼不學習這些基礎的話,那麼後面加固點和脫殼點能讓你輕輕鬆鬆看得雲裏霧裏,不知所以。想學這一門手藝,該跨的門檻一點兒馬虎不得,但是我有預感,當你把這些基礎底層難啃的知識整出些門道之後,看後面的加固和脫殼出現的名詞和爲什麼這樣做時,你就會感到:原來是這樣。
當我們在手機屏幕上點擊一個APP之後,從技術角度透過屏幕和硬件,觀察它的內部發生了哪些變化,在安卓系統又經過了怎樣的流程呢?
一、啓動的地方
在我們編寫Android應用的時候,邏輯開始的地方是onCreate(),但是當我們點擊APP的時候,首先是經過Android系統出發,這裏是真正啓動的代碼:
// 這裏是真正啓動的代碼:
Process.ProcessStartResult startResult = Process.start(
entryPoint,
app.processName,
uid, uid, gids,
debugFlags,
mountExternal,
app.info.targetSdkVersion,
app.info.seinfo,
requiredAbi, instructionSet,
app.info.dataDir, entryPointArgs
);
Process.start
裏面有很多參數,現在不需要疑惑它們是幹啥的,隨着後面的介紹,你會發現好多參數並不在這個課題的討論之下,可以不用管它。
但是這個Process.start()
函數很重要
app.processName是進程名,在安卓系統中,經歷了這個過程:
Process.start()通過Binder IPC創建系統服務(system_server),然後系統服務通過socket發送請求給Zygote這塊流程,就和安卓系統啓動虛擬器流程圖對上了。
我們看下圖2中的system_server主要乾了哪些事。
看精簡版的部分源代碼:
ZygoteInit.java
private static boolean startSystemServer(String abiList, String socketName) throws MethodAndArgsCaller,RuntimeException{
...
//Request to fork the system server process
pid = Zygote.forkSystemServer(
...
);
...
//子進程返回0,即systemServer
if (pid == 0){
if (hasSecondZygote(abiList)){
waitForSecondaryZygote(socketName);
}
handleSystemServerProcess(parsedArgs);
}
}
- 0:代表着是在新建的子進程中執行的。
- pid of child:在Zygote創建的父進程裏再創建的子進程
- -1:代表出錯。
這裏我們分析下pid = 0的邏輯,我的理解是APP運行時,不會是由Zygote創建的父進程直接創建子進程給APP,因爲在我們打開應用程序之前,Zygote已經創建了多個系統自帶的應用程序。
if (pid == 0)後的邏輯。
在start()方法通過socket的方式向Zygote進程發起創建請求之後,Zygote.forkSystemServer()
會調用handleSystemServerProcess()
private static void handleSystemServerProcess(ZygoteConnection.Arguments parsedArgs)throws ZygoteInit.MethodAndArgsCaller{
//關閉socket服務
closeServerSocket();
...
...
//創建classLoader並且把它設爲currentThread()當前線程的classLoader
ClassLoader cl = null;
if (systemServerClasspath != null){
// createSystemServerClassLoader()創建的是一個PathClassLoader
cl = createSystemServerClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion);
Thread.currentThread().setContextClassLoader(cl);
}
...
}
運行到這裏時,代表當前是系統服務的進程,所以也創建了系統服務的classLoader,設置了Thread.current().setContextClassLoader()
類加載器,後面的應用中所有的類默認是通過這個類加載器來加載的。
至於什麼是classLoader和類加載器的概念,可以閱讀這篇文章。
二、創建Application
Application是APP運行的基礎,每當應用程序啓動時,系統會自動將這個類進行初始化,Application的聲明週期就是整個APP的生命週期。
在擁有了Thread.current().setContextClassLoader()
類加載器後,通過socket反射調用了ActivityThread.main()方法,創建了ActivityThread後,進行attach()操作。
ActivityThread.java
public static void main(String[] args){
...
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
...
Looper.loop()
throw new RuntimeException("Main thread loop unexpectedly exited");
}
// 在thread.attach()裏面進行了attachApplication(mAppThread)同時會調用thread.bindApplication() 這裏是ActivityThread.bindApplication()
public final void bindApplication(...){
...
//最後一句,發送了一條message消息
sendMessage(H.BIND_APPLICATION, data);
}
//在ActivityThread.handleMessage()中對BIND_APPLICATION的接收後調用了handleBindApplication()
// 真正綁定application的地方
private void handleBindApplication(AppBindData data){
...
...
...
Application app = data.info.makeApplication(data.restrictedBackupMode, null);
mInitiApplication = app;
...
}
言而總之,attach操作,是把創建的系統服務進程和我們的app進行了一系列的綁定,不做過多分析,我們只看重要的部分。
attach操作接下來會調用LoadedApk.java, ApplicationLoaders.java
部分
這兩個類我們從名字上就能看出一二,是幹什麼的。
其中LoadedApk.java
裏的內容是我們脫殼和加固內容中非常重要的類,後續遇到時再講。
其中,LoadedApk.makeApplication()
裏面的代碼會去獲取classLoader,並且創建appContext,在通過classLoader和appContext去創建application對象。
// LoadedApk.java
public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation){
...
// getClassLoader()是去獲取一個與當前apk相關聯的pathClassLoader,
java.lang.ClassLoader cl = getClassLoader();
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
//app爲application
app = mActivityThread.mInstrumentation.newApplicationn(
cl, appClass, appContext);
}
總結:創建appication的過程
- mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext)
- 第一個參數cl,是當前app的classLoader
- 第二個參數是appClass,當前app application的名字,檢索的包名
- appContext是新建的context
- newApplication,利用cl.loadClass(appClass)得到當前app的application
- 補充loadClass後得到的clazz,下一步進行
其中app.attach(context),會調用application的attachBaseContext();
然後調用instrumentation.callApplicationOnCreate(app)
,會調用application.onCreate()
方法;
目前爲止,thread.attach();引起了一系列的事件,現在已經正式創建了appication,可以執行application的onCreate()方法。
三、apk什麼時候與classLoader什麼時候關聯
我們看下創建Application的時候這個語句:
// getClassLoader()是去獲取一個與當前apk相關聯的pathClassLoader,
java.lang.ClassLoader cl = getClassLoader();
這裏稍微提一下,上面的代碼出現的pathClassLoader,是動態加載部分的內容,順便提一下它的作用和同類。
- DexClassLoader:可以加載jar/apk/dex,可以加載sd卡中未安裝的apk;
- PathClassLoader:只能加載系統中已經安裝過的 apk;
實現cl的getClassLoader
實現:
public ClassLoader getClassLoader() {
synchronized(this){
if (mClassLoader == null){
createOrUpdateClassLoaderLocked(null)
}
return mClassLoader;
}
}
private void createOrUpdateClassLoaderLocked(List<String> addedPaths){
...
//
mClassLoader = ApplicationLoaders.getDefault().getClassLoader("", mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath, libraryPermittedPath, mBaseClassLoader);
}
對mClassLoader進行了賦值,那內部的ApplicationLoaders.getDefault().getClassLoader()
是如何實現的呢?
對於ApplicationLoaders這個類,它裏面維護了一個mLoaders,map結構,key爲string,可以理解爲 包名,value爲ClassLoader(類加載器)。
public ClassLoader getClassLoader(String zip, ...){
...
ClassLoader loader = mloaders.get(zip);
if(loader != null){
return loader;
}
PathClassLoader pathClassloader = PathClassLoaderFactory.createClassLoader(...);
...
mLoaders.put(zip, pathClassloader);
return pathClassloader;
...
}
每個app進程都有唯一的ApplicationLoaders實例,後續則通過apk的路徑(zip參數)查詢返回classLoader。
因爲ApplicationLoaders
裏維護了classLoader的一個map常量mLoaders,所以一個進程可以對應多個APK。
這樣說吧,一個app有唯一的ApplicationLoaders
實例對應,但是一個進程裏的ApplicationLoaders
,可以維護多個apk。
總結一下,APP啓動時發生的事情:
- 先從zygote fork一個進程
- fork進程之後,會調用ActivityThread.main(),new出一個ActivityThread對象並且會把它與application關聯起來,即attach();
- attach會引發一系列事件,先後創建classLoader, appContext和application把application和activityThread關聯起來。
- 調用
instrumentation.callApplicationOnCreate(app)
,觸發application的onCreate()方法。
寫到這裏,我不禁留下了熱淚,過程如此之多,差點把自己繞進去了。不過消化這個過程是必須的,我用了一張比較詳細的思維導圖來描述這整個過程。
這裏提示下,光看沒有用,最好動手對照着畫下自己理解的思維導圖,等你把這些東西消化後理解流程就水到渠成了。
Refer: