1. 程式人生 > >Android 5.0 以下Native程序保活嘗試

Android 5.0 以下Native程序保活嘗試

最近博主嘗試了Android 5.0 以下版本的Native保活機制,感覺收穫頗豐,在此寫下一篇部落格記錄一下。

首先把整個保活流程通過圖片的形式描述下:

Native程序保活流程

首先是AndroidManifest 中註冊的控制元件:

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name=".PersistService"
            android:process=":persist" />
        <receiver android:name=".WakeUpBroadcastReceiver"
            android:process=":wake_up"
            android:exported="true"/>

主要有三個控制元件:一個用於啟動服務的Activity,一個在:persist 子程序中的執行任務的服務Service,以及一個在:wake_up 子程序中用於拉活Service的BroadcastReceiver

MainActivity過於簡單在此就不做介紹了,PersistService的程式碼如下:

public class PersistService extends Service {

    private static final String TAG = "MyLog";

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        // 執行守護Daemon
        Log.d(TAG, "run daemon");
        Daemon.run(this, WakeUpBroadcastReceiver.class);
        new Thread(new Runnable() {
            @Override
            public void run() {
                int i = 0;
                while (true) {
                    Log.d("TestLive", "number is " + i++);
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {}
                }
            }
        }).start();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }
}

在PersistService的onCreate方法中,呼叫了Daemon類啟動守護程序對PersistService類進行守護,並啟動了新執行緒計數模擬工作。

Daemon類程式碼如下:

    private static final String DIR_NAME = "bin";
    private static final String FILE_NAME = "serviceDaemon";

    private static AlarmManager alarmManager;
    public static PendingIntent wakeUpIntent;

    static {
        System.loadLibrary("daemon-lib");
    }

    /**
     * 執行守護程序
     * @param context 上下文
     * @param wakeUpClass 喚醒Service類
     */
    public static void run(final Context context, final Class wakeUpClass) {
        // 初始化鬧鐘與喚醒用的intent
        alarmManager = ((AlarmManager)context.getSystemService(Context.ALARM_SERVICE));
        Intent intent = new Intent(context, wakeUpClass);
        wakeUpIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
        // 啟動守護程序
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 複製binary檔案
                String binaryFilePath = Command.install(context, DIR_NAME, FILE_NAME);
                if (binaryFilePath != null) {
                    // 執行
                    start(context.getPackageName(), wakeUpClass.getSimpleName(), binaryFilePath);
                }
            }
        }).start();
    }

    private native static void start(String packageName, String serviceClassName, String binaryFilePath);

在run方法中,首先呼叫了Command工具類根據系統ABI獲取在assets資料夾下相應的二進位制檔案,複製到特定地點後更改許可權等待執行:


然後啟動子執行緒通過JNI呼叫native的start方法,傳入包名、需要喚醒的類名以及二進位制檔案的路徑:

