1. 程式人生 > >從native層實現startService(android7.1原生系統保活)

從native層實現startService(android7.1原生系統保活)

一.準備工作:

因為我們的工程會包含binder庫和binder間接依賴的cutils庫,但這些庫都不在Ndk裡面,直接在native層去編譯會找不到,所以要放在原始碼環境下編譯。

#include <binder/MemoryHeapBase.h>
#include <binder/ProcessState.h>
#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
#include <binder/IInterface.h>
#include <binder/Parcel.h>

在原始碼下的/packages/experimental/ 目錄(這個目錄是用來建立系統服務程式的,既可以建立android的app,也可以建立庫、可執行檔案等,都是用Android.mk來編譯,原始碼裡面也有一些例子,建立的目標檔案都是基於android系統環境)下建立一個資料夾,比如叫‘nativeService26’,把程式碼和Android.mk都放進去,接著在aosp目錄(原始碼要make過的)下執行:

source build/envsetup.sh
mmm  /packages/experimental/nativeService26

以建立一個可執行檔案為例,建立成功後,會在下面路徑生成一個二進位制檔案,拿過來用就是了。這裡有個點額外說明一下,之前想在這裡編譯個so檔案在應用層去用,後來發現不行,執行的時候會關聯很多系統庫檔案,所以就編譯一個二進位制檔案出來。

out/target/product/generic/system/bin/native_service26

二.程式碼:
主要是這樣的一個流程:

int main(int argc, char* argv[])
{
    android::ProcessState::self()->startThreadPool();
    sp<IBinder> am = getActivityManagerService();
    startService(); 
    return 1;
}

①先啟動一個binder執行緒池,因為我們執行這個二進位制檔案時是fork出來的子程序中執行的,還沒有binder執行緒池,要自己先手動啟動一個,相關的程式碼在binder/IPCThreadState.h 這裡。相關的binder執行緒池可參考:

http://gityuan.com/2016/10/29/binder-thread-pool/
②從native層拿到AMS,通過ServiceManager去拿到,相關的程式碼在binder/IServiceManager.h 裡面。
③建立Parcel,通過AMS把資料傳遞給系統服務:

	Parcel data , reply;
    String16 des = String16("android.app.IActivityManager");
    data.writeInterfaceToken(des);
    data.writeStrongBinder(NULL); 
    // intent.writeToParcel 
    data.writeString16(NULL, 0);  // action
    data.writeInt32(NULL_TYPE_ID); //uri 
    data.writeString16(NULL, 0);              /* type */
    data.writeInt32(0);    // flag
    data.writeString16(NULL, 0); // package
    data.writeString16(String16("com.example.myapplication"));  //component package
    data.writeString16(String16("com.example.myapplication.DemoService"));
    data.writeInt32(0);               /* source bound - size */
    data.writeInt32(0);               /* Categories - size */
    data.writeInt32(0);               /* selector - size */
    data.writeInt32(0);                /* ClipData */
    data.writeInt32(-1);          // UserHint           
    data.writeInt32(-1);      /* bundle(extras) size */
    // intent datas end
    data.writeString16(NULL, 0);  /* resolvedType */
    data.writeString16(String16("com.example.myapplication"));  // calling package
    data.writeInt32(false);       /* userid */
    status_t ret = am->transact(START_SERVICE_TRANSACTION, data, &reply);

不知道為什麼要這樣寫資料的,可以看我之前那篇文章《關於Android的Parcel》

這裡和從java層去startService的資料寫入過程是一一對應的,看上去比較多是因為intent的資料也直接寫上去了,intent.writeToParcel其實寫了很多資料進去:

public void writeToParcel(Parcel out, int flags) {
    out.writeString(mAction);
    Uri.writeToParcel(out, mData);
    out.writeString(mType);
    out.writeInt(mFlags);
    out.writeString(mPackage);
    ComponentName.writeToParcel(mComponent, out);
    if (mSourceBounds != null) {
        out.writeInt(1);
        mSourceBounds.writeToParcel(out, flags);
    } else {
        out.writeInt(0);
    }
    if (mCategories != null) {
        final int N = mCategories.size();
        out.writeInt(N);
        for (int i=0; i<N; i++) {
            out.writeString(mCategories.valueAt(i));
        }
    } else {
        out.writeInt(0);
    }
    if (mSelector != null) {
        out.writeInt(1);
        mSelector.writeToParcel(out, flags);
    } else {
        out.writeInt(0);
    }
    if (mClipData != null) {
        out.writeInt(1);
        mClipData.writeToParcel(out, flags);
    } else {
        out.writeInt(0);
    }
    out.writeInt(mContentUserHint);
    out.writeBundle(mExtras);
}

