×

Android app安装过程分析(基于Nougat)

96
super_shanks
2017.02.14 17:06* 字数 2118

首先我们要清楚总体可以分成如下几种安装的情况

  • 系统开机的应用安装,安装的是系统级别的应用,用户在没有获取到root权限的情况下无法卸载的应用
  • adb安装的应用,没有安装界面
  • 第三方市场下载的应用,此处要分情况,部分是通过电脑的客户端安装的没有安装的界面,部分是手机上的市场安装的,会有安装的界面

那么我们就对几种情况一一分析它的安装流程

开机安装

  1. 首先在开机的时候systemServer会启动PackageMangerService来

    具体是通过systemServer的main()->init1()->init2()->new ServerThread()->构建PMS

    以上都是SystemServer.cpp中的代码,了解即可。我们前往PMS查看接下去的逻辑:在PMS的构造方法中我们看到了一大坨的逻辑
    ,源码过于长,我们直接在构造方法里面搜索如下这两个方法:

scanDirTracedLI   //最后还是会调用下面的方法,只是对下面的方法做一下跟踪
scanDirLI

就是通过这两个方法进行应用的安装,从scanDirLI开始追踪:

private void scanDirLI(File dir, final int parseFlags, int scanFlags, long currentTime) {
        final File[] files = dir.listFiles();
        ...
        for (File file : files) {
            final boolean isPackage = (isApkFile(file) || file.isDirectory())
                    && !PackageInstallerService.isStageName(file.getName());
            if (!isPackage) {
                // Ignore entries which are not packages
                continue;
            }
            try {
                scanPackageTracedLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,
                        scanFlags, currentTime, null);
            } catch (PackageManagerException e) {
                Slog.w(TAG, "Failed to parse " + file + ": " + e.getMessage());

                // Delete invalid userdata apps
                if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
                        e.error == PackageManager.INSTALL_FAILED_INVALID_APK) {
                    logCriticalInfo(Log.WARN, "Deleting invalid package at " + file);
                    removeCodePathLI(file);
                }
            }
        }
    }

继续看scanPackageTracedLI方法:

private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,
            long currentTime, UserHandle user) throws PackageManagerException {
        if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile);
        PackageParser pp = new PackageParser();
        pp.setSeparateProcesses(mSeparateProcesses);
        pp.setOnlyCoreApps(mOnlyCore);
        pp.setDisplayMetrics(mMetrics);

        if ((scanFlags & SCAN_TRUSTED_OVERLAY) != 0) {
            parseFlags |= PackageParser.PARSE_TRUSTED_OVERLAY;
        }

        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
        final PackageParser.Package pkg;
        try {
            pkg = pp.parsePackage(scanFile, parseFlags);
        } catch (PackageParserException e) {
            throw PackageManagerException.from(e);
        } finally {
            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
        }

        return scanPackageLI(pkg, scanFile, parseFlags, scanFlags, currentTime, user);
    }

我们看到从这个方法开始parsePackage, 通过PackageParser解析apk文件,我们可以去到PackageParser的文件中看到这个parsePackage方法就是把安装包中的配置文件解析出来,并且保存到Package中返回出来,中间辗转反侧对于配置文件各种分情况进行parse,最最主要的是parseBaseApkCommon方法,这个方法对manifast文件的标签进行解析,而其中还调用了parseBaseApplication方法对application标签进行解析。

然后再scanPackageLI->scanPackageDirtyLI,在后者的方法中,我们会把之前解析出来的

PackageParser.Provider p = pkg.providers.get(i);
                p.info.processName = fixProcessName(pkg.applicationInfo.processName,
                        p.info.processName, pkg.applicationInfo.uid);
                mProviders.addProvider(p);
PackageParser.Service s = pkg.services.get(i);
                s.info.processName = fixProcessName(pkg.applicationInfo.processName,
                        s.info.processName, pkg.applicationInfo.uid);
                mServices.addService(s);