extern "C"
JNIEXPORT void JNICALL Java_com_app_superxlcr_mynativetest_Daemon_start
        (JNIEnv *env, jclass clazz, jstring jPackageName, jstring jWakeUpClassName,
         jstring jBinaryFilePath) {
    // 獲取Java引數
    const char *packageName = env->GetStringUTFChars(jPackageName, 0);
    const char *wakeUpClassName = env->GetStringUTFChars(jWakeUpClassName, 0);
    const char *binaryFilePath = env->GetStringUTFChars(jBinaryFilePath, 0);
    LOGD(MY_NATIVE_TAG, "packageName is %s", packageName);
    LOGD(MY_NATIVE_TAG, "wakeUpCLassName is %s", wakeUpClassName);
    LOGD(MY_NATIVE_TAG, "binaryFilePath is %s", binaryFilePath);
    // 建立管道,pipe1用於父程序監聽子程序,pipe2用於子程序監聽父程序
    int pipe1Fd[2];
    int pipe2Fd[2];
    if (pipe(pipe1Fd) == -1) {
        LOGE(MY_NATIVE_TAG, "create pipe1 error");
    }
    if (pipe(pipe2Fd) == -1) {
        LOGE(MY_NATIVE_TAG, "create pipe2 error");
    }
    // 執行二進位制檔案
    pid_t result = fork();
    if (result > 0) { // 父程序
        // 關閉pipe1的寫端
        close(pipe1Fd[1]);
        // 關閉pipe2的讀端
        close(pipe2Fd[0]);
        // 監聽子程序情況
        char readBuffer[100];
        int readResult = read(pipe1Fd[0], readBuffer, 100);
        LOGD(MY_NATIVE_TAG, "readResult is %d, errno is %d", readResult, errno);
        // 阻塞中斷,子程序已退出
        LOGD(MY_NATIVE_TAG, "child process is dead");
        // 釋放Java引數記憶體
        env->ReleaseStringUTFChars(jPackageName, packageName);
        env->ReleaseStringUTFChars(jWakeUpClassName, wakeUpClassName);
        env->ReleaseStringUTFChars(jBinaryFilePath, binaryFilePath);
        // 拉活處理,回撥Java方法
        jmethodID methodID = env->GetStaticMethodID(clazz, "onDaemonDead", "()V");
        env->CallStaticVoidMethod(clazz, methodID, NULL);
        return;
    } else if (result == 0) { // 子程序
        // 管道描述符轉為字串
        char strP1r[10];
        char strP1w[10];
        char strP2r[10];
        char strP2w[10];
        sprintf(strP1r, "%d", pipe1Fd[0]);
        sprintf(strP1w, "%d", pipe1Fd[1]);
        sprintf(strP2r, "%d", pipe2Fd[0]);
        sprintf(strP2w, "%d", pipe2Fd[1]);
        // 執行二進位制檔案
        LOGD(MY_NATIVE_TAG, "execute binary file");
        LOGD(MY_NATIVE_TAG, "binary file argv :%s %s %s %s %s %s %s %s %s %s %s %s %s",
             BINARY_FILE_NAME,
             PACKAGE_NAME, packageName,
             SERVICE_CLASS_NAME, wakeUpClassName,
             PIPE_1_READ, strP1r,
             PIPE_1_WRITE, strP1w,
             PIPE_2_READ, strP2r,
             PIPE_2_WRITE, strP2w);
        execlp(binaryFilePath,
               BINARY_FILE_NAME,
               PACKAGE_NAME, packageName,
               SERVICE_CLASS_NAME, wakeUpClassName,
               PIPE_1_READ, strP1r,
               PIPE_1_WRITE, strP1w,
               PIPE_2_READ, strP2r,
               PIPE_2_WRITE, strP2w,
               NULL);
        // 函式返回,執行失敗
        LOGE(MY_NATIVE_TAG, "execute binary file fail, errno is %d", errno);
    } else {
        LOGE(MY_NATIVE_TAG, "fork fail!");
    }
    // 釋放Java引數記憶體
    env->ReleaseStringUTFChars(jPackageName, packageName);
    env->ReleaseStringUTFChars(jWakeUpClassName, wakeUpClassName);
    env->ReleaseStringUTFChars(jBinaryFilePath, binaryFilePath);
}

在start方法中,我們首先獲取了從Java層傳過來的引數,然後通過pipe函式開啟了兩條匿名管道用於服務程序與其後代守護程序監聽存活情況:
  • pipe1:管道1,用於父程序監聽子程序的存活情況,父程序關閉pipe1的寫端,子程序關閉pipe1的讀端
  • pipe2:管道2,用於子程序監聽父程序的存活情況,父程序關閉pipe2的讀端,子程序關閉pipe2的寫端
然後通過fork建立相應的子程序,在父程序中通過管道監聽子程序的狀況,在子程序中通過exec函式執行二進位制檔案重生為守護程序。 當父程序管道讀取返回時,代表子程序已被銷燬。此時,父程序通過回撥Java層的onDeamonDead方法喚醒拉活程序並自殺等待重啟:
    public static void onDaemonDead() {
        Log.d(TAG, "call onDaemonDead!");
        // 鬧鐘啟動拉活程序
        alarmManager.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 5000, wakeUpIntent);
        // 自殺
        Process.killProcess(Process.myPid());
    }

