應(yīng)用安裝(三)- 系統(tǒng)實(shí)現(xiàn)apk安裝整體流程

系統(tǒng)源碼參考:android 11。

系統(tǒng)側(cè)實(shí)現(xiàn)apk安裝鲤遥,主要通過PakcageManagerService來完成疚俱,安裝過程主要分為復(fù)制apk和安裝apk兩個(gè)階段书在,本篇文章現(xiàn)在針對(duì)整體安裝流程進(jìn)行梳理,中間牽扯到的重要模塊之后單獨(dú)梳理腮恩。

一梢杭、復(fù)制apk流程解析

frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

void installStage(ActiveInstallSession activeInstallSession) {
    if (DEBUG_INSTANT) {
        if ((activeInstallSession.getSessionParams().installFlags
                & PackageManager.INSTALL_INSTANT_APP) != 0) {
            Slog.d(TAG, "Ephemeral install of " + activeInstallSession.getPackageName());
       }
    }

    final Message msg = mHandler.obtainMessage(INIT_COPY);
   //初始化InstallParams,后續(xù)會(huì)由他來主導(dǎo)安裝任務(wù)
   final InstallParams params = new InstallParams(activeInstallSession);
   params.setTraceMethod("installStage").setTraceCookie(System.identityHashCode(params));
   msg.obj = params;
   Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "installStage",
           System.identityHashCode(msg.obj));
   Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
           System.identityHashCode(msg.obj));
  //發(fā)送INIT_COPY消息
   mHandler.sendMessage(msg);
}

這個(gè)方法主要干了兩件事:

  • 初始化InstallParams
  • 發(fā)送INIT_COPY消息

初始化InstallParams秸滴,在構(gòu)造方法中:

InstallParams(ActiveInstallSession activeInstallSession) {
    super(activeInstallSession.getUser());
   final PackageInstaller.SessionParams sessionParams =
            activeInstallSession.getSessionParams();
   if (DEBUG_INSTANT) {
        if ((sessionParams.installFlags
                & PackageManager.INSTALL_INSTANT_APP) != 0) {
            Slog.d(TAG, "Ephemeral install of " + activeInstallSession.getPackageName());
       }
    }
    verificationInfo = new VerificationInfo(
            sessionParams.originatingUri,
           sessionParams.referrerUri,
           sessionParams.originatingUid,
           activeInstallSession.getInstallerUid());
   origin = OriginInfo.fromStagedFile(activeInstallSession.getStagedDir());
   move = null;
   installReason = fixUpInstallReason(
            activeInstallSession.getInstallSource().installerPackageName,
           activeInstallSession.getInstallerUid(),
           sessionParams.installReason);
   observer = activeInstallSession.getObserver();
   installFlags = sessionParams.installFlags;
   installSource = activeInstallSession.getInstallSource();
   volumeUuid = sessionParams.volumeUuid;
   packageAbiOverride = sessionParams.abiOverride;
   grantedRuntimePermissions = sessionParams.grantedRuntimePermissions;
   whitelistedRestrictedPermissions = sessionParams.whitelistedRestrictedPermissions;
   autoRevokePermissionsMode = sessionParams.autoRevokePermissionsMode;
   signingDetails = activeInstallSession.getSigningDetails();
   requiredInstalledVersionCode = sessionParams.requiredInstalledVersionCode;
   forceQueryableOverride = sessionParams.forceQueryableOverride;
   mDataLoaderType = (sessionParams.dataLoaderParams != null)
            ? sessionParams.dataLoaderParams.getType() : DataLoaderType.NONE;
   mSessionId = activeInstallSession.getSessionId();
}

這里很明顯武契,之前PackageInstaller將apk寫入session,這里通過session獲取到安裝包信息,并賦值給InstallParams吝羞,同時(shí)這里額外初始化了一個(gè)VerificationInfo驗(yàn)證信息類兰伤。

再來看發(fā)送INIT_COPY消息:

case INIT_COPY: {
    HandlerParams params = (HandlerParams) msg.obj;
   if (params != null) {
        if (DEBUG_INSTALL) Slog.i(TAG, "init_copy: " + params);
       Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
               System.identityHashCode(params));
       Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "startCopy");
       params.startCopy();
       Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
   }
    break;
}

這里的HandlerParams是抽象基類,具體實(shí)現(xiàn)是前面?zhèn)魅氲腎nstallParams钧排,由它來執(zhí)行startCopy

final void startCopy() {
    if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this);
   handleStartCopy(); 
   handleReturnCode();
}

startCopy分了兩步敦腔,一個(gè)個(gè)來看。

/*
* Invoke remote method to get package information and install
* location values. Override install location based on default
* policy if needed and then create install arguments based
* on the install location.
*/
public void handleStartCopy() {
    int ret = PackageManager.INSTALL_SUCCEEDED;
   // If we're already staged, we've firmly committed to an install location
   if (origin.staged) {
        if (origin.file != null) {
            installFlags |= PackageManager.INSTALL_INTERNAL;
       } else {
            throw new IllegalStateException("Invalid stage location");
       }
    }
    final boolean onInt = (installFlags & PackageManager.INSTALL_INTERNAL) != 0;
   final boolean ephemeral = (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
   PackageInfoLite pkgLite = null;
    //通過PackageParser.parsePackageLite解析一個(gè)精簡(jiǎn)版的包信息
   pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mContext,
           origin.resolvedPath, installFlags, packageAbiOverride);
   if (DEBUG_INSTANT && ephemeral) {
        Slog.v(TAG, "pkgLite for install: " + pkgLite);
   }
    /*
    * If we have too little free space, try to free cache
    * before giving up.
    */
    //檢查剩余的磁盤空間
        //如果是通過ActiveInstallSession 初始化的InstallParams恨溜,origin.staged為true
   if (!origin.staged && pkgLite.recommendedInstallLocation
            == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
        // TODO: focus freeing disk space on the target device
       final StorageManager storage = StorageManager.from(mContext);
       final long lowThreshold = storage.getStorageLowBytes(
                Environment.getDataDirectory());
       final long sizeBytes = PackageManagerServiceUtils.calculateInstalledSize(
                origin.resolvedPath, packageAbiOverride);
       if (sizeBytes >= 0) {
            try {
                mInstaller.freeCache(null, sizeBytes + lowThreshold, 0, 0);
               pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mContext,
                       origin.resolvedPath, installFlags, packageAbiOverride);
           } catch (InstallerException e) {
                Slog.w(TAG, "Failed to free cache", e);
           }
        }
        /*
        * The cache free must have deleted the file we downloaded to install.
        *
        * TODO: fix the "freeCache" call to not delete the file we care about.
        */
       if (pkgLite.recommendedInstallLocation
                == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
            pkgLite.recommendedInstallLocation
                    = PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
       }
    }
    if (ret == PackageManager.INSTALL_SUCCEEDED) {
        int loc = pkgLite.recommendedInstallLocation;
       if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {
            ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
       } else if (loc == PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS) {
            ret = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
       } else if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
            ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
       } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) {
            ret = PackageManager.INSTALL_FAILED_INVALID_APK;
       } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
            ret = PackageManager.INSTALL_FAILED_INVALID_URI;
       } else if (loc == PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE) {
            ret = PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
       } else {
            // Override with defaults if needed.
           //確定推薦安裝
           loc = installLocationPolicy(pkgLite);
           if (loc == PackageHelper.RECOMMEND_FAILED_VERSION_DOWNGRADE) {
                ret = PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
           } else if (loc == PackageHelper.RECOMMEND_FAILED_WRONG_INSTALLED_VERSION) {
                ret = PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION;
           } else if (!onInt) {
                // Override install location with flags
               if (loc == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) {
                    // Set the flag to install on external media.
                   installFlags &= ~PackageManager.INSTALL_INTERNAL;
               } else if (loc == PackageHelper.RECOMMEND_INSTALL_EPHEMERAL) {
                    if (DEBUG_INSTANT) {
                        Slog.v(TAG, "...setting INSTALL_EPHEMERAL install flag");
                   }
                    installFlags |= PackageManager.INSTALL_INSTANT_APP;
                   installFlags &= ~PackageManager.INSTALL_INTERNAL;
               } else {
                    // Make sure the flag for installing on external
                   // media is unset
                   installFlags |= PackageManager.INSTALL_INTERNAL;
               }
            }
        }
    }

    //創(chuàng)建InstallArgs
    final InstallArgs args = createInstallArgs(this);
   mVerificationCompleted = true;
   mIntegrityVerificationCompleted = true;
   mEnableRollbackCompleted = true;
   mArgs = args;
   if (ret == PackageManager.INSTALL_SUCCEEDED) {
        final int verificationId = mPendingVerificationToken++;
       // Perform package verification (unless we are simply moving the package).
       if (!origin.existing) {
            PackageVerificationState verificationState =
                    new PackageVerificationState(this);
           mPendingVerification.append(verificationId, verificationState);
            //發(fā)起安裝包完整性校驗(yàn)
           sendIntegrityVerificationRequest(verificationId, pkgLite, verificationState);
           ret = sendPackageVerificationRequest(
                    verificationId, pkgLite, verificationState);
           // If both verifications are skipped, we should remove the state.
           if (verificationState.areAllVerificationsComplete()) {
                mPendingVerification.remove(verificationId);
           }
        }
  ...
    mRet = ret;

}