等等,我们会把相应的provider,service,receiver,activity全都保存到PMS的成员集合类中去.

至此系统应用的安装算是全部完成了,也许你会说我们并有看到什么install的什么方法,其实最最精髓的就是上面最后那一坨1000+行的方法,这里面把所有parser出来的信息全部保存到PMS中去了,而这就是所谓的安装,安装就是一个吧apk中的信息解析出来保存给PMS的过程,然后在launcher上生成一个图标,以供用户打开,仅此而已。


从网络上下载应用安装

最终都是通过如下的方式去进行安装的

String fileName = "/mnt/usb/sda4/test.apk";
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(new File(fileName)), "application/vnd.android.package-archive");
startActivity(intent);

启动的就是PackageInstallerActivity,而这个activity是Android的系统应用之一,源码在package/app/packageinstaller中,具体源码可以前往下面链接查看:
https://github.com/android/platform_packages_apps_packageinstaller/blob/master/src/com/android/packageinstaller/PackageInstallerActivity.java
我们在其onCreate方法中看到之后会调用checkIfAllowedAndInitiateInstall->initiateInstall-> startInstallConfirm,会处理有关权限的一些问题

之后如果我们点击下方的ok按钮,会进入到安装流程:

 public void onClick(View v) {
        if (v == mOk) {
            if (mOkCanInstall || mScrollView == null) {
                if (mSessionId != -1) {
                    mInstaller.setPermissionsResult(mSessionId, true);
                    clearCachedApkIfNeededAndFinish();
                } else {
                    startInstall();
                }
            } else {
                mScrollView.pageScroll(View.FOCUS_DOWN);
            }
        } else if (v == mCancel) {
           ...
    }

我们接着去startInstall方法看:

private void startInstall() {
        // Start subactivity to actually install the application
        Intent newIntent = new Intent();
        ...
        newIntent.setClass(this, InstallAppProgress.class);
        ...
        startActivity(newIntent);
        finish();
    }

跳到InstallAppProgress这个activity,很明显就是我们点击安装之后进入的安装界面,有个进度条一直在闪的那个正在安装的界面。
onCreate()->initView():

void initView() {
        setContentView(R.layout.op_progress);

        ...

        if ("package".equals(mPackageURI.getScheme())) {
            try {
                pm.installExistingPackage(mAppInfo.packageName);
                onPackageInstalled(PackageInstaller.STATUS_SUCCESS);
            } catch (PackageManager.NameNotFoundException e) {
                onPackageInstalled(PackageInstaller.STATUS_FAILURE_INVALID);
            }
        } else {
           ...

            mInstallHandler.post(new Runnable() {
                @Override
                public void run() {
                    doPackageStage(pm, params);
                }
            });
        }
    }

精简了一下,主要是这两个方法。

installExistingPackage

一个是pm.installExistingPackage,我们知道实际上调用的就是ApplicationPackageManager的installExistingPackage


    @Override
    public int installExistingPackage(String packageName) throws NameNotFoundException {
        return installExistingPackageAsUser(packageName, mContext.getUserId());
    }

    @Override
    public int installExistingPackageAsUser(String packageName, int userId)
            throws NameNotFoundException {
        try {
            int res = mPM.installExistingPackageAsUser(packageName, userId);
            if (res == INSTALL_FAILED_INVALID_URI) {
                throw new NameNotFoundException("Package " + packageName + " doesn't exist");
            }
            return res;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

继续,我们前往PMS的installExistingPackageAsUser方法,具体方法不贴出,实际的意思就是这个安装包已经装过了,只是当前用户下还没有安装,就会对后台的设置做一下处理,实际并不涉及到安装的东西的。我们来看另外一个方法

doPackageStage

private void doPackageStage(PackageManager pm, PackageInstaller.SessionParams params) {
        final PackageInstaller packageInstaller = pm.getPackageInstaller();
        PackageInstaller.Session session = null;
        try {
            session = packageInstaller.openSession(sessionId);
            ...
            session.commit(pendingIntent.getIntentSender());
        } catch (IOException e) {
            onPackageInstalled(PackageInstaller.STATUS_FAILURE);
        } finally {
            IoUtils.closeQuietly(session);
        }
    }

当中对session做了很多操作,我们只看最主要的最后一个session.commit,

  • PackageInstaller.Session
    这个session是在5.0之后新加进来的一种安装处理的方式,我们可以把它理解为一种记录,就和浏览器中的session的作用是一样的,它用来保存应用安装的所有信息,当我们的手机如果因为异常只安装了一半退出了之后,当我们下回再次打开手机的时候,可以继续上次的安装。

ok,我们接着进到PackageInstaller.java看session的commit方法

public void commit(@NonNull IntentSender statusReceiver) {
            try {
                mSession.commit(statusReceiver);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }

这里的mSession我们看到是

        private IPackageInstallerSession mSession;

 /** {@hide} */
        public Session(IPackageInstallerSession session) {
            mSession = session;
        }

万恶的binder,回到上面看session的定义:

session = packageInstaller.openSession(sessionId);

找openSession方法

public @NonNull Session openSession(int sessionId) throws IOException {
        try {
            return new Session(mInstaller.openSession(sessionId));
        } catch (RuntimeException e) {
            ExceptionUtils.maybeUnwrapIOException(e);
            throw e;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

mInstaller是什么,

    private final IPackageInstaller mInstaller;

又来一个binder,我们就不继续一步一步贴了,相信读者也可以自己找到最终的实现是在PackageInstallerSession

@Override
    public void commit(IntentSender statusReceiver) {
        ...
        final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(mContext,
                statusReceiver, sessionId, mIsInstallerDeviceOwner, userId);
        mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget();
    }

省去部分直接看到最后给handler发了条消息

        mHandler = new Handler(looper, mHandlerCallback);

private final Handler.Callback mHandlerCallback = new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            // Cache package manager data without the lock held
            final PackageInfo pkgInfo = mPm.getPackageInfo(
                    params.appPackageName, PackageManager.GET_SIGNATURES /*flags*/, userId);
            final ApplicationInfo appInfo = mPm.getApplicationInfo(
                    params.appPackageName, 0, userId);

            synchronized (mLock) {
                if (msg.obj != null) {
                    mRemoteObserver = (IPackageInstallObserver2) msg.obj;
                }

                try {
                    commitLocked(pkgInfo, appInfo);
                } catch (PackageManagerException e) {
                    final String completeMsg = ExceptionUtils.getCompleteMessage(e);
                    Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg);
                    destroyInternal();
                    dispatchSessionFinished(e.error, completeMsg, null);
                }

                return true;
            }
        }
    };

重要的就是那句commitLocked,这个方法比较长,依然是做一些权限和配置方面的事情,能在方法的最后看到

mPm.installStage(mPackageName, stageDir, stageCid, localObserver, params,
                installerPackageName, installerUid, user, mCertificates);

我们去到PMS的installStage方法:

void installStage(String packageName, File stagedDir, String stagedCid,
            IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams,
            String installerPackageName, int installerUid, UserHandle user,
            Certificate[][] certificates) {
        ...
        final Message msg = mHandler.obtainMessage(INIT_COPY);
        final InstallParams params = new InstallParams(origin, null, observer,
                sessionParams.installFlags, installerPackageName, sessionParams.volumeUuid,
                verificationInfo, user, sessionParams.abiOverride,
                sessionParams.grantedRuntimePermissions, certificates);
        ...
        msg.obj = params;
        ...
        mHandler.sendMessage(msg);
    }

我们把整个过程精简到上面三局代码,发送一个what为INIT_COPY的消息

case INIT_COPY: {
                    HandlerParams params = (HandlerParams) msg.obj;
                    int idx = mPendingInstalls.size();
                    if (DEBUG_INSTALL) Slog.i(TAG, "init_copy idx=" + idx + ": " + params);
                    // 此处为了连接服务,初始情况都为false,待服务脸上之后就会把mBound设置为true,就可以进入下面条件
                    // 因此这里我们直接进入else的条件
                    if (!mBound) {
                        Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "bindingMCS",
                                System.identityHashCode(mHandler));
                        // If this is the only one pending we might
                        // have to bind to the service again.
                        if (!connectToService()) {
                            Slog.e(TAG, "Failed to bind to media container service");
                            params.serviceError();
                            Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "bindingMCS",
                                    System.identityHashCode(mHandler));
                            if (params.traceMethod != null) {
                                Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, params.traceMethod,
                                        params.traceCookie);
                            }
                            return;
                        } else {
                            // Once we bind to the service, the first
                            // pending request will be processed.
                            mPendingInstalls.add(idx, params);
                        }
                    } else {
                        mPendingInstalls.add(idx, params);
                        // Already bound to the service. Just make
                        // sure we trigger off processing the first request.
                        if (idx == 0) {
                            mHandler.sendEmptyMessage(MCS_BOUND);
                        }
                    }
                    break;
                }

