Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 關於android開發 >> Android動態部署五:如何從插件apk中啟動Service

Android動態部署五:如何從插件apk中啟動Service

編輯:關於android開發

Android動態部署五:如何從插件apk中啟動Service


經過前面幾篇文章的分析,我們了解到了Google原生是如何拆分apk的,並且我們自己可以通過解析manifest文件,通過創建插件ClassLoader,Resources對象來啟動插件APK中的Activity,上一篇文章關於資源的問題,有一點遺漏,在插件中開發者有可能通過如下代碼獲取資源Id

getIdentifier("xxx", "layout", getPackageName());
此時調用getPackageName()方法返回的是宿主apk的包名,所以我們需要在DynamicContextImpl類中重寫getPackageName()方法,返回從插件apk的manifest中解析出來的的包名,接下來我們通過分析Service啟動流程來看看宿主apk如何啟動Android四大組件之Service。

Service啟動流程

startService(new Intent(this, TargetService.class));

在Activity中,很簡單的一行代碼,就可以啟動TargetService了,下圖就是調用這行代碼後的時序圖:
這裡寫圖片描述

帶著成功啟動插件Activity的經驗,我們繼續通過分析Service啟動流程,試圖從中找到hook點從而將我們對插件Service的擴展操作,通過類似的重寫DynamicInstrumentation類,替換進ActivityThread中。
在時序圖中我們發現在調用startService方法後,最終都會調到ContextImpl中的startService。

@Override
public ComponentName startService(Intent service) {
    warnIfCallingFromSystemProcess();
    return startServiceCommon(service, mUser);
}

private ComponentName startServiceCommon(Intent service, UserHandle user) {
    try {
        validateServiceIntent(service);
        service.prepareToLeaveProcess();
        ComponentName cn = ActivityManagerNative.getDefault().startService(
            mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
                        getContentResolver()), getOpPackageName(), user.getIdentifier());
        if (cn != null) {
            if (cn.getPackageName().equals("!")) {
                throw new SecurityException(
                        "Not allowed to start service " + service
                        + " without permission " + cn.getClassName());
            } else if (cn.getPackageName().equals("!!")) {
                throw new SecurityException(
                        "Unable to start service " + service
                        + ": " + cn.getClassName());
            }
        }
        return cn;
    } catch (RemoteException e) {
        throw new RuntimeException("Failure from system", e);
    }
}

在看了源碼之後我們發現,這個方法的功能,其實跟Activity啟動流程中Instrumentation類中的execStartActivity方法類似,看到這就感覺啟動插件Service已經十拿九穩了,我們已經跨出了很大的一步,我們繼續來深入研究Service的啟動流程的源碼:
ActiveServices.java

