保活分析

MAX ?;罘治?br> 可以引入module,和使用打包好的AAR形式。使用方法:


企業(yè)微信截圖_16444873994239.png

注意:如果targetSdk為31 需要將module AndroidManifest.xml中AutoBootReceiver 增加android:exported="true"

首先講一下使用文件鎖來保活進程的思路,首先windows,linux是有一套文件的進程同步機制,一個進程可以給一個文件上鎖,在操作完成之后再進行解鎖,其他進程如果訪問這個文件,會先檢查一下這個文件是否有鎖,如果有鎖的話,代表別人正在操作,就會對文件延遲處理,那么當一個進程掛掉,他給文件加的鎖也自然會消失。


企業(yè)微信截圖_20220224151152.png

按照這個機制,可以想到進程A給文件A1加鎖,進程B給進程B1加鎖,然后讓進程A去阻塞讀取進程B1文件,進程B去阻塞讀取A1文件,那么如果有進程掛掉那么另一個進程就會讀取到被釋放的文件,通過這個方式監(jiān)聽到對方掛掉。

這個方法JAVA層是無法做到的,所以需要使用C,在native層來實現(xiàn)。
思路是這樣的,但是系統(tǒng)在殺死應用對應進程的時候時間非???,只有幾十毫秒時間,而發(fā)送一個Intent時間較長,它使用的是ActivityManagerService的一個代理類,通過Binder將Intent傳給系統(tǒng),執(zhí)行完時間在百毫秒,很難發(fā)出去,所以要做到用最短的時間來啟動。
我們發(fā)送intent的時候會初始化一個Parcel,通過binder transcate過去。
時間消耗在了創(chuàng)建Parcel上面,這里需要我們在進程開始的時候就要創(chuàng)建好Parcel,再拿到Binder直接發(fā)送出去,看看競品是怎么做的。

p = Parcel.obtain();
        p.writeInterfaceToken("android.app.IActivityManager");
        p.writeStrongBinder(null);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            p.writeInt(1);
        }
        entity.intent.writeToParcel(p, 0);
        p.writeString(null);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            p.writeInt(0);
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            p.writeString(entity.intent.getComponent().getPackageName());
        }
        p.writeInt(0);



try {
            Class<?> cls = Class.forName("android.app.ActivityManagerNative");
            Object invoke = cls.getMethod("getDefault", new Class[0]).invoke(cls, new Object[0]);
            Field field = invoke.getClass().getDeclaredField("mRemote");
            field.setAccessible(true);
            binder = (IBinder) field.get(invoke);
            field.setAccessible(false);
            Logger.v(Logger.TAG, "initAmsBinder: mRemote == iBinder " + binder);
        } catch (Throwable th) {
            binderManager.thrown(th);
        }

        if (binder == null) {
            try {
                binder = (IBinder) Class.forName("android.os.ServiceManager").getMethod(
                        "getService", new Class[]{String.class}).invoke(null,
                        new Object[]{"activity"});
            } catch (Throwable th) {
                binderManager.thrown(th);
            }
        }

Pacel是在進程初始化的時候拿到的,而Binder是通過反射拿到的。

大概思路是這樣的,當然在實現(xiàn)中還會有很多問題,比如:
1.binder transcate 無法啟動Service,需要在廣播中去啟動,還需要創(chuàng)建一個parcel來啟動廣播,使用廣播來拉起進程
2.在Native層中直接傳遞parcel,會導致監(jiān)聽不到進程被殺;改成傳輸u8數(shù)據(jù)解決了

現(xiàn)在我們來從頭梳理一下,這個項目到底在Application中都做了什么?
在DaemonHolder中attach方法做了哪些事?

public void attach(Context base, Application app) {
        app.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(final Activity activity, Bundle savedInstanceState) {
                Logger.v(Logger.TAG, String.format("====> [%s] created", activity.getLocalClassName()));
                ServiceHolder.getInstance().bindService(activity, DaemonService.class,
                        new ServiceHolder.OnServiceConnectionListener() {
                            @Override
                            public void onServiceConnection(ServiceConnection connection, boolean isConnected) {
                                if (isConnected) {
                                    connCache.put(activity, connection);
                                }
                            }
                        });
            }
..........
}

可以看到監(jiān)聽了ActivityLifecycleCallbacks,在onActivityCreated通過BindService方式去啟動了DaemonService。

public class DaemonService extends DaemonBaseService {

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