直接在最后看到发送了MCS_BOUND消息

 case MCS_BOUND: {
                    ...
                    //服务已经连上,直接跳到else if
                    if (mContainerService == null) {
                        ...
                    } 
                    //等待安装的队列肯定大于0
                    else if (mPendingInstalls.size() > 0) {
                        HandlerParams params = mPendingInstalls.get(0);
                        if (params != null) {
                            Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
                                    System.identityHashCode(params));
                            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "startCopy");
                            //开始进入到copy
                            if (params.startCopy()) {
                                // We are done...  look for more work or to
                                // go idle.
                                if (DEBUG_SD_INSTALL) Log.i(TAG,
                                        "Checking for more work or unbind...");
                                // Delete pending install
                                if (mPendingInstalls.size() > 0) {
                                    mPendingInstalls.remove(0);
                                }
                                //如果已经把要安装的都处理完了,那么久断开服务连接
                                //否则就再发送一个MCS_BOUND信息,继续循环处理
                                if (mPendingInstalls.size() == 0) {
                                    if (mBound) {
                                        if (DEBUG_SD_INSTALL) Log.i(TAG,
                                                "Posting delayed MCS_UNBIND");
                                        removeMessages(MCS_UNBIND);
                                        Message ubmsg = obtainMessage(MCS_UNBIND);
                                        // Unbind after a little delay, to avoid
                                        // continual thrashing.
                                        sendMessageDelayed(ubmsg, 10000);
                                    }
                                } else {
                                    // There are more pending requests in queue.
                                    // Just post MCS_BOUND message to trigger processing
                                    // of next pending install.
                                    if (DEBUG_SD_INSTALL) Log.i(TAG,
                                            "Posting MCS_BOUND for next work");
                                    mHandler.sendEmptyMessage(MCS_BOUND);
                                }
                            }
                            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
                        }
                    } else {
                        // Should never happen ideally.
                        Slog.w(TAG, "Empty queue");
                    }
                    break;
                }

