云手机内管理程序保活方案
管理程序形态分类
在云手机内的管理程序,基本分为两类:
Android APK Service 形态,即以Android无UI的Service的形式在云手机后台运行。
Android JNI Native 形态,即用Android JNI开发的Executable的二进制可执行程序。
管理程序保活方案
两种形态的管理程序保活方案都依赖一个关键点:extend_custom.sh,即在云手机可内置的钩子脚本。详细信息请参见开机回调脚本。
钩子脚本 extend_custom.sh 的机制说明:
云手机在系统 boot_complete 之后会检查 /data/local/tmp 目录下是否有 extend_custom.sh 脚本,如果有则会执行此脚本。客户可以在此脚本中执行自己文件的操作和移动,也可以启动和管理自己的程序等,但是有个限制条件是脚本执行超时时间是10s。
1. Service形态的管理程序保活
Service形态的程序由于系统机制限制,首次安装之后,如果不启动是属于处于停止状态的,而开机启动完成的广播使用了FLAG_EXCLUDE_STOPPED_PACKAGES ,会使得处于停止状态的应用收不到开机启动完成广播。因此,service管理程序的的安装和首次启动都需要依赖在云手机 /data/local/tmp 目录下内置 extend_custom.sh 脚本来实现,后续重启开机就可以通过接收开机启动完成广播来自启动。
自启动可以在AndroidManifest.xml里配置接收开机广播并在对应的广播处理里启动Service。
<!-- example code-->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<!-- 适配 8.0及以上版本 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<receiver android:name=".DemoServiceReceiver">
<intent-filter android:priority="1000">
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
开机广播处理并启动Service:
// example code
public class DemoServiceReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (!intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
return;
}
Intent serviceIntent = new Intent(context, DemoService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(serviceIntent);
} else {
context.startService(serviceIntent);
}
}
}
service 程序的保活可以在对应的 Service 类里的 onStartCommand 函数里返回 START_STICKY,这样的话,当 Service 进程被系统 kill 后可以被重新拉起。
// example code
public class DemoService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//other todo...
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { //适配8.0及以上
String channel_id = "MyTestService-id";
String channel_name = "MyTestService-name";
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
NotificationChannel Channel = new NotificationChannel(channel_id, channel_name, NotificationManager.IMPORTANCE_HIGH);
if (manager != null) {
manager.createNotificationChannel(Channel);
}
Notification notification = new Notification.Builder(this).setChannelId(channel_id).setSmallIcon(R.mipmap.ic_launcher).build();
startForeground(100, notification);
}
return START_STICKY;
}
}
安装和首次启动依赖的 extend_custom.sh 脚本实现,示例如下(示例中默认apk文件也在 /data/local/tmp 目录下):
# example code
# !/system/bin/sh
# APK名称
ApkName=service.apk
# APK包名
PackageName=com.huawei.myapplication
# Service
ServiceName=com.huawei.myapplication/.MyService
InstallService() {
count=`pm list packages | grep $PackageName |wc -l`
if [ $count -le 0 ]; then
echo "$ApkName is not installed, now install."
ret=`pm install $ApkName`
Succ="Success"
if [ "$ret" == "$Succ" ]; then
echo "install succeed."
else
echo "install failed."
exit 1
fi
else
echo "$ApkName is already installed."
echo "Service start by boot_complete broadcast."
exit 1
fi
return 0
}
StartService() {
ret=`am startservice -n $ServiceName`
Err="Error: Not found; no service started."
contain=$(echo $ret | grep "${Err}")
if [[ "$contain" != "" ]]; then
echo "Service start failed."
exit 1
else
echo "Service start succeed."
fi
return 0
}
main() {
# 安装
InstallService
echo "To start when first installed."
# 启动
StartService
}
2. Native形态的管理程序保活
Native 管理程序需要被放在云手机 /system/bin 目录下,并给予可执行权限,Native程序依赖的二进制so库需要放在 system/lib 和 system/lib64 目录下。
Native程序、二进制so库文件等都可以通过共享存储或共享应用的方式推进手机的data目录,然后利用 extend_custom.sh 脚本将自己的文件放置在对应的目录下。针对使用诉求不同,有以下几种方式:
(1)系统级保活
可以实现 init_custom.rc 文件,并将文件移动到 /data/local/目录下,然后需要重启手机生效,重启手机后系统会扫描到 init_custom.rc 文件并在 boot_completed 之后启动 NativeDemo(示例中 Native 管理程序名),如果 NativeDemo 进程异常退出或被kill都会被系统再次拉起。
on property:sys.boot_completed=1
start NativeDemo
service NativeDemo /system/bin/NativeDemo
user root
group root
disabled
writepid /dev/cpuset/system-background/tasks
系统级保活方式有以下优缺点:
优点:系统级的自启动和保活,可靠性高,拉起实时性高。
缺点:需要重启一次手机才可生效。
(2)用户级保活
如果既需要开机自启又需要保活管理程序,而且不希望重启手机生效,可以先在 extend_custom.sh 脚本中执行一个管理程序的检测保活脚本native_demo_monitor.sh,由管理程序的检测保活脚本真正实现 NativeDemo 管理程序的启动和保活。

extend_custom.sh 钩子脚本实现示例:
# example code # !/system/bin/sh [[ -f /data/local/tmp/native_demo_monitor.sh ]] && sh /data/local/tmp/native_demo_monitor.sh &
native_demo_monitor.sh 脚本实现示例:
# example code
# !/system/bin/sh
file_dir="/data/demo"
CopyFile() {
cp -rf $file_dir/NativeDemo /system/bin/
chmod 755 /system/bin/NativeDemo
cp $file_dir/lib*.so /system/lib64/
}
CheckIfCopyFile() {
if [ -s $file_dir ]; then
echo "file exist"
CopyFile
else
echo "file not exist"
fi
}
Start() {
nohup NativeDemo &
}
KeepAlive() {
while do
echo "check NativeDemo proc"
proc_count=`ps | grep NativeDemo | wc -l`
if [ $proc_count -le 0 ]; then
echo "start NativeDemo"
Start
else
echo "NativeDemo already started"
fi
sleep 5
done
}
main() {
CheckIfCopyFile
KeepAlive
}
main
用户级保活方式有以下优缺点:
优点:不需要重启手机即可生效。
缺点:拉起的实时性不如系统级,依赖检查的循环间隔时间。