private final String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
        boolean whileRestarting) throws TransactionTooLargeException {
    if (r.app != null && r.app.thread != null) {
        sendServiceArgsLocked(r, execInFg, false);//如果Service已啟動,在這裡會直接調用Service的onStartCommand方法
        return null;
    }

    if (!whileRestarting && r.restartDelay > 0) {
        // If waiting for a restart, then do nothing.
        return null;
    }

    if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Bringing up " + r + " " + r.intent);

    // We are now bringing the service up, so no longer in the
    // restarting state.
    if (mRestartingServices.remove(r)) {
        r.resetRestartCounter();
        clearRestartingIfNeededLocked(r);
    }

    // Make sure this service is no longer considered delayed, we are starting it now.
    if (r.delayed) {
        if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "REM FR DELAY LIST (bring up): " + r);
        getServiceMap(r.userId).mDelayedStartList.remove(r);
        r.delayed = false;
    }

    // Make sure that the user who owns this service is started.  If not,
    // we don't want to allow it to run.
    if (mAm.mStartedUsers.get(r.userId) == null) {
        String msg = "Unable to launch app "
                + r.appInfo.packageName + "/"
                + r.appInfo.uid + " for service "
                + r.intent.getIntent() + ": user " + r.userId + " is stopped";
        Slog.w(TAG, msg);
        bringDownServiceLocked(r);
        return msg;
    }

    // Service is now being launched, its package can't be stopped.
    try {
        AppGlobals.getPackageManager().setPackageStoppedState(
                r.packageName, false, r.userId);
    } catch (RemoteException e) {
    } catch (IllegalArgumentException e) {
        Slog.w(TAG, "Failed trying to unstop package "
                + r.packageName + ": " + e);
    }

    final boolean isolated = (r.serviceInfo.flags&ServiceInfo.FLAG_ISOLATED_PROCESS) != 0;
    final String procName = r.processName;
    ProcessRecord app;

    if (!isolated) {
        app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false);
        if (DEBUG_MU) Slog.v(TAG_MU, "bringUpServiceLocked: appInfo.uid=" + r.appInfo.uid
                    + " app=" + app);
        if (app != null && app.thread != null) {
            try {
                app.addPackage(r.appInfo.packageName, r.appInfo.versionCode, mAm.mProcessStats);
                realStartServiceLocked(r, app, execInFg); //這裡會真正啟動Service
                return null;
            } catch (TransactionTooLargeException e) {
                throw e;
            } catch (RemoteException e) {
                Slog.w(TAG, "Exception when starting service " + r.shortName, e);
            }

            // If a dead object exception was thrown -- fall through to
            // restart the application.
        }
    } else {
        // If this service runs in an isolated process, then each time
        // we call startProcessLocked() we will get a new isolated
        // process, starting another process if we are currently waiting
        // for a previous process to come up.  To deal with this, we store
        // in the service any current isolated process it is running in or
        // waiting to have come up.
        app = r.isolatedProc;
    }

    // Not running -- get it started, and enqueue this service record
    // to be executed when the app comes up.
    if (app == null) {
        if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
                "service", r.name, false, isolated, false)) == null) {
            String msg = "Unable to launch app "
                    + r.appInfo.packageName + "/"
                    + r.appInfo.uid + " for service "
                    + r.intent.getIntent() + ": process is bad";
            Slog.w(TAG, msg);
            bringDownServiceLocked(r);
            return msg;
        }
        if (isolated) {
            r.isolatedProc = app;
        }
    }

    if (!mPendingServices.contains(r)) {
        mPendingServices.add(r);
    }

    if (r.delayedStop) {
        // Oh and hey we've already been asked to stop!
        r.delayedStop = false;
        if (r.startRequested) {
            if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE,
                    "Applying delayed stop (in bring up): " + r);
            stopServiceLocked(r);
        }
    }

    return null;
}

private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg,
        boolean oomAdjusted) throws TransactionTooLargeException {
    ...

    while (r.pendingStarts.size() > 0) {
        ...
        try {
            ...
            r.app.thread.scheduleServiceArgs(r, si.taskRemoved, si.id, flags, si.intent);
        } catch (Exception e) {
        }
        ...
    }
    ...
}

