天天看點

【Android 程序保活】應用程序拉活 ( 雙程序守護保活 )

文章目錄

  • 一、 雙程序守護保活原理
  • 二、 雙程序守護保活完整源碼
  • 1、AIDL 接口
  • 2、本地前台服務 Service
  • 3、遠端前台服務 Service
  • 4、清單配置
  • 5、啟動兩個服務
  • 5、執行效果
  • 三、 源碼資源

一、 雙程序守護保活原理

雙程序守護拉活 , 使用 JobScheduler 拉活 和 系統 Service 機制拉活 兩種拉活方式 , 結合起來使用 ;

雙程序機制拉活 , 比之前的 廣播拉活 , 系統 Service 機制拉活 , 賬戶同步拉活 , JobScheduler 機制拉活 , 成功率都要高 , 可靠性比較高 , 但是也存在失敗的情況 ;

JobScheduler 原理 :

在應用中 , 運作了一個主程序 , 除此之外 , 還運作了一個 " 本地前台程序 " , 運作該 " 本地前台程序 " 時 , 開啟前台程序 , 用于提權 , 并綁定 " 遠端前台程序 " ;

" 遠端前台程序 " 與 " 本地前台程序 " 實作了相同的功能 , 代碼基本一緻 , 這兩個程序都是前台程序 , 都進行了提權 , 并且互相綁定 , 當監聽到綁定的另外一個程序突然斷開連接配接 , 則本程序再次開啟前台程序提權 , 并且重新綁定對方程序 , 以達到拉活對方程序的目的 ;

​舉例 :​ " 本地前台程序 " LocalForegroundService , " 遠端前台程序 " RemoteForegroundService ;

這兩個程序之間需要綁定 , 這裡就需要定義 AIDL 接口 IMyAidlInterface , 每個服務中都需要定義繼承 IMyAidlInterface.Stub 的 Binder 類 , 作為程序間通信的橋梁 ; ( 這是個預設的 AIDL 接口 )

/**
     * AIDL 遠端調用接口
     * 其它程序調與該 RemoteForegroundService 服務程序通信時 , 可以通過 onBind 方法擷取該 myBinder 成員
     * 通過調用該成員的 basicTypes 方法 , 可以與該程序進行資料傳遞
     */
    class MyBinder extends IMyAidlInterface.Stub {
        @Override
        public void basicTypes(
                int anInt, long aLong, boolean aBoolean, float aFloat,
                double aDouble, String aString) throws RemoteException {
            // 通信内容
        }
    }      

" 本地前台程序 " LocalForegroundService 在 onCreate 方法中開啟前台服務 , 提權 , 參考 【Android 程序保活】提升程序優先級 ( 使用前台 Service 提高應用程序優先級 | 效果展示 | 源碼資源 ) , 并且建立用于程序間通信的 Binder 對象 ;

/**
     * 遠端調用 Binder 對象
     */
    private MyBinder myBinder;

    @Override
    public void onCreate() {
        super.onCreate();
        // 建立 Binder 對象
        myBinder = new MyBinder();

        // 啟動前台程序
        startService();
    }      

" 本地前台程序 " LocalForegroundService , 在 onBind 方法中傳回 onCreate 方法中建立的 Binder 對象 ;

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

" 本地前台程序 " LocalForegroundService 中 , 綁定遠端程序時 , 需要使用到 ServiceConnection 類 , 在服務綁定成功時回調 onServiceConnected , 服務斷開時回調 onServiceDisconnected 方法 ; 這裡就在 onServiceDisconnected 方法中再次對本服務進行提權 , 并且再次綁定 " 遠端前台程序 " RemoteForegroundService ;

class Connection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 服務綁定成功時回調
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            // 再次啟動前台程序
            startService();
            // 綁定另外一個遠端程序
            bindService();
        }
    }      

另外特别注意權限問題 , 需要在清單檔案中配置 android.permission.FOREGROUND_SERVICE 權限 :

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />      

二、 雙程序守護保活完整源碼

1、AIDL 接口

這裡的 AIDL 不實作任何操作 , 是系統預設生成的 AIDL 接口 , 隻是用于單純的綁定兩個程序 , 監聽程序的連接配接斷開 ;

// IMyAidlInterface.aidl
package kim.hsl.two_process_alive;

// Declare any non-default types here with import statements

interface IMyAidlInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}      

2、本地前台服務 Service

package kim.hsl.two_process_alive;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Color;
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;

import androidx.core.app.NotificationCompat;