我们直接进入到startCopy

final boolean startCopy() {
            boolean res;
            try {
                if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this);
                //尝试的次数超过了最大尝试次数,就宣告失败
                if (++mRetries > MAX_RETRIES) {
                    Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
                    mHandler.sendEmptyMessage(MCS_GIVE_UP);
                    handleServiceError();
                    return false;
                } else {
                    //开始进行拷贝
                    handleStartCopy();
                    res = true;
                }
            } catch (RemoteException e) {
                if (DEBUG_INSTALL) Slog.i(TAG, "Posting install MCS_RECONNECT");
                mHandler.sendEmptyMessage(MCS_RECONNECT);
                res = false;
            }
            //处理return code
            handleReturnCode();
            return res;
        }

我们先来看一下handleStartCopy方法,发现时抽象方法,具体的实现,我们网上可以找到是 InstallParams。

public void handleStartCopy() throws RemoteException {
    int ret = PackageManager.INSTALL_SUCCEEDED;
    // 决定是安装在手机内还是sdcard中,设置对应标志位
    if (origin.staged) {
        if (origin.file != null) {
            installFlags |= PackageManager.INSTALL_INTERNAL;
            installFlags &= ~PackageManager.INSTALL_EXTERNAL;
        } else if (origin.cid != null) {
            installFlags |= PackageManager.INSTALL_EXTERNAL;
            installFlags &= ~PackageManager.INSTALL_INTERNAL;
        } else {
            throw new IllegalStateException("Invalid stage location");
        }
    }
    ...
    // 检查APK的安装位置是否正确
    if (onInt && onSd) {
        // Check if both bits are set.
        Slog.w(TAG, "Conflicting flags specified for installing on both internal and external");
        ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
    } else if (onSd && ephemeral) {
        Slog.w(TAG,  "Conflicting flags specified for installing ephemeral on external");
        ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
    } else {
        ...
    }
    ...
    // createInstallArgs用于创建一个安装参数对象
    final InstallArgs args = createInstallArgs(this);
    
    if (ret == PackageManager.INSTALL_SUCCEEDED) {
        ...
            // 调用InstallArgs的copyApk函数
            ret = args.copyApk(mContainerService, true);
        }
    }
    mRet = ret;
}