private final void realStartServiceLocked(ServiceRecord r,
        ProcessRecord app, boolean execInFg) throws RemoteException {
    if (app.thread == null) {
        throw new RemoteException();
    }
    ...
    r.app = app;
    r.restartTime = r.lastActivity = SystemClock.uptimeMillis();

    final boolean newService = app.services.add(r);
    bumpServiceExecutingLocked(r, execInFg, "create");
    mAm.updateLruProcessLocked(app, false, null);
    mAm.updateOomAdjLocked();

    boolean created = false;
    try {
        if (LOG_SERVICE_START_STOP) {
            String nameTerm;
            int lastPeriod = r.shortName.lastIndexOf('.');
            nameTerm = lastPeriod >= 0 ? r.shortName.substring(lastPeriod) : r.shortName;
            EventLogTags.writeAmCreateService(
                    r.userId, System.identityHashCode(r), nameTerm, r.app.uid, r.app.pid);
        }
        synchronized (r.stats.getBatteryStats()) {
            r.stats.startLaunchedLocked();
        }
        mAm.ensurePackageDexOpt(r.serviceInfo.packageName);
        app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
        app.thread.scheduleCreateService(r, r.serviceInfo,
                mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
                app.repProcState);// 類似Activity啟動流程中的app.thread.scheduleLaunchActivity方法,在這裡會new一個Service,並且調用attach以及onCreate方法
        r.postNotification();
        created = true;
    } catch (DeadObjectException e) {
        Slog.w(TAG, "Application dead when creating service " + r);
        mAm.appDiedLocked(app);
        throw e;
    } finally {
        if (!created) {
            // Keep the executeNesting count accurate.
            final boolean inDestroying = mDestroyingServices.contains(r);
            serviceDoneExecutingLocked(r, inDestroying, inDestroying);

            // Cleanup.
            if (newService) {
                app.services.remove(r);
                r.app = null;
            }

            // Retry.
            if (!inDestroying) {
                scheduleServiceRestartLocked(r, false);
            }
        }
    }

    requestServiceBindingsLocked(r, execInFg);

    updateServiceClientActivitiesLocked(app, null, true);

    // If the service is in the started state, and there are no
    // pending arguments, then fake up one so its onStartCommand() will
    // be called.
    if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) {
        r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
                null, null));
    }

    sendServiceArgsLocked(r, execInFg, true);//第一次啟動的Service,會在這裡調用onStartCommand方法

    if (r.delayed) {
        if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "REM FR DELAY LIST (new proc): " + r);
        getServiceMap(r.userId).mDelayedStartList.remove(r);
        r.delayed = false;
    }

    if (r.delayedStop) {
        // Oh and hey we've already been asked to stop!
        r.delayedStop = false;
        if (r.startRequested) {
            if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE,
                    "Applying delayed stop (from start): " + r);
            stopServiceLocked(r);
        }
    }
}

ActivityThread.java

//ActivityThread$ApplicationThread
public final void scheduleServiceArgs(IBinder token, boolean taskRemoved, int startId,
    int flags ,Intent args) {
    ...
    sendMessage(H.SERVICE_ARGS, s);
}

public final void scheduleCreateService(IBinder token,
        ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
    ...
    sendMessage(H.CREATE_SERVICE, s);
}

//ActivityThread$H
public void handleMessage(Message msg) {
    switch (msg.what) {
        case CREATE_SERVICE: {
            handleCreateService((CreateServiceData)msg.obj);
        } break;
        case SERVICE_ARGS: {
            handleServiceArgs((ServiceArgsData)msg.obj);
        } break;
    }
}

//ActivityThread
private void handleCreateService(CreateServiceData data) {
    ...

    LoadedApk packageInfo = getPackageInfoNoCheck(
            data.info.applicationInfo, data.compatInfo);
    Service service = null;
    try {
        java.lang.ClassLoader cl = packageInfo.getClassLoader();
        service = (Service) cl.loadClass(data.info.name).newInstance();//這裡和Activity的啟動流程有些許區別
    } catch (Exception e) {
        if (!mInstrumentation.onException(service, e)) {
            throw new RuntimeException(
                "Unable to instantiate service " + data.info.name
                + ": " + e.toString(), e);
        }
    }

    try {
        if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);

        ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
        context.setOuterContext(service);

        Application app = packageInfo.makeApplication(false, mInstrumentation);
        service.attach(context, this, data.info.name, data.token, app,
                ActivityManagerNative.getDefault());
        service.onCreate();//這裡直接調用了onCreate,而沒有類似的先調用callActivityOnCreate方法
        mServices.put(data.token, service);
        try {
            ActivityManagerNative.getDefault().serviceDoneExecuting(
                    data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
        } catch (RemoteException e) {
            // nothing to do.
        }
    } catch (Exception e) {
        if (!mInstrumentation.onException(service, e)) {
            throw new RuntimeException(
                "Unable to create service " + data.info.name
                + ": " + e.toString(), e);
        }
    }
}