三.保活方案:
主要是參考了MarsDaemon (https://github.com/Marswin/MarsDaemon),這個保活模組還是非常強大的,主要是在程序之間使用檔案鎖的方法,一個程序死了之後,另外一個程序會馬上得到檔案鎖,感知到死亡之後,馬上去拉起那個程序,其實還是要跟系統爭取時間。但是在初始化的時候要先把一些準備工作做好,比如他的程式碼裡,先把廣播的parcel的資料先填充好,在native層感知到程序死亡之後馬上回調java層的方法,把廣播或者是service發出去,這樣就能節省很多時間。

@SuppressLint("Recycle")// when process dead, we should save time to restart and kill self, don`t take a waste of time to recycle
	private void initBroadcastParcel(Context context, String broadcastName){
		Intent intent = new Intent();
        ComponentName componentName = new ComponentName(context.getPackageName(), broadcastName);
        intent.setComponent(componentName);
        intent.setFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
		
		/*	 
//      Object contextImpl = ((Application)context.getApplicationContext()).getBaseContext();
        //this context is ContextImpl, get MainThread instance immediately
        Field mainThreadField = context.getClass().getDeclaredField("mMainThread");
        mainThreadField.setAccessible(true);
      	Object mainThread = mainThreadField.get(context);
      	//get ApplicationThread instance
      	Object applicationThread = mainThread.getClass().getMethod("getApplicationThread").invoke(mainThread);  
      	//get Binder
      	Binder callerBinder = (Binder) (applicationThread.getClass().getMethod("asBinder").invoke(applicationThread));
		 */
      
//      	UserHandle userHandle = android.os.Process.myUserHandle();
//      	int handle = (Integer) userHandle.getClass().getMethod("getIdentifier").invoke(userHandle);
		
      	mBroadcastData = Parcel.obtain();
      	mBroadcastData.writeInterfaceToken("android.app.IActivityManager");
//      	mBroadcastData.writeStrongBinder(callerBinder);
      	mBroadcastData.writeStrongBinder(null);
      	intent.writeToParcel(mBroadcastData, 0);
      	mBroadcastData.writeString(intent.resolveTypeIfNeeded(context.getContentResolver()));
      	mBroadcastData.writeStrongBinder(null);
      	mBroadcastData.writeInt(Activity.RESULT_OK);
      	mBroadcastData.writeString(null);
      	mBroadcastData.writeBundle(null);
      	mBroadcastData.writeString(null);
      	mBroadcastData.writeInt(-1);
      	mBroadcastData.writeInt(0);
      	mBroadcastData.writeInt(0);
//      	mBroadcastData.writeInt(handle);
      	mBroadcastData.writeInt(0);
	}

資料準備好,需要傳送廣播的時候隨時傳送:

private boolean sendBroadcastByAmsBinder(){	
		try {
			if(mRemote == null || mBroadcastData == null){
				Log.e("Daemon", "REMOTE IS NULL or PARCEL IS NULL !!!");
				return false;
			}
			mRemote.transact(14, mBroadcastData, null, 0);//BROADCAST_INTENT_TRANSACTION = 0x00000001 + 13
			return true;
		} catch (RemoteException e) {
			e.printStackTrace();
			return false;
		}
	}

MarsDaemon相容到android6,在7和7以後,因為Android修改了killProcess的方法(具體可以去研究下原始碼,邊幅有限,不在這裡細說),7以後殺程序的速度會快很多,主要是killProcessQuiet和killProcessGroup這兩個方法,7以前是同步的,7的時候就變成非同步了,java程序很快就被幹掉了,這個時候還去回撥Java層的方法,時間上就來不及了。所以在native層也先把資料startService需要的parcel資料準備好,感知到程序掛掉的時候直接去startService,也就是執行一句:

am->transact(START_SERVICE_TRANSACTION, data, &reply);

把資料提交給遠端的系統服務,這樣就能夠在極短的時間內把另外一個程序拉起,可以做到即便是force-stop也難以殺死:
在這裡插入圖片描述

但是隻能在原生系統上保活,廠商畢竟會修改一些原始碼,導致保活不成功,所以為了相容不同廠商的機子,還需要做一些適配,看下篇文章《從native層去startService(非原生系統7.1保活)》

參考:https://blog.csdn.net/alien75/article/details/50789168