一.准备工作:
因为我们的工程会包含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保活)》