二進位制檔案程式碼:
int main(int argc, char *argv[]) {
    // 引數個數檢查
    if (argc < 13) {
        LOGE(MY_NATIVE_TAG, "argc number is %d, expected 13!", argc);
        return 0;
    }
    char *processName = argv[0];
    char *packageName = NULL;
    char *wakeUpClassName = NULL;
    int pipe1Fd[2];
    int pipe2Fd[2];
    for (int i = 0; i < argc; i++) {
        if (!strcmp(PACKAGE_NAME, argv[i])) {
            packageName = argv[i + 1];
            LOGD(MY_NATIVE_TAG, "packageName is : %s", packageName);
        }
        if (!strcmp(SERVICE_CLASS_NAME, argv[i])) {
            wakeUpClassName = argv[i + 1];
            LOGD(MY_NATIVE_TAG, "wakeUpClassName is : %s", wakeUpClassName);
        }
        if (!strcmp(PIPE_1_READ, argv[i])) {
            pipe1Fd[0] = atoi(argv[i + 1]);
            LOGD(MY_NATIVE_TAG, "pipe1r is : %d", pipe1Fd[0]);
        }
        if (!strcmp(PIPE_1_WRITE, argv[i])) {
            pipe1Fd[1] = atoi(argv[i + 1]);
            LOGD(MY_NATIVE_TAG, "pipe1w is : %d", pipe1Fd[1]);
        }
        if (!strcmp(PIPE_2_READ, argv[i])) {
            pipe2Fd[0] = atoi(argv[i + 1]);
            LOGD(MY_NATIVE_TAG, "pipe2r is : %d", pipe2Fd[0]);
        }
        if (!strcmp(PIPE_2_WRITE, argv[i])) {
            pipe2Fd[1] = atoi(argv[i + 1]);
            LOGD(MY_NATIVE_TAG, "pipe2w is : %d", pipe2Fd[1]);
        }
    }

    // 成為守護程序
    int forkResult = fork();
    if (forkResult == 0) {
        // 成為會話組長
        setsid();
        // 改變工作目錄
        chdir("/");

        // 清理殭屍程序
        cleanZombieProcess(processName);

        // 管道處理
        // 關閉pipe1的讀端
        close(pipe1Fd[0]);
        // 關閉pipe2的寫端
        close(pipe2Fd[1]);

        // 管道監聽,監聽父程序情況
        char readBuffer[100];
        int readResult = read(pipe2Fd[0], readBuffer, 100);
        LOGD(MY_NATIVE_TAG, "readResult is %d, errno is %d", readResult, errno);
        // 阻塞中斷,父程序已退出
        LOGD(MY_NATIVE_TAG, "parent process is dead");
        // 拉活處理
        pid_t childPid = fork();
        if (childPid == 0) {
            // 喚醒廣播拉活
            char *wakeUpName = new char[strlen(packageName) + strlen(wakeUpClassName) + 1];
            sprintf(wakeUpName, "%s/.%s", packageName, wakeUpClassName);
            LOGD(MY_NATIVE_TAG, "wakeUpName is %s", wakeUpName);
            int result = execlp("am", "am", "broadcast",
                                "--user", "0", "-n", wakeUpName, (char *) NULL);
            LOGD(MY_NATIVE_TAG, "execute am broadcast result is %d", result);
        } else if (childPid > 0) {
            waitpid(childPid, NULL, 0);
            LOGD(MY_NATIVE_TAG, "execute am broadcast over");
        } else {
            LOGE(MY_NATIVE_TAG, "fork fail!");
        }
        return 0;
    } else if (forkResult > 0) {
        return 0;
    } else {
        LOGE(MY_NATIVE_TAG, "fork fail!");
    }
}



在二進位制檔案中,首先檢查了傳入的引數,然後使該程序成為Linux的守護程序(沒有關閉標準輸入輸出錯誤、關閉後執行am指令會出問題),並清理了以前剩下的殭屍守護程序,然後通過管道監聽父程序的存活情況,當父程序死亡時,通過exec函式執行am指令傳送廣播喚醒拉活程序,並退出程式關閉守護程序。 負責拉活程序的廣播接收器如下:
public class WakeUpBroadcastReceiver extends BroadcastReceiver {

    private static final String TAG = "MyLog";

    @Override
    public void onReceive(Context context, Intent intent) {
        // 拉活處理
        Log.d(TAG, "Receiver Start Service");
        Intent serviceIntent = new Intent(context, PersistService.class);
        context.startService(serviceIntent);
    }
}

下面是在Android 4.3 CoolPad手機上測試的效果: 首次啟動: 首次啟動
在adb shell中通過force-stop關閉服務程序: force-stop
守護程序檢測到Service程序被殺死,進行拉活處理: 子程序喚醒
此次守護程序的pid為21561,我們嘗試通過kill命令殺死守護程序:
Service程序檢測到守護程序被殺死,進行拉活處理: 父程序喚醒
通過測試發現,無論是Service程序死亡還是守護程序死亡,另一方都能通過喚醒拉活程序將其救活,真正實現了Native層的雙程序守護功能。 然而,在Android 5.0以上版本該方法並沒有效果……目前博主正在進行進一步的學習研究。