整個(gè)方法都是基于PackageManagerServiceUtils.getMinimalPackageInfo發(fā)起的符衔,根據(jù)對(duì)包解析的情況,來決定如下事情的處理:

  • 是否有足夠的磁盤空間糟袁,不夠則清理判族;
  • 確定推薦安裝位置
  • 初始化InstallArgs來做后續(xù)的copy apk任務(wù);
  • 發(fā)起包的完整性校驗(yàn)项戴;

然后接著走h(yuǎn)andleReturnCode方法

void handleReturnCode() {
    //校驗(yàn)通過
    if (mVerificationCompleted
            && mIntegrityVerificationCompleted && mEnableRollbackCompleted) {
        if ((installFlags & PackageManager.INSTALL_DRY_RUN) != 0) {
            String packageName = "";
           ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
                    new ParseTypeImpl(
                            (changeId, packageName1, targetSdkVersion) -> {
                                ApplicationInfo appInfo = new ApplicationInfo();
                               appInfo.packageName = packageName1;
                               appInfo.targetSdkVersion = targetSdkVersion;
                               return mPackageParserCallback.isChangeEnabled(changeId,
                                       appInfo);
                           }).reset(),
                   origin.file, 0);
           if (result.isError()) {
                Slog.e(TAG, "Can't parse package at " + origin.file.getAbsolutePath(),
                       result.getException());
           } else {
                packageName = result.getResult().packageName;
           }
            try {
                observer.onPackageInstalled(packageName, mRet, "Dry run", new Bundle());
           } catch (RemoteException e) {
                Slog.i(TAG, "Observer no longer exists.");
           }
            return;
       }
        if (mRet == PackageManager.INSTALL_SUCCEEDED) {
            mRet = mArgs.copyApk();
       }
        processPendingInstall(mArgs, mRet);
   }
}

校驗(yàn)完成形帮,由InstallArgs發(fā)起apk copy操作。之前在初始化的時(shí)候會(huì)根據(jù)是否已經(jīng)安裝了當(dāng)前應(yīng)用來初始化不同的實(shí)現(xiàn)類:

private InstallArgs createInstallArgs(InstallParams params) {
    if (params.move != null) {
               //處理已安裝的應(yīng)用程序   
        return new MoveInstallArgs(params);
   } else {
               //處理新安裝的應(yīng)用程序
        return new FileInstallArgs(params);
   }
}

而后續(xù)發(fā)起的copyApk流程最終結(jié)果是將apk和lib copy到確認(rèn)的安裝目錄下