    @Override
    public void onCreate() {
        try {
            ContextCompat.startForegroundService(this,
                    new Intent().setClassName(getPackageName(), NotifyResidentService.class.getName()));
        } catch (Throwable th) {
            Logger.e(Logger.TAG, "failed to start foreground service: " + th.getMessage());
        }

        Intent intent2 = new Intent();
        intent2.setClassName(getPackageName(), AssistService1.class.getName());
        startService(intent2);

        Intent intent3 = new Intent();
        intent3.setClassName(getPackageName(), AssistService2.class.getName());
        startService(intent3);
        super.onCreate();
    }
}

DaemonService 中啟動了前臺通知NotifyResidentService,和AssistService1,AssistService2。
NotifyResidentService是一個前臺通知Service,而AssistService1,AssistService2就是在不同進程上的常規(guī)Service,這里不過多介紹。

再接著attach方法中往下看

JavaDaemon.getInstance().fire(
                base,
                new Intent(base, DaemonService.class),
                new Intent(base, DaemonReceiver.class),
                new Intent(base, DaemonInstrumentation.class)
        );

接著往下分析JavaDaemon中都干了什么,捋下來發(fā)現(xiàn)創(chuàng)建了三空文件,
image.png
private void fire(Context context, DaemonEnv env, String[] strArr) {
        Logger.i(Logger.TAG, "############################################## !!! fire(): " +
                "env=" + env + ", strArr=" + Arrays.toString(strArr));
        boolean z;
        String processName = Utils.getProcessName();
        Logger.e(Logger.TAG, "processName: " + processName);
        if (processName.startsWith(context.getPackageName()) && processName.contains(COLON_SEPARATOR)) {
            String substring = processName.substring(processName.lastIndexOf(COLON_SEPARATOR) + 1);
            List<String> list = new ArrayList();
            if (strArr != null) {
                z = false;
                for (String str : strArr) {
                    if (str.equals(substring)) {
                        z = true;
                    } else {
                        list.add(str);
                    }
                }
            } else {
                z = false;
            }
            if (z) {
                Logger.v(Logger.TAG, "app lock file start: " + substring);
                NativeKeepAlive.lockFile(context.getFilesDir() + "/" + substring + "_d");
                Logger.v(Logger.TAG, "app lock file finish");
                String[] strArr2 = new String[list.size()];
                for (int i = 0; i < strArr2.length; i++) {
                    strArr2[i] = context.getFilesDir() + "/" + list.get(i) + "_d";
                    Logger.e(Logger.TAG, "processName strarr2: " + strArr2[i]);
                }
                futureScheduler.scheduleFuture(new AppProcessRunnable(env, strArr2, "daemon"), 0);
            }
        } else if (processName.equals(context.getPackageName())) {
            ContextCompat.startForegroundService(context, new Intent(context, DaemonService.class));
        }
    }

除此之外還有一段核心代碼:

@Override
    public void run() {
        DaemonEntity entity = new DaemonEntity();
        entity.str = str;
        entity.strArr = strArr;
        entity.intent = env.intent;
        entity.intent2 = env.intent2;
        entity.intent3 = env.intent3;

        List<String> list = new ArrayList();
        list.add("export CLASSPATH=$CLASSPATH:" + env.publicSourceDir);
        if (env.nativeLibraryDir.contains("arm64")) {
            list.add("export _LD_LIBRARY_PATH=/system/lib64/:/vendor/lib64/:" + env.nativeLibraryDir);
            list.add("export LD_LIBRARY_PATH=/system/lib64/:/vendor/lib64/:" + env.nativeLibraryDir);
            list.add(String.format("%s / %s %s --application --nice-name=%s &",
                    new Object[]{new File("/system/bin/app_process").exists() ?
                            "app_process" : "app_process", DaemonMain.class.getName(),
                            entity.toString(), str}));
        } else {
            list.add("export _LD_LIBRARY_PATH=/system/lib/:/vendor/lib/:" + env.nativeLibraryDir);
            list.add("export LD_LIBRARY_PATH=/system/lib/:/vendor/lib/:" + env.nativeLibraryDir);
            list.add(String.format("%s / %s %s --application --nice-name=%s &",
                    new Object[]{new File("/system/bin/app_process32").exists() ?
                            "app_process32" : "app_process", DaemonMain.class.getName(),
                            entity.toString(), str}));
        }
        Logger.i(Logger.TAG, "cmds: " + list);
        File file = new File("/");
        String[] strArr = new String[list.size()];
        for (int i = 0; i < strArr.length; i++) {
            strArr[i] = list.get(i);
        }
        ShellExecutor.execute(file, null, strArr);
    }