import static androidx.core.app.NotificationCompat.PRIORITY_MIN;

/**
 * 前台服務提權
 */
public class LocalForegroundService extends Service {

    /**
     * 遠端調用 Binder 對象
     */
    private MyBinder myBinder;

    /**
     * 連接配接對象
     */
    private Connection connection;

    /**
     * AIDL 遠端調用接口
     * 其它程序調與該 RemoteForegroundService 服務程序通信時 , 可以通過 onBind 方法擷取該 myBinder 成員
     * 通過調用該成員的 basicTypes 方法 , 可以與該程序進行資料傳遞
     */
    class MyBinder extends IMyAidlInterface.Stub {
        @Override
        public void basicTypes(
                int anInt, long aLong, boolean aBoolean, float aFloat,
                double aDouble, String aString) throws RemoteException {
            // 通信内容
        }
    }

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

    @Override
    public void onCreate() {
        super.onCreate();
        // 建立 Binder 對象
        myBinder = new MyBinder();

        // 啟動前台程序
        startService();
    }

    private void startService(){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
            // startForeground();

            // 建立通知通道
            NotificationChannel channel = new NotificationChannel("service",
                    "service", NotificationManager.IMPORTANCE_NONE);
            channel.setLightColor(Color.BLUE);
            channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
            NotificationManager service = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            // 正式建立
            service.createNotificationChannel(channel);

            NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "service");
            Notification notification = builder.setOngoing(true)
                    .setSmallIcon(R.mipmap.ic_launcher)
                    .setPriority(PRIORITY_MIN)
                    .setCategory(Notification.CATEGORY_SERVICE)
                    .build();

            // 開啟前台程序 , API 26 以上無法關閉通知欄
            startForeground(10, notification);

        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2){
            startForeground(10, new Notification());
            // API 18 ~ 25 以上的裝置 , 啟動相同 id 的前台服務 , 并關閉 , 可以關閉通知
            startService(new Intent(this, CancelNotificationService.class));

        } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2){
            // 将該服務轉為前台服務
            // 需要設定 ID 和 通知
            // 設定 ID 為 0 , 就不顯示已通知了 , 但是 oom_adj 值會變成背景程序 11
            // 設定 ID 為 1 , 會在通知欄顯示該前台服務
            // 8.0 以上該用法報錯
            startForeground(10, new Notification());
        }
    }

    /**
     * 綁定 另外一個 服務
     * LocalForegroundService 與 RemoteForegroundService 兩個服務互相綁定
     */
    private void bindService(){
        // 綁定 另外一個 服務
        // LocalForegroundService 與 RemoteForegroundService 兩個服務互相綁定

        // 建立連接配接對象
        connection = new Connection();

        // 建立本地前台程序元件意圖
        Intent bindIntent = new Intent(this, RemoteForegroundService.class);
        // 綁定程序操作
        bindService(bindIntent, connection, BIND_AUTO_CREATE);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 綁定另外一個服務
        bindService();
        return super.onStartCommand(intent, flags, startId);
    }

    class Connection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 服務綁定成功時回調
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            // 再次啟動前台程序
            startService();
            // 綁定另外一個遠端程序
            bindService();
        }
    }

    /**
     * API 18 ~ 25 以上的裝置, 關閉通知到專用服務
     */
    public static class CancelNotificationService extends Service {
        public CancelNotificationService() {
        }

        @Override
        public void onCreate() {
            super.onCreate();
            startForeground(10, new Notification());
            stopSelf();
        }

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

    }

}      

3、遠端前台服務 Service

package kim.hsl.two_process_alive;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Color;
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;

import androidx.core.app.NotificationCompat;

import static androidx.core.app.NotificationCompat.PRIORITY_MIN;

/**
 * 前台服務提權
 */
public class RemoteForegroundService extends Service {

    /**
     * 遠端調用 Binder 對象
     */
    private MyBinder myBinder;

    /**
     * 連接配接對象
     */
    private Connection connection;

    /**
     * AIDL 遠端調用接口
     * 其它程序調與該 RemoteForegroundService 服務程序通信時 , 可以通過 onBind 方法擷取該 myBinder 成員
     * 通過調用該成員的 basicTypes 方法 , 可以與該程序進行資料傳遞
     */
    class MyBinder extends IMyAidlInterface.Stub {
        @Override
        public void basicTypes(
                int anInt, long aLong, boolean aBoolean, float aFloat,
                double aDouble, String aString) throws RemoteException {
            // 通信内容
        }
    }

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

