更新时间:2024-11-18 GMT+08:00
分享

云手机内管理程序保活方案

管理程序形态分类

在云手机内的管理程序,基本分为两类:

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

用户级保活方式有以下优缺点:

优点:不需要重启手机即可生效。

缺点:拉起的实时性不如系统级,依赖检查的循环间隔时间。

相关文档