DaemonEntity 儲存了context.getApplicationInfo()中的信息,publicSourceDir,nativeLibraryDir,都是由ApplicationInfo對象獲取的。
然后通過ProcessBuilder對象以命令行的形式,來執(zhí)行l(wèi)ist中的內(nèi)容。android 底層是linux,也就是在Linux中去執(zhí)行命令。

我們查看一下list中的數(shù)據(jù):

export CLASSPATH=$CLASSPATH:/data/app/com.daemon.keepalive2-yTOyxGcJe4jXr41qdEREFg==/base.apk, 

export _LD_LIBRARY_PATH=/system/lib64/:/vendor/lib64/:/data/app/com.daemon.keepalive2-yTOyxGcJe4jXr41qdEREFg==/lib/arm64,

export LD_LIBRARY_PATH=/system/lib64/:/vendor/lib64/:/data/app/com.daemon.keepalive2-yTOyxGcJe4jXr41qdEREFg==/lib/arm64, 

app_process / com.keepalive.daemon.core.DaemonMain AgAAADIAAAAvAGQAYQB0AGEALwB1AHMAZQByAC8AMAAvAGMAbwBtAC4AZABhAGUAbQBvAG4ALgBrAGUAZQBwAGEAbABpAHYAZQAyAC8AZgBpAGwAZQBzAC8AYQBzAHMAaQBzAHQAMQBfAGQAAAAAADIAAAAvAGQAYQB0AGEALwB1AHMAZQByAC8AMAAvAGMAbwBtAC4AZABhAGUAbQBvAG4ALgBrAGUAZQBwAGEAbABpAHYAZQAyAC8AZgBpAGwAZQBzAC8AYQBzAHMAaQBzAHQAMgBfAGQAAAAAAAYAAABkAGEAZQBtAG8AbgAAAAAAAQAAAP////8AAAAA//////////8AAAAA/////xUAAABjAG8AbQAuAGQAYQBlAG0AbwBuAC4AawBlAGUAcABhAGwAaQB2AGUAMgAAADEAAABjAG8AbQAuAGsAZQBlAHAAYQBsAGkAdgBlAC4AZABhAGUAbQBvAG4ALgBjAG8AcgBlAC4AYwBvAG0AcABvAG4AZQBuAHQALgBEAGEAZQBtAG8AbgBTAGUAcgB2AGkAYwBlAAAAAAAAAAAAAAAAAAAAAAAAAP7/////////AAAAAAAAAAAAAAAAAAAAAAEAAAD/////AAAAAP//////////AAAAAP////8VAAAAYwBvAG0ALgBkAGEAZQBtAG8AbgAuAGsAZQBlAHAAYQBsAGkAdgBlADIAAAAyAAAAYwBvAG0ALgBrAGUAZQBwAGEAbABpAHYAZQAuAGQAYQBlAG0AbwBuAC4AYwBvAHIAZQAuAGMAbwBtAHAAbwBuAGUAbgB0AC4ARABhAGUAbQBvAG4AUgBlAGMAZQBpAHYAZQByAAAAAAAAAAAAAAAAAAAAAAAAAAAA/v////////8AAAAAAAAAAAAAAAAAAAAAAQAAAP////8AAAAA//////////8AAAAA/////xUAAABjAG8AbQAuAGQAYQBlAG0AbwBuAC4AawBlAGUAcABhAGwAaQB2AGUAMgAAADkAAABjAG8AbQAuAGsAZQBlAHAAYQBsAGkAdgBlAC4AZABhAGUAbQBvAG4ALgBjAG8AcgBlAC4AYwBvAG0AcABvAG4AZQBuAHQALgBEAGEAZQBtAG8AbgBJAG4AcwB0AHIAdQBtAGUAbgB0AGEAdABpAG8AbgAAAAAAAAAAAAAAAAAAAAAAAAD+/////////wAAAAAAAAAAAAAAAAAAAAA= --application --nice-name=daemon &]

那這些數(shù)據(jù)到底是什么意思?前三條好理解,就是配置了linux 環(huán)境變量,對應的數(shù)據(jù)分別是publicSourceDir,nativeLibraryDir,nativeLibraryDir,那第四條實在是看不懂,讓我大膽猜測一下,通過下文代碼得知,會不會是將DaemonMain類在底層去運行呢,而DaemonMain是和進程每個進程單獨持有的,也就是說每個進程都聲明到了底層,然后具體類通過這個類去實現(xiàn)。