private void handleServiceArgs(ServiceArgsData data) {
    Service s = mServices.get(data.token);
    if (s != null) {
        try {
            if (data.args != null) {
                data.args.setExtrasClassLoader(s.getClassLoader());
                data.args.prepareToEnterProcess();
            }
            int res;
            if (!data.taskRemoved) {
                res = s.onStartCommand(data.args, data.flags, data.startId);
            } else {
                s.onTaskRemoved(data.args);
                res = Service.START_TASK_REMOVED_COMPLETE;
            }

            QueuedWork.waitToFinish();

            try {
                ActivityManagerNative.getDefault().serviceDoneExecuting(
                        data.token, SERVICE_DONE_EXECUTING_START, data.startId, res);
            } catch (RemoteException e) {
                // nothing to do.
            }
            ensureJitEnabled();
        } catch (Exception e) {
            if (!mInstrumentation.onException(s, e)) {
                throw new RuntimeException(
                        "Unable to start service " + s
                        + " with " + data.args + ": " + e.toString(), e);
            }
        }
    }
}

看到這裡,原生Service也啟動起來了,我們發現Service的啟動流程和Activity的類似,但又不完全一樣,正是因為這些許差別,讓我們的開發工作陷入了困境,遇到了下面這些“坑”:
1. Service不像Activity的標准模式,可以一直實例化,當某個Service啟動後,從上面的代碼可以看到,再次調用startService方法,源碼中並不會去重新創建Service,調用onCreate,而是直接調用onStartCommand方法,所以我們不能通過StubService的方式來啟動插件Service。
2. Service的handleCreateService方法不像在Activity啟動流程中的performLaunchActivity方法中獲取類名後,通過Instrumentation類的newActivity方法實例化,通過callActivityOnCreate方法間接調用Activity的onCreate,這樣我們有機會通過重寫DynamicInstrumentation類來擴展插件功能,而Service卻直接在ActivityThread類中實例化,並且在attach方法結束後直接調用onCreate方法。

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ...
    Activity a = performLaunchActivity(r, customIntent);
    ...
}

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ActivityInfo aInfo = r.activityInfo;
    if (r.packageInfo == null) {
        r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                Context.CONTEXT_INCLUDE_CODE);
    }
    ...
    Activity activity = null;
    try {
        java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
        ...
    } catch (Exception e) {
        ...
    }

    try {
        ...
        if (activity != null) {
            ...
            activity.attach(appContext, this, getInstrumentation(), r.token,
                    r.ident, app, r.intent, r.activityInfo, title, r.parent,
                    r.embeddedID, r.lastNonConfigurationInstances, config,
                    r.referrer, r.voiceInteractor);
            ...
            if (r.isPersistable()) {
                mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
            } else {
                mInstrumentation.callActivityOnCreate(activity, r.state);
            }
            ...
        }
    } catch (Exception e) {
        ...
    }
    return activity;
}

上面那兩個坑,恰恰是我們在實現從插件apk中啟動Activity時所關注的點,似乎這些點在這裡一個都用不上,前面跨出的一大步,感覺也是被打了回去,在接下來的幾天裡,我反復閱讀源碼,發現了其中的一個關鍵點,插件中的Service是使用插件的ClassLoader通過類名來加載的,那我們可以在ClassLoader上做一些“手腳”。

private void handleCreateService(CreateServiceData data) {
    ...
    LoadedApk packageInfo = getPackageInfoNoCheck(
            data.info.applicationInfo, data.compatInfo);
    Service service = null;
    try {
        java.lang.ClassLoader cl = packageInfo.getClassLoader();
        service = (Service) cl.loadClass(data.info.name).newInstance();
    } catch (Exception e) {
        ...
    }
    ...
}

public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
        CompatibilityInfo compatInfo) {
    return getPackageInfo(ai, compatInfo, null, false, true, false);
}