主要工作就是复制apk消息到指定位置
再来看handleReturnCode

void handleReturnCode() {
    // If mArgs is null, then MCS couldn't be reached. When it
    // reconnects, it will try again to install. At that point, this
    // will succeed.
    if (mArgs != null) {
        processPendingInstall(mArgs, mRet);
    }
}

我们一路看,processPendingInstall()->installPackageTracedLI()->installPackageLI(),整个方法比较长,主要做了清单文件解析和获取证书,提取签名的工作,你可以从这个方法中找到如下代码段,就是这段代码处理了安装


    try (PackageFreezer freezer = freezePackageForInstall(pkgName, installFlags,
            "installPackageLI")) {
        if (replace) {
            // 4.更新已经存在的packages
            replacePackageLIF(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user,
                    installerPackageName, res);
        } else {
            // 5.安装新的packages
            installNewPackageLIF(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES,
                    args.user, installerPackageName, volumeUuid, res);
        }
    }

我们继续

/*
     * Install a non-existing package.
     */
    private void installNewPackageLIF(PackageParser.Package pkg, final int policyFlags,
            int scanFlags, UserHandle user, String installerPackageName, String volumeUuid,
            PackageInstalledInfo res) {
        ...
        try {
            PackageParser.Package newPackage = scanPackageTracedLI(pkg, policyFlags, scanFlags,
                    System.currentTimeMillis(), user);

            updateSettingsLI(newPackage, installerPackageName, null, res, user);

            if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
                prepareAppDataAfterInstallLIF(newPackage);

            } else {
                // Remove package from internal structures, but keep around any
                // data that might have already existed
                deletePackageLIF(pkgName, UserHandle.ALL, false, null,
                        PackageManager.DELETE_KEEP_DATA, res.removedInfo, true, null);
            }
        } catch (PackageManagerException e) {
            res.setError("Package couldn't be installed in " + pkg.codePath, e);
        }
        ...
    }

经过删减省略,我们看到了我们熟悉的方法scanPackageTracedLI,再具体的步骤见上面系统应用安装。


最后我们再来看一下另外一种ADB的安装

adb安装

当我们在在命令行窗口输入adb install的时候实际上,系统会帮我们运行如下的程序

db_commandline
    install_app_legacy or install_app 
        pm_command
            send_shell_command
                Pm.runInstall()