void handleReturnCode() {
   ...
        if (mRet == PackageManager.INSTALL_SUCCEEDED) {
            mRet = mArgs.copyApk();
       }
        processPendingInstall(mArgs, mRet);
   }
}
/data/app/com.miui.packageinstaller-Qp2hRWM-6O9lWcKH-GT-dw== # ls -al
-rw-r--r--  1 system system  6385298 2021-04-28 07:44 base.apk. //apk
drwxr-xr-x  3 system system     4096 2021-04-28 07:44 lib     //native lib

整個(gè)流程粗略看起來比較簡(jiǎn)單周叮,但是其中有幾個(gè)細(xì)節(jié)值得深挖一下:確定推薦安裝位置辩撑、包完整性校驗(yàn)。這個(gè)后續(xù)有時(shí)間再單獨(dú)分析仿耽。

二合冀、安裝apk流程解析

g

handleReturnCode最終執(zhí)行processPendingInstall發(fā)起安裝:

private void processPendingInstall(final InstallArgs args, final int currentStatus) {
   if (args.mMultiPackageInstallParams != null) {
       args.mMultiPackageInstallParams.tryProcessInstallRequest(args, currentStatus);
   } else {
       PackageInstalledInfo res = createPackageInstalledInfo(currentStatus);
       processInstallRequestsAsync(
               res.returnCode == PackageManager.INSTALL_SUCCEEDED,
               Collections.singletonList(new InstallRequest(args, res)));
   }
}

多包和單包安裝,最終都會(huì)走到processInstallRequestsAsync方法

private void processInstallRequestsAsync(boolean success,
       List<InstallRequest> installRequests) {
   mHandler.post(() -> {
       if (success) {
           for (InstallRequest request : installRequests) {
                //安裝前處理
               request.args.doPreInstall(request.installResult.returnCode);
           }
           synchronized (mInstallLock) {
                 //發(fā)起安裝
               installPackagesTracedLI(installRequests);
           }
           for (InstallRequest request : installRequests) {
                //安裝后處理
               request.args.doPostInstall(
                       request.installResult.returnCode, request.installResult.uid);
           }
       }
       for (InstallRequest request : installRequests) {
           restoreAndPostInstall(request.args.user.getIdentifier(), request.installResult,
                   new PostInstallData(request.args, request.installResult, null));
       }
   });
}

這里分了三步:對(duì)安裝前项贺、中君躺、后分別做了處理。這里args對(duì)應(yīng)的是FileInstallArgs开缎。

2.1 安裝前后處理工作

FileInstallArgs

int doPreInstall(int status) {
    if (status != PackageManager.INSTALL_SUCCEEDED) {
        cleanUp();
   }
    return status;
}

int doPostInstall(int status, int uid) {
    if (status != PackageManager.INSTALL_SUCCEEDED) {
        cleanUp();
   }
    return status;
}

安裝前后棕叫,都會(huì)判斷是否安裝成功,如果不成功啥箭,走相同清理流程谍珊。clearup就是對(duì)安裝相關(guān)目錄文件進(jìn)行清理。

2.2 發(fā)起安裝流程

installPackagesTracedLI直接調(diào)用installPackagesLI來處理安裝:

/**
* Installs one or more packages atomically. This operation is broken up into four phases:
* <ul>
*     <li><b>Prepare</b>
*         <br/>Analyzes any current install state, parses the package and does initial
*         validation on it.</li>
*     <li><b>Scan</b>
*         <br/>Interrogates the parsed packages given the context collected in prepare.</li>
*     <li><b>Reconcile</b>
*         <br/>Validates scanned packages in the context of each other and the current system
*         state to ensure that the install will be successful.
*     <li><b>Commit</b>
*         <br/>Commits all scanned packages and updates system state. This is the only place
*         that system state may be modified in the install flow and all predictable errors
*         must be determined before this phase.</li>
* </ul>
*
* Failure at any phase will result in a full failure to install all packages.
*/