private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
        ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
        boolean registerPackage) {
    ...
    synchronized (mResourcesManager) {
        WeakReference ref;
        if (differentUser) {
            // Caching not supported across users
            ref = null;
        } else if (includeCode) {
            ref = mPackages.get(aInfo.packageName);//宿主apk的LoadApk保存在mPackages中
        } else {
            ref = mResourcePackages.get(aInfo.packageName);
        }

        LoadedApk packageInfo = ref != null ? ref.get() : null;
        if (packageInfo == null || (packageInfo.mResources != null
                && !packageInfo.mResources.getAssets().isUpToDate())) {
            if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package "
                    : "Loading resource-only package ") + aInfo.packageName
                    + " (in " + (mBoundApplication != null
                            ? mBoundApplication.processName : null)
                    + ")");
            packageInfo =
                new LoadedApk(this, aInfo, compatInfo, baseLoader,
                        securityViolation, includeCode &&
                        (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);

            if (mSystemThread && "android".equals(aInfo.packageName)) {
                packageInfo.installSystemApplicationInfo(aInfo,
                        getSystemContext().mPackageInfo.getClassLoader());
            }

            if (differentUser) {
                // Caching not supported across users
            } else if (includeCode) {
                mPackages.put(aInfo.packageName,
                        new WeakReference(packageInfo));
            } else {
                mResourcePackages.put(aInfo.packageName,
                        new WeakReference(packageInfo));
            }
        }
        return packageInfo;
    }
}

看到這個點之後,感覺像是抓住一個救命稻草那樣興奮,我們可以在每次安裝一個插件apk的同時將插件apk的ClassLoader安裝到mPackages.get(“host package name”)中,這樣就可以不做其他的修改,根據插件apk中的Service類名就可以加載TargetService了,同時細心的讀者也會發現,其實Activity,BroadcastReceiver,ContentProvider也是通過這樣的方式加載的,真是一勞永逸呢。
DynamicClassLoaderWrapper.java

package com.ximsfei.dynamic.app;

import java.util.ArrayList;

/**
 * Created by pengfenx on 3/15/2016.
 */
public class DynamicClassLoaderWrapper extends ClassLoader {
    private final ClassLoader mBase;
    private final ArrayList mDynamicLoaders = new ArrayList<>();

    protected DynamicClassLoaderWrapper(ClassLoader base) {
        super();
        mBase = base;
    }

    public void addClassLoader(ClassLoader cl) {
        if (!mDynamicLoaders.contains(cl)) {
            mDynamicLoaders.add(cl);
        }
    }

    @Override
    protected Class findClass(String className) throws ClassNotFoundException {
        try {
            return mBase.loadClass(className);
        } catch (ClassNotFoundException e) {
        }

        int N = mDynamicLoaders.size();
        for (int i=0; i

安裝ClassLoader:

public synchronized void installClassLoader(ClassLoader classLoader) {
    Object loadedApk = ((WeakReference) getPackages().get(getHostPackageName())).get();
    try {
        ClassLoader cl = Reflect.create(loadedApk.getClass())
                .setMethod("getClassLoader").invoke(loadedApk);
        if (!(cl instanceof DynamicClassLoaderWrapper)) {
            DynamicClassLoaderWrapper dclw = new DynamicClassLoaderWrapper(cl);
            dclw.addClassLoader(classLoader);
            Reflect.create(loadedApk.getClass()).setField("mClassLoader")
                    .set(loadedApk, dclw);
        } else {
            ((DynamicClassLoaderWrapper) cl).addClassLoader(classLoader);
        }
    } catch (Exception e) {
    }
}

private synchronized Map getPackages() {
    if (mPackages == null) {
        try {
            mPackages = mActivityThreadReflect.setField("mPackages").get(currentActivityThread());
        } catch (Exception e) {
        }
    }
    return mPackages;
}

stopService, bindService, unbindService的流程與startService流程類似,並且不需要做過多的修改,在這裡就不再分析了,有興趣的讀者可以自己去看一下源碼,分析一下。

遺留問題

第一個坑中,Service只能啟動一次,所以我們不能通過偽裝成StubService的方式,來“騙過”AndroidManifest的檢測,我也沒有想到更好的方法來實現,暫時只能將要使用的Service類名注冊到宿主apk的AndroidManifest中來實現,如果讀者有什麼好的方法,可以分享出來一起學習一下。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved