從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執行緒池可參考:
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);
這裡和從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,也就是執行一句:
am->transact(START_SERVICE_TRANSACTION, data, &reply);
把資料提交給遠端的系統服務,這樣就能夠在極短的時間內把另外一個程序拉起,可以做到即便是force-stop也難以殺死:
但是隻能在原生系統上保活,廠商畢竟會修改一些原始碼,導致保活不成功,所以為了相容不同廠商的機子,還需要做一些適配,看下篇文章《從native層去startService(非原生系統7.1保活)》