    @Override
    public void onCreate() {
        super.onCreate();
        // 建立 Binder 對象
        myBinder = new MyBinder();

        // 啟動前台程序
        startService();
    }

    private void startService(){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
            // startForeground();

            // 建立通知通道
            NotificationChannel channel = new NotificationChannel("service",
                    "service", NotificationManager.IMPORTANCE_NONE);
            channel.setLightColor(Color.BLUE);
            channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
            NotificationManager service = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            // 正式建立
            service.createNotificationChannel(channel);

            NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "service");
            Notification notification = builder.setOngoing(true)
                    .setSmallIcon(R.mipmap.ic_launcher)
                    .setPriority(PRIORITY_MIN)
                    .setCategory(Notification.CATEGORY_SERVICE)
                    .build();

            // 開啟前台程序 , API 26 以上無法關閉通知欄
            startForeground(10, notification);

        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2){
            startForeground(10, new Notification());
            // API 18 ~ 25 以上的裝置 , 啟動相同 id 的前台服務 , 并關閉 , 可以關閉通知
            startService(new Intent(this, CancelNotificationService.class));

        } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2){
            // 将該服務轉為前台服務
            // 需要設定 ID 和 通知
            // 設定 ID 為 0 , 就不顯示已通知了 , 但是 oom_adj 值會變成背景程序 11
            // 設定 ID 為 1 , 會在通知欄顯示該前台服務
            // 8.0 以上該用法報錯
            startForeground(10, new Notification());
        }
    }

    /**
     * 綁定 另外一個 服務
     * LocalForegroundService 與 RemoteForegroundService 兩個服務互相綁定
     */
    private void bindService(){
        // 綁定 另外一個 服務
        // LocalForegroundService 與 RemoteForegroundService 兩個服務互相綁定

        // 建立連接配接對象
        connection = new Connection();

        // 建立本地前台程序元件意圖
        Intent bindIntent = new Intent(this, LocalForegroundService.class);
        // 綁定程序操作
        bindService(bindIntent, connection, BIND_AUTO_CREATE);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 綁定另外一個服務
        bindService();
        return super.onStartCommand(intent, flags, startId);
    }

    class Connection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 服務綁定成功時回調
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            // 再次啟動前台程序
            startService();
            // 綁定另外一個遠端程序
            bindService();
        }
    }

    /**
     * API 18 ~ 25 以上的裝置, 關閉通知到專用服務
     */
    public static class CancelNotificationService extends Service {
        public CancelNotificationService() {
        }

        @Override
        public void onCreate() {
            super.onCreate();
            startForeground(10, new Notification());
            stopSelf();
        }

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

    }
}      

4、清單配置

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="kim.hsl.two_process_alive">

    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Two_Process_Alive">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!-- 本地提權前台服務 Service -->
        <service
            android:name=".LocalForegroundService"
            android:enabled="true"
            android:exported="true"></service>

        <!-- 本地服務 , API 18 ~ 25 以上的裝置, 關閉通知到專用服務 -->
        <service
            android:name=".LocalForegroundService$CancelNotificationService"
            android:enabled="true"
            android:exported="true"></service>

        <!-- 遠端提權前台服務 Service -->
        <service
            android:name=".RemoteForegroundService"
            android:enabled="true"
            android:exported="true"
            android:process=":remote"></service>

        <!-- 遠端服務 , API 18 ~ 25 以上的裝置, 關閉通知到專用服務 -->
        <service
            android:name=".RemoteForegroundService$CancelNotificationService"
            android:enabled="true"
            android:exported="true"
            android:process=":remote"></service>

    </application>

</manifest>      

5、啟動兩個服務

package kim.hsl.two_process_alive;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 通過前台 Service 提升應用權限
        // 啟動普通 Service , 但是在該 Service 的 onCreate 方法中執行了 startForeground
        // 變成了前台 Service 服務
        startService(new Intent(this, LocalForegroundService.class));
        startService(new Intent(this, RemoteForegroundService.class));
    }
}      

5、執行效果

執行上述應用後 , 可以看到啟動了兩個應用 , 幹掉應用後 , 可以被遠端程序拉起 , 幹掉遠端程序 , 遠端程序可以本主程序服務拉起 ;

【Android 程式保活】應用程式拉活 ( 雙程式守護保活 )

三、 源碼資源

  • ​GitHub 位址 :​ https://github.com/han1202012/Two_Progress_Alive