
# 云手机内管理程序保活方案
#### 管理程序形态分类
在云手机内的管理程序，基本分为两类：
Android APK Service 形态，即以Android无UI的Service的形式在云手机后台运行。
Android JNI Native 形态，即用Android JNI开发的Executable的二进制可执行程序。
#### 管理程序保活方案
两种形态的管理程序保活方案都依赖一个关键点：extend_custom.sh，即在云手机可内置的钩子脚本。详细信息请参见[开机回调脚本](https://support.huaweicloud.com/usermanual-cph/cph_ug_0041.html)。
钩子脚本 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 管理程序的启动和保活。
![](https://support.huaweicloud.com/bestpractice-cph/zh-cn_image_0000001387683069.png "点击放大")
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
```
用户级保活方式有以下优缺点：
优点：不需要重启手机即可生效。
缺点：拉起的实时性不如系统级，依赖检查的循环间隔时间。