有了猜測去實驗,發(fā)現(xiàn)果然,通過debug方式發(fā)現(xiàn)代碼無法走到DaemonMain方法中,而查看log發(fā)現(xiàn)DaemonMain中是運行的?。?/p>

那DaemonMain就是關鍵了啊,那這個保活方案和上文中我們提到的文件鎖?;钤硎遣皇且恢履?,繼續(xù)往下看:

public class DaemonMain {
    private IBinderManager binderManager = new IBinderManager();
    public DaemonEntity entity;

    private Parcel p;
    private Parcel p2;
    private Parcel p3;
    private IBinder binder;

    private static volatile FutureScheduler futureScheduler;

    private DaemonMain(DaemonEntity entity) {
        this.entity = entity;
    }

    public static void main(String[] strArr) {
        if (futureScheduler == null) {
            synchronized (DaemonMain.class) {
                if (futureScheduler == null) {
                    futureScheduler = new SingleThreadFutureScheduler(
                            "daemonmain-holder",
                            true
                    );
                }
            }
        }

        DaemonEntity entity = DaemonEntity.create(strArr[0]);
        if (entity != null) {
            new DaemonMain(entity).execute();
        }
        Logger.e(Logger.TAG, "DaemonMain.main Process.killProcess" + Process.myPid());
        Process.killProcess(Process.myPid());
    }

    private void execute() {
        try {
            initAmsBinder();
            assembleParcel();
            NativeKeepAlive.nativeSetSid();
            try {
                Logger.v(Logger.TAG, "setArgV0: " + entity.str);
                Process.class.getMethod("setArgV0", new Class[]{String.class}).invoke(null,
                        new Object[]{entity.str});
            } catch (Exception e) {
                e.printStackTrace();
            }
            for (int i = 1; i < entity.strArr.length; i++) {
                futureScheduler.scheduleFuture(new DaemonRunnable(this, i), 0);
            }
            Logger.v(Logger.TAG, "[" + entity.str + "] wait file lock start: " + entity.strArr[0]);
            NativeKeepAlive.waitFileLock(entity.strArr[0]);
            Logger.v(Logger.TAG, "[" + entity.str + "] wait file lock finish");
            startService();
            broadcastIntent();
            startInstrumentation();
            Logger.v(Logger.TAG, "[" + entity.str + "] start android finish");
        } catch (Throwable th) {
            binderManager.thrown(th);
        }
    }

    public void startInstrumentation() {
        Logger.i(Logger.TAG, "call startInstrumentation(): " + p3);
        if (p3 != null) {
            try {
                binder.transact(binderManager.startInstrumentation(), p3, null, 1);
            } catch (Throwable th) {
                binderManager.thrown(th);
            }
        }
    }

    public void broadcastIntent() {
        Logger.i(Logger.TAG, "call broadcastIntent(): " + p2);
        if (p2 != null) {
            try {
                binder.transact(binderManager.broadcastIntent(), p2, null, 1);
            } catch (Throwable th) {
                binderManager.thrown(th);
            }
        }
    }

    public void startService() {
        Logger.i(Logger.TAG, "call startService(): " + p);
        if (p != null) {
            try {
                binder.transact(binderManager.startService(), p, null, 1);
            } catch (Throwable th) {
                binderManager.thrown(th);
            }
        }
    }

    private void assembleParcel() {
        assembleServiceParcel();
        assembleBroadcastParcel();
        assembleInstrumentationParcel();
    }

    private void assembleServiceParcel() {
        p = Parcel.obtain();
        p.writeInterfaceToken("android.app.IActivityManager");
        p.writeStrongBinder(null);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            p.writeInt(1);
        }
        entity.intent.writeToParcel(p, 0);
        p.writeString(null);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            p.writeInt(0);
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            p.writeString(entity.intent.getComponent().getPackageName());
        }
        p.writeInt(0);
    }

    @SuppressLint("WrongConstant")
    private void assembleBroadcastParcel() {
        p2 = Parcel.obtain();
        p2.writeInterfaceToken("android.app.IActivityManager");
        p2.writeStrongBinder(null);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            p2.writeInt(1);
        }
        entity.intent2.setFlags(32);
        entity.intent2.writeToParcel(p2, 0);
        p2.writeString(null);
        p2.writeStrongBinder(null);
        p2.writeInt(-1);
        p2.writeString(null);
        p2.writeInt(0);
        p2.writeStringArray(null);
        p2.writeInt(-1);
        p2.writeInt(0);
        p2.writeInt(0);
        p2.writeInt(0);
        p2.writeInt(0);
    }

    private void assembleInstrumentationParcel() {
        p3 = Parcel.obtain();
        p3.writeInterfaceToken("android.app.IActivityManager");
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            p3.writeInt(1);
        }
        entity.intent3.getComponent().writeToParcel(p3, 0);
        p3.writeString(null);
        p3.writeInt(0);
        p3.writeInt(0);
        p3.writeStrongBinder(null);
        p3.writeStrongBinder(null);
        p3.writeInt(0);
        p3.writeString(null);
    }

    private void initAmsBinder() {
        try {
            Class<?> cls = Class.forName("android.app.ActivityManagerNative");
            Object invoke = cls.getMethod("getDefault", new Class[0]).invoke(cls, new Object[0]);
            Field field = invoke.getClass().getDeclaredField("mRemote");
            field.setAccessible(true);
            binder = (IBinder) field.get(invoke);
            field.setAccessible(false);
            Logger.v(Logger.TAG, "initAmsBinder: mRemote == iBinder " + binder);
        } catch (Throwable th) {
            binderManager.thrown(th);
        }

        if (binder == null) {
            try {
                binder = (IBinder) Class.forName("android.os.ServiceManager").getMethod(
                        "getService", new Class[]{String.class}).invoke(null,
                        new Object[]{"activity"});
            } catch (Throwable th) {
                binderManager.thrown(th);
            }
        }
    }

    class DaemonRunnable implements Runnable {
        private WeakReference<DaemonMain> thiz;
        private int index;

        private DaemonRunnable(DaemonMain thiz, int index) {
            this.thiz = new WeakReference<>(thiz);
            this.index = index;
        }

        @Override
        public void run() {
            Logger.v(Logger.TAG, "[Thread] wait file lock start: " + index);
            NativeKeepAlive.waitFileLock(thiz.get().entity.strArr[index]);
            Logger.v(Logger.TAG, "[Thread] wait file lock finished");
            thiz.get().startService();
            thiz.get().broadcastIntent();
            thiz.get().startInstrumentation();
            Logger.v(Logger.TAG, "[Thread] start android finish");
        }
    }
}

大致一看發(fā)現(xiàn),果然通過反射的方式拿到了Binder,也初始化了三個Parcel,分別是服務,廣播,還有Instrumentation,乍一看果然和文件鎖的原理差不多啊,但是別輕易下結論,它是對文件進行鎖定操作了,但是目前局勢并不明朗。

這里Instrumentation 不做詳細詳解,可以去搜索相關源碼進行了解
http://developer.android.com/intl/zh-cn/reference/android/app/Instrumentation.html

image.png

大概意思是強大的跟蹤application及activity生命周期的功能,用于android 應用測試框架中,被做為基類使用。

可以看到在DaemonMain中聲明了Main方法,其中最顯眼的就是這個Process.killProcess(Process.myPid());這就令人奇怪了,為什么會手動結束進程呢,在結束之前又做了什么操作?我們一步一步分析。

execute方法中首先初始化Binder,再初始化了Parcel,緊接著調(diào)用Native方法中setId,調(diào)用setsid函數(shù)的進程成為新的會話的領頭進程,并與其父進程的會話組和進程組脫離

void keep_alive_set_sid(JNIEnv *env, jclass jclazz) {
    setsid();
}

可以看見有一個waitFileLock 方法,這是個阻塞方法,也就是讀取文件狀態(tài)是否被鎖定,那么為什么在進程被殺死后還要
Process.killProcess(Process.myPid())呢,
原來Process.killProcess 最終是調(diào)用 linuxAPI kill() 發(fā)送 SIGKILL 信號,進行收到這個信息都會立即結束進程。

然而Android 下不同的是 ActivityManager 一直監(jiān)聽者進程狀態(tài)。如果發(fā)現(xiàn)進程被kill,會立即重啟進行,并重啟之前狀態(tài)對應的Activity、Service、ContentProvider等。

這就是為什么我們調(diào)用Process.killProcess后,發(fā)現(xiàn)程序是重啟了,而不是被kill了

也在最大程度保證了進程被重新啟動。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容