这个过程会把apk文件copy到data/local/tmp/目录下,然后向shell服务发送pm命令安装apk,最后调用Pm.runInstall()方法来安装apk。
我们可以看到这个方法:

private int runInstall() throws RemoteException {
        ...
        final int sessionId = doCreateSession(params.sessionParams,
                params.installerPackageName, params.userId);

        try {
            if (inPath == null && params.sessionParams.sizeBytes == 0) {
                System.err.println("Error: must either specify a package size or an APK file");
                return 1;
            }
            if (doWriteSession(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk",
                    false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
                return 1;
            }
            if (doCommitSession(sessionId, false /*logSuccess*/)
                    != PackageInstaller.STATUS_SUCCESS) {
                return 1;
            }
            System.out.println("Success");
            return 0;
        } finally {
            try {
                mInstaller.abandonSession(sessionId);
            } catch (Exception ignore) {
            }
        }
    }

我们依然省略着看,把上面这一段略去,直接在最下面看到三个重要的方法,doCreateSession,doWriteSession,doCommitSession,直接点开最后一个

private int doCommitSession(int sessionId, boolean logSuccess) throws RemoteException {
        PackageInstaller.Session session = null;
        try {
            session = new PackageInstaller.Session(
                    mInstaller.openSession(sessionId));

            final LocalIntentReceiver receiver = new LocalIntentReceiver();
            session.commit(receiver.getIntentSender());

            final Intent result = receiver.getResult();
            final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
                    PackageInstaller.STATUS_FAILURE);
            if (status == PackageInstaller.STATUS_SUCCESS) {
                if (logSuccess) {
                    System.out.println("Success");
                }
            } else {
                System.err.println("Failure ["
                        + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
            }
            return status;
        } finally {
            IoUtils.closeQuietly(session);
        }
    }

看到熟悉的session.commit了么,


总结

下面是有安装界面的app安装流程,中途插入adb install以及没有安装界面的系统安装。

  • PackageInstallerActivity的onCreate();
  • 根据从Intent中获取的Data的scheme的不同,调用不同的处理逻辑安装App;
  • 解析App文件到Package对象;
  • initInstall()函数获取apk中的权限信息;
  • onClick()监听用户点击同意安装按钮;
  • 启动InstallAppProcess Activity;
  • onCrete()函数调用
  • initView()函数;
  • 继续调用doPackageStage()函数,做session.commit();
    此处插入adb install 1.Pm.runInstall() 2.Pm.doCommitSession()
  • 通过Handler机制启动了startCopy()函数;
  • startCopy()调用了启动了handlerStartCopy()将apk拷贝到/data/app、pkg-name/和把.so文件拷贝到/data/app/pkg-name/lib/的
  • handlerReturnCode()函数;
  • handlerReturnCode调用了installPackageLI()函数;
  • 在installPackageLI()中,首先调用了collectCertificates()函数对apk的签名做了校验,确保apk没有被非法修改(修改其中的文件);
    接着,调用collectManifestDigest()函数计算了Manifest.xml的文件摘要,并且存在了Package的manifestDigest成员下;
    接着,将从apk文件中提提取的manifest计算摘要和之前installer解析到的进行比较,若不匹配,则抛出异常。
    然后,对App升级或者使用了共享属性sharedUid的情况校验待安装的App的证书是否匹配,并就升级方式做了具体处理,这里有两个问题,就是升级过程中的Upgrade keysets升级方式和Signature[]的获得不明朗。
  • 根据是升级App还是安装新App,调用不同的函数进行处理环节。
    此处插入系统应用安装 1.scanPackageTracedLI 2.scanPackageLI 3.scanPackageDirtyLI
  • 最后在scanPackageDirtyLI方法中,把所有的provider,service,receiver,activity从parser中拿出来保存到PMS中。

以上!

心得
Web note ad 1