@GuardedBy("mInstallLock")
private void installPackagesLI(List<InstallRequest> requests) {
   final Map<String, ScanResult> preparedScans = new ArrayMap<>(requests.size());
   final Map<String, InstallArgs> installArgs = new ArrayMap<>(requests.size());
   final Map<String, PackageInstalledInfo> installResults = new ArrayMap<>(requests.size());
   final Map<String, PrepareResult> prepareResults = new ArrayMap<>(requests.size());
   final Map<String, VersionInfo> versionInfos = new ArrayMap<>(requests.size());
   final Map<String, PackageSetting> lastStaticSharedLibSettings =
           new ArrayMap<>(requests.size());
   final Map<String, Boolean> createdAppId = new ArrayMap<>(requests.size());
   boolean success = false;
   try {
       Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackagesLI");
       for (InstallRequest request : requests) {
           // TODO(b/109941548): remove this once we've pulled everything from it and into
           //                    scan, reconcile or commit.
           final PrepareResult prepareResult;
                // 1.prepare
               prepareResult =
                       preparePackageLI(request.args, request.installResult);
          ...
                // 2.scan
               final ScanResult result = scanPackageTracedLI(
                       prepareResult.packageToScan, prepareResult.parseFlags,
                       prepareResult.scanFlags, System.currentTimeMillis(),
                       request.args.user, request.args.abiOverride);
...

       ReconcileRequest reconcileRequest = new ReconcileRequest(preparedScans, installArgs,
               installResults,
               prepareResults,
               mSharedLibraries,
               Collections.unmodifiableMap(mPackages), versionInfos,
               lastStaticSharedLibSettings);
       CommitRequest commitRequest = null;
       synchronized (mLock) {
           Map<String, ReconciledPackage> reconciledPackages;
           try {
               Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "reconcilePackages”);
                 // 3.reconcile
               reconciledPackages = reconcilePackagesLocked(
                       reconcileRequest, mSettings.mKeySetManagerService);
         ...
               commitRequest = new CommitRequest(reconciledPackages,
                       mUserManager.getUserIds());
                // 4.commit
               commitPackagesLocked(commitRequest);
               success = true;
           } finally {
               Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
           }
       }
                   //5.執(zhí)行應(yīng)用程序安裝
       executePostCommitSteps(commitRequest);
  ...
}

主要工作分四個(gè)階段:

  • prepare: 解析apk急侥。
  • scan: 掃描apk砌滞,更新共享庫(kù)和settings信息。
  • reconcile:驗(yàn)證掃描包狀態(tài)以確保安裝成功坏怪。
  • commit:提交所有掃描的包并更新系統(tǒng)狀態(tài)贝润。

這里后面會(huì)分別來進(jìn)行分析。

最后:executePostCommitSteps 執(zhí)行安裝后的掃尾工作

/**
* On successful install, executes remaining steps after commit completes and the package lock
* is released. These are typically more expensive or require calls to installd, which often
* locks on {@link #mLock}.
*/
private void executePostCommitSteps(CommitRequest commitRequest) {
    final ArraySet<IncrementalStorage> incrementalStorages = new ArraySet<>();
   for (ReconciledPackage reconciledPkg : commitRequest.reconciledPackages.values()) {
        final boolean instantApp = ((reconciledPkg.scanResult.request.scanFlags
                        & PackageManagerService.SCAN_AS_INSTANT_APP) != 0);
       final AndroidPackage pkg = reconciledPkg.pkgSetting.pkg;
       final String packageName = pkg.getPackageName();
       final boolean onIncremental = mIncrementalManager != null
               && isIncrementalPath(pkg.getCodePath());
       …
        //createAppData
        prepareAppDataAfterInstallLIF(pkg);
      …
        //dex2oat編譯
           mPackageDexOptimizer.performDexOpt(pkg, realPkgSetting,
                   null /* instructionSets */,
                   getOrCreateCompilerPackageStats(pkg),
                   mDexManager.getPackageUseInfoOrDefault(packageName),
                   dexoptOptions);
           Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
       }
        // Notify BackgroundDexOptService that the package has been changed.
       // If this is an update of a package which used to fail to compile,
       // BackgroundDexOptService will remove it from its denylist.
       // TODO: Layering violation
       BackgroundDexOptService.notifyPackageChanged(packageName);
       notifyPackageChangeObserversOnUpdate(reconciledPkg);
   }
    NativeLibraryHelper.waitForNativeBinariesExtraction(incrementalStorages);
}

這里主要是通過installd干兩件事:createAppData铝宵、dex2oat 編譯

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載打掘,如需轉(zhuǎn)載請(qǐng)通過簡(jiǎn)信或評(píng)論聯(lián)系作者华畏。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市尊蚁,隨后出現(xiàn)的幾起案子亡笑,更是在濱河造成了極大的恐慌,老刑警劉巖横朋,帶你破解...
    沈念sama閱讀 222,627評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仑乌,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡琴锭,警方通過查閱死者的電腦和手機(jī)晰甚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來决帖,“玉大人厕九,你說我怎么就攤上這事〉鼗兀” “怎么了扁远?”我有些...
    開封第一講書人閱讀 169,346評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)落君。 經(jīng)常有香客問我穿香,道長(zhǎng),這世上最難降的妖魔是什么绎速? 我笑而不...
    開封第一講書人閱讀 60,097評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮焙蚓,結(jié)果婚禮上纹冤,老公的妹妹穿的比我還像新娘。我一直安慰自己购公,他們只是感情好萌京,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,100評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著宏浩,像睡著了一般知残。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上比庄,一...
    開封第一講書人閱讀 52,696評(píng)論 1 312
  • 那天求妹,我揣著相機(jī)與錄音,去河邊找鬼佳窑。 笑死制恍,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的神凑。 我是一名探鬼主播净神,決...
    沈念sama閱讀 41,165評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼何吝,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了鹃唯?” 一聲冷哼從身側(cè)響起爱榕,我...
    開封第一講書人閱讀 40,108評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎坡慌,沒想到半個(gè)月后呆细,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,646評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡八匠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,709評(píng)論 3 342
  • 正文 我和宋清朗相戀三年絮爷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片梨树。...
    茶點(diǎn)故事閱讀 40,861評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡坑夯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出抡四,到底是詐尸還是另有隱情柜蜈,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布指巡,位于F島的核電站淑履,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏藻雪。R本人自食惡果不足惜秘噪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,196評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望勉耀。 院中可真熱鬧指煎,春花似錦、人聲如沸便斥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)枢纠。三九已至像街,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間晋渺,已是汗流浹背镰绎。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留些举,地道東北人跟狱。 一個(gè)月前我還...
    沈念sama閱讀 49,287評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像户魏,于是被迫代替她去往敵國(guó)和親驶臊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子挪挤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,860評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • APK安裝流程系列文章整體內(nèi)容如下: APK安裝流程詳解0——前言APK安裝流程詳解1——有關(guān)"安裝ing"的實(shí)體...
    隔壁老李頭閱讀 14,225評(píng)論 15 59
  • 背景 你是否知道APK是如何進(jìn)行裝載的?又是否知道APK具體的安裝原理关翎。當(dāng)你以此為契機(jī)查閱各種資料的時(shí)候扛门,發(fā)現(xiàn)各不...
    MxsQ閱讀 7,224評(píng)論 0 21
  • Android Apk安裝過程分析 本文以android 8.0(API Level:26) 源碼講解apk安裝過...
    北疆小兵閱讀 706評(píng)論 0 0
  • 背景 安裝影響的目錄 /system/app /data/app /data/data /data/dalvik-...
    II花菜君II閱讀 1,585評(píng)論 0 2
  • 前言 android系統(tǒng)里app有哪些類型及其安裝涉及目錄、所需權(quán)限是什么纵寝? apk安裝有幾種方式论寨? apk安裝流...
    MY1112閱讀 7,802評(píng)論 1 9