Android APK安装流程(1)

前言

  • android系统里app有哪些类型及其安装涉及目录、所需权限是什么?
  • apk安装有几种方式?
  • apk安装流程会涉及到哪些android系统知识?
  • apk安装的过程大体上分哪几步?
  • apk安装的过程中涉及到比较重要的类有哪些?分别用来做什么?
  • 了解apk安装流程有什么用?

Android系统里app类型、安装涉及目录、所需权限

1./system/framwork:
  • 保存的是资源型的应用程序,它们用来打包资源文件
2./system/app:
  • 系统自带的应用程序,获得adb root 权限才能删除。
  • 如果想修改该目录下的app,必须手动push新的apk进去,该新app文件不会自动被安装,需要系统重启时,系统检查到apk被更新,才会去安装。
  • 系统app升级时,实际上是在/data/app里重新安装了一个app,只是这个路径会重新注册到系统那里去,当打开该系统app时,会指向新app的地址。
  • 如果你卸载该更新后的系统app,系统只是卸载/data/app里的app,然后恢复/system/app里的app给用户使用。
3./system/priv-app:
  • 这里放的是权限更高的系统核心应用,例如系统设置、系统UI、开机launcher等,这个目录非常重要,一般情况下不建议改动。
4./vendor/app:
  • 保存设备厂商提供的应用程序
5./data/app:
  • 用户程序安装的目录,安装时把apk文件复制到此目录。因为Android机有内部存储和SD卡两部分,很多Android机为了节省内存空间,会把apk安装到SD卡上。如此以来,/data/app在大部分Android机上会有2个,1个在内部存储1个在SD卡里。
6./data/app-private:
  • 保存受DRM保护的私有应用程序,例如一个受保护的歌曲或受保护的视频是使用 DRM保护的文件。
7./data/data:
  • 存放应用程序数据的目录
8./data/dalvik-cache:
  • 存放apk中的dex文件。dex文件是dalvik虚拟机的可执行文件,其大小约为原始apk文件大小的四分之一,但是ART-Android Runtime的可执行文件格式为.oat,所以启动ART时,系统会执行dex文件转换至oat文件
9./data/system:

该目录主要是存放一些系统文件

  • packages.xml文件:类似于Window的注册表,这个文件是解析apk时由writeLP()创建的,里面记录了系统的permissons,以及每个apk的name,codePath,flag,ts,version,userid,native 库的存储路径等信息,这些信息主要通过apk的AndroidManifest解析获取,解析完apk后将更新信息写入这个文件并保存到flash,下次开机的时候直接从里面读取相关信息并添加到内存相关列表中。当有apk升级,安装或删除时会更新这个文件。
  • pakcages-back.xml:packages.xml文件的备份。
  • pakcages-stoped.xml:记录系统中被强制停止的运行的应用信息,系统在强制停止某个应用的时候,会将应用的信息记录在该文件中。
  • pakcages-stoped-backup.xml:pakcages-stoped.xml文件的备份。
  • package.list:packages.list指定了应用的默认存储位置/data/data/com.xxx.xxx;
  • 这5个文件中pakcages-back.xml和pakcages-stoped-backup.xml是备份文件。当Android对文件packages.xml和pakcages-stoped.xml写之前,会先把它们备份,如果写文件成功了,再把备份文件删除。如果写的时候,系统出问题了,重启后在需要读取这两个文件时,如果发现备份文件存在,会使用备份文件的内容,因为源文件可能已经损坏了。其中packages.xml是PackageManagerServcie启动时,需要用到的文件。

APK安装主体流程

apk_install_structure.png

1.复制APK到/data/app目录下,解压并扫描安装包
2.将APP的dex文件拷贝到/data/dalvik-cache目录,再在/data/data/目录下创建应用程序的数据目录(以应用包名命令),用来存放应用的数据库、xml文件、cache、二进制的so动态库等
3.解析apk的AndroidManifest.xml文件,注册四大组件,将apk的权限、应用包名、apk的安装位置、版本、userID等重要信息保存在/data/system/packages.xml文件中。这些操作都是在PackageManagerService中完成。
4.资源管理器解析APK里的资源文件。
5.dex2oat操作,对dex文件进行优化,并保存在dalvik-cache目录下。
6.更新权限信息。
7.安装完成后,发送Intent.ACTION_PACKAGE_ADDED广播。
8.显示icon图标,应用程序经过PMS中的逻辑处理后,相当于已经注册好了,如果想要在Android桌面上看到icon图标,则需要Launcher将系统中已经安装的程序展现在桌面上。

总体说来就两件事情拷贝APK和解析APK,解析APK主要是解析APK的应用配置文件AndroidManifest.xml,以便获得它的安装信息。在安装的过程中还会这个应用分配Linux用户ID和Linux用户组ID(以便它可以在系统中获取合适的运行权限)。

Tips,dexopt/dex2oat 操作在不同版本下的区别:
  • Dalvik虚拟机-dexopt: 该时机在第一次执行时app时 非安装时(详情见 Android运行流程)
  • ART虚拟机-dex2oat:AOT (Ahead-Of-Time) 运行前编译
    • Android O(8.0)前,将dex字节码翻译成本地字节码oat(非全量)
    • Android O(8.0)开始:开始新增vdex概念,不是为了提升性能,而是为了避免不必要的验证Dex 文件合法性的过程,例如首次安装时进行dex2oat时会校验Dex 文件各个section的合法性,这时候使用的compiler filter 为了照顾安装速度等方面,并没有采用全量编译,当app盘启动后,运行一段时间后,收集了足够多的jit 热点方法信息,Android会在后台重新进行dex2oat,将热点方法编译成机器代码,这时候就不用再重复做验证Dex文件的过程了。

APK安装的几种方式

  • 1、系统安装

系统启动后调用 PackageManagerService.main() 初始化注册解析安装工作。PackageManagerService处理各种应用的安装,卸载,管理等工作,开机时由systemServer启动此服务。
第1步:PackageManagerService.main()初始化注册
第2步:建立Java层的installer与C层的intalld的socket联接
第3步:建立PackageHandler消息循环
第4步:调用成员变量mSettings的readLPw()方法恢复上一次的安装信息
第5步:.jar文件的detopt优化
第6步:scanDirLI函数扫描特定目录的APK文件解析
第7步:updatePermissionsLPw()函数分配权限
第8步:调用mSettings.writeLPr()保存安装信息

  • 2、adb 命令安装

通过 pm 参数,调用 PM 的 runInstall 方法,进入 PackageManagerService 进行安装工作。
第1步:pm.java的runInstall()方法
第2步:参数不对会调用showUsage方法,弹出使用说明
第3步:正常情况runInstall会调用mPm变量的installPackageWithVerification方法
第4步:由于pm.java中的变量mPm是PackageManagerService的实例,所以实际上是调用PackageManagerService的installPackageWithVerfication()方法
第5步:进入PackageManagerService的doHandleMessage方法的INIT_COPY分支
第6步:成功绑定了com.android.defcontainer.DefaultContainerService服务,进入MCS_BOUND分支
第7步:里面调用PackageManagerService中内部抽象类HandlerParams的子类InstallParams的startCopy方法。
第8步:抽象类的HandlerParams的startCopy方法调用了HandlerParams子类的handleStartCopy和handlerReturnCode两个方法
第9步:handlesStartCopy方法调用了InstallArgs的子类copyApk,它负责将下载的APK文件copy到/data/app
第10步:handleReturnCode调用handleReturnCode方法
第11步:调用PackageManagerService服务的installPackageLI(PackageParser.Package, int, int, UserHandle, String, String,PackageInstalledInfo)方法进行APK扫描。
第12步:上面的方法判断是否APP应安装,调用installNewPackageLI或replacePackageLI方法
第13步:调用updateSettingsLI方法进行更新PackageManagerService的Settings
第14步:发送what值为POST_INSTALL的Message给PackageHandler进行处理
第15步:发送what值为MCS_UNBIND的Message给PackageHandler,进而调用PackageHandler.disconnectService()中断连接

  • 3、应用市场安装

这个要视应用的权限,有系统的权限无安装界面(例如MiUI的小米应用商店)。
第1步:调用PackageManagerService的installPackage方法
第2步:上面的方法调用installPackageWithVerfication(),进行权限校验,发送INIT_COPY的msg
第3步:进入PackageManagerService的doHandleMessage方法的INIT_COPY分支
第4步:成功绑定了com.android.defcontainer.DefaultContainerService服务,进入MCS_BOUND分支
第5步:里面调用PackageManagerService中内部抽象类HandlerParams的子类InstallParams的startCopy方法。
第6步:抽象类的HandlerParams的startCopy方法调用了HandlerParams子类的handleStartCopy和handlerReturnCode两个方法
第7步:handlesStartCopy方法调用了InstallArgs的子类copyApk,它负责将下载的APK文件copy到/data/app
第8步:handleReturnCode调用handleReturnCode方法
第9步:调用PackageManagerService服务的installPackageLI(PackageParser.Package, int, int, UserHandle, String, String,PackageInstalledInfo)方法进行APK扫描。
第10步:上面的方法判断是否APP应安装,调用installNewPackageLI或replacePackageLI方法
第11步:调用updateSettingsLI方法进行更新PackageManagerService的Settings
第12步:发送what值为POST_INSTALL的Message给PackageHandler进行处理
第13步:发送what值为MCS_UNBIND的Message给PackageHandler,进而调用PackageHandler.disconnectService()中断连接

  • 4、第三方安装

有安装界面,通过PackageInstaller.apk来处理,安装及卸载的过程的界面先调用 InstallStart(是一个Activity) 进行权限检查之后启动 PackageInstallActivity,调用 PackageInstallActivity 的 startInstall 方法,点击 OK 按钮后进入 PackageManagerService 完成拷贝解析安装工作。
第1步:通过隐式跳转启动InstallStart
第2步:在InstallStart的onCreate生命周期里判断如果不允许未知来源的则阻止安装、targetSdkVersion<0阻止安装,如果targetSdkVersion大于等于26(8.0), 且获取不到REQUEST_INSTALL_PACKAGES权限也中止安装,然后启动PackageInstallerActivity
第3步:调用PackageInstallerActivity的onCreate方法初始化安装界面
第4步:初始化界面以后调用initiateInstall方法
第5步:上面的方法调用startInstallConfirm方法,弹出确认和取消安装的按钮
第6步:点击确认按钮,打开新的activity:InstallAppProgress
第7步:InstallAppProgress类初始化带有进度条的界面之后,调用PackageManager的installPackage方法
第8步:PackageManager是PackageManagerService实例,所以就是调用PackageManagerService的installPackage方法
第9步:调用PackageManagerService的installPackage方法
第10步:上面的方法调用installPackageWithVerfication(),进行权限校验,发送INIT_COPY的msg
第11步:进入PackageManagerService的doHandleMessage方法的INIT_COPY分支
第12步:成功绑定了com.android.defcontainer.DefaultContainerService服务,进入MCS_BOUND分支
第13步:里面调用PackageManagerService中内部抽象类HandlerParams的子类InstallParams的startCopy方法。
第14步:抽象类的HandlerParams的startCopy方法调用了HandlerParams子类的handleStartCopy和handlerReturnCode两个方法
第15步:handlesStartCopy方法调用了InstallArgs的子类copyApk,它负责将下载的APK文件copy到/data/app
第16步:handleReturnCode调用handleReturnCode方法
第17步:调用PackageManagerService服务的installPackageLI(PackageParser.Package, int, int, UserHandle, String, String,PackageInstalledInfo)方法进行APK扫描。
第18步:上面的方法判断是否APP应安装,调用installNewPackageLI或replacePackageLI方法
第19步:调用updateSettingsLI方法进行更新PackageManagerService的Settings
第20步:发送what值为POST_INSTALL的Message给PackageHandler进行处理
第21步:发送what值为MCS_UNBIND的Message给PackageHandler,进而调用PackageHandler.disconnectService()中断连接

以上4种安装apk方式最终都会走到PackageManagerService,其中第4种方式走的流程最长最完整,所以下面我们会用第4种方式的流程进行代码详细分析。

APK安装详细代码流程(android-10.0.0_r14)

  • 第1步 通过隐式跳转启动InstallStart

InstallStart是从Android8.0开始PackageInstaller.apk的入口Activity,7.0 隐式匹配的 Activity 是 PackageInstallerActivity。
platform/frameworks/base/packages/PackageInstaller/AndroidManifest.xml

        ...
        <activity android:name=".InstallStart"
                android:theme="@android:style/Theme.Translucent.NoTitleBar"
                android:exported="true"
                android:excludeFromRecents="true">
            <intent-filter android:priority="1">
                <action android:name="android.intent.action.VIEW" />
                <action android:name="android.intent.action.INSTALL_PACKAGE" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="content" />
                <data android:mimeType="application/vnd.android.package-archive" />
            </intent-filter>
            <intent-filter android:priority="1">
                <action android:name="android.intent.action.INSTALL_PACKAGE" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="package" />
                <data android:scheme="content" />
            </intent-filter>
            <intent-filter android:priority="1">
                <action android:name="android.content.pm.action.CONFIRM_INSTALL" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
        ...

从上面的Manifest文件我们可以看出除了通过android:mimeType=application/vnd.android.package-archive能启动入口Activity-InstallStart,还能通过action为android.intent.action.INSTALL_PACKAGE的其他Scheme以及"android.content.pm.action.CONFIRM_INSTALL启动,其中定义了两个 scheme:content 和 package,这2种scheme数据会在后面流程里的PackageInstallerActivity里解析通过不同的方式获取到安装包信息,最后在InstallInstalling再通过不同的安装方式来完成安装动作(后续会有详细分析)。

  • content: 在 file 协议的处理中调用了 PackageUtil.getPackageInfo 方法,方法内部调用了 PackageParser.parsePackage() 把 APK 文件的 manifest 和签名信息都解析完成并保存在了 Package,Package 包含了该 APK 的所有信息,调用 PackageParser.generatePackageInfo 生成 PackageInfo。

  • package: 在 package 协议中调用了 PackageManager.getPackageInfo 方法生成 PackageInfo,PackageInfo 是跨进程传递的包数据(activities、receivers、services、providers、permissions等等)包含 APK 的所有信息。

  • 第2步 InstallStart启动,进入其onCreate()生命周期

其实onCreate()生命周期里主要做了如下事情:
1.判断当前的apk包是否是可信任来源,如果是不可信任来源,且没有申请不可信任来源包安装权限,则会强制结束当前的Activity。
2.此时就会从data从获取到我们传递进来的apk包的地址uri,并且设置下一个启动的Activity为InstallStaging。
3.启动InstallStaging 这个Activity。
源码如下:

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mIPackageManager = AppGlobals.getPackageManager();
        Intent intent = getIntent();
        String callingPackage = getCallingPackage();

        final boolean isSessionInstall =
                PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction());

        // If the activity was started via a PackageInstaller session, we retrieve the calling
        // package from that session
        final int sessionId = (isSessionInstall
                ? intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1)
                : -1);
        if (callingPackage == null && sessionId != -1) {
            PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
            PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId);
            callingPackage = (sessionInfo != null) ? sessionInfo.getInstallerPackageName() : null;
        }

        final ApplicationInfo sourceInfo = getSourceInfo(callingPackage);
        final int originatingUid = getOriginatingUid(sourceInfo);
        boolean isTrustedSource = false;
        // 判断是否勾选“未知来源”选项
        if (sourceInfo != null
                && (sourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
            isTrustedSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false);
        }

        if (!isTrustedSource && originatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
            final int targetSdkVersion = getMaxTargetSdkVersionForUid(this, originatingUid);
            // 如果targetSdkVerison小于0中止安装
            if (targetSdkVersion < 0) {
                Log.w(LOG_TAG, "Cannot get target sdk version for uid " + originatingUid);
                // Invalid originating uid supplied. Abort install.
                mAbortInstall = true;
            // 如果targetSdkVersion大于等于26(8.0), 且获取不到REQUEST_INSTALL_PACKAGES权限中止安装
            } else if (targetSdkVersion >= Build.VERSION_CODES.O && !declaresAppOpPermission(
                    originatingUid, Manifest.permission.REQUEST_INSTALL_PACKAGES)) {
                Log.e(LOG_TAG, "Requesting uid " + originatingUid + " needs to declare permission "
                        + Manifest.permission.REQUEST_INSTALL_PACKAGES);
                mAbortInstall = true;
            }
        }
        if (mAbortInstall) {
            setResult(RESULT_CANCELED);
            finish();
            return;
        }

        Intent nextActivity = new Intent(intent);
        nextActivity.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);

        // The the installation source as the nextActivity thinks this activity is the source, hence
        // set the originating UID and sourceInfo explicitly
        nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_PACKAGE, callingPackage);
        nextActivity.putExtra(PackageInstallerActivity.EXTRA_ORIGINAL_SOURCE_INFO, sourceInfo);
        nextActivity.putExtra(Intent.EXTRA_ORIGINATING_UID, originatingUid);
        // 如果设置了ACTION_CONFIRM_INSTALL,则调用PackageInstallerActivity
        if (isSessionInstall) {
            nextActivity.setClass(this, PackageInstallerActivity.class);
        } else {
            Uri packageUri = intent.getData();
            // 判断Uri的Scheme协议是否是content
            if (packageUri != null && packageUri.getScheme().equals(
                    ContentResolver.SCHEME_CONTENT)) {
                // 这个路径已经被起用了,但是仍然可以工作
                // [IMPORTANT] This path is deprecated, but should still work. Only necessary
                // features should be added.

                // Copy file to prevent it from being changed underneath this process


                // 调用InstallStaging来拷贝file/content,防止被修改
                nextActivity.setClass(this, InstallStaging.class);
            } else if (packageUri != null && packageUri.getScheme().equals(
                    PackageInstallerActivity.SCHEME_PACKAGE)) {
                // 如果Uri中包含package,则调用PackageInstallerActivity
                nextActivity.setClass(this, PackageInstallerActivity.class);
            } else {
                // Uri不合法
                Intent result = new Intent();
                result.putExtra(Intent.EXTRA_INSTALL_RESULT,
                        PackageManager.INSTALL_FAILED_INVALID_URI);
                setResult(RESULT_FIRST_USER, result);

                nextActivity = null;
            }
        }

        if (nextActivity != null) {
            startActivity(nextActivity);
        }
        finish();
    }

从以上源码我们可以看出:
1.如果我们使用另一种安装方式,设置Intent的action为PackageInstaller.ACTION_CONFIRM_INSTALL(也就是android.content.pm.action.CONFIRM_INSTALL),则会直接启动PackageInstallerActivity
2.如果Scheme是package协议也是直接启动PackageInstallerActivity
3.只有当Scheme是content时才会跳转到InstallStaging(即使跳转到InstallStaging,最后还是会跳转到PackageInstallerActivity,所以第3步我们会分析跳转到InstallStaging的代码)

  • 第3步 启动InstallStaging,进入其onResume()生命周期

看其onResume()方法(核心实现)

    @Override
    protected void onResume() {
        super.onResume();
        // This is the first onResume in a single life of the activity
        if (mStagingTask == null) {
            // File does not exist, or became invalid
            if (mStagedFile == null) {
                // Create file delayed to be able to show error
                try {
                    mStagedFile = TemporaryFileManager.getStagedFile(this);
                } catch (IOException e) {
                    showError();
                    return;
                }
            }
            mStagingTask = new StagingAsyncTask();
            mStagingTask.execute(getIntent().getData());
        }
    }

在这里通过TemporaryFileManager.getStagedFile(this)方法构建了一个临时文件:

  /**
     * Create a new file to hold a staged file.
     *
     * @param context The context of the caller
     *
     * @return A new file
     */
    @NonNull
    public static File getStagedFile(@NonNull Context context) throws IOException {
        return File.createTempFile("package", ".apk", context.getNoBackupFilesDir());
    }

这个文件建立在当前应用私有目录下的no_backup文件夹上。也就是/data/no_backup/packagexxx.apk 这个临时文件。

这有一个StagingAsyncTask类:

    private final class StagingAsyncTask extends AsyncTask<Uri, Void, Boolean> {
        @Override
        protected Boolean doInBackground(Uri... params) {
            if (params == null || params.length <= 0) {
                return false;
            }
            Uri packageUri = params[0];
            try (InputStream in = getContentResolver().openInputStream(packageUri)) {
                // Despite the comments in ContentResolver#openInputStream the returned stream can
                // be null.
                if (in == null) {
                    return false;
                }

                try (OutputStream out = new FileOutputStream(mStagedFile)) {
                    byte[] buffer = new byte[1024 * 1024];
                    int bytesRead;
                    while ((bytesRead = in.read(buffer)) >= 0) {
                        // Be nice and respond to a cancellation
                        if (isCancelled()) {
                            return false;
                        }
                        out.write(buffer, 0, bytesRead);
                    }
                }
            } catch (IOException | SecurityException | IllegalStateException e) {
                Log.w(LOG_TAG, "Error staging apk from content URI", e);
                return false;
            }
            return true;
        }

        @Override
        protected void onPostExecute(Boolean success) {
            if (success) {
                // Now start the installation again from a file
                Intent installIntent = new Intent(getIntent());
                installIntent.setClass(InstallStaging.this, DeleteStagedFileOnResult.class);
                installIntent.setData(Uri.fromFile(mStagedFile));

                if (installIntent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
                    installIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
                }

                installIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
                startActivity(installIntent);

                InstallStaging.this.finish();
            } else {
                showError();
            }
        }
    }

StagingAsyncTask主要工作:
1.doInBackground 是指在异步线程池中处理的事务。doInBackground中实际上就是把uri中需要安装的apk拷贝到临时文件中(上文的/data/no_backup/packagexxx.apk)。
2.onPostExecute 当拷贝任务处理完之后,就会把当前的临时文件Uri作为Intent的参数(这个时候会把Uri的类型设置为file),跳转到DeleteStagedFileOnResult中。

  • 第4步 启动DeleteStagedFileOnResult

看其源码:

/**
 * Trampoline activity. Calls PackageInstallerActivity and deletes staged install file onResult.
 */
public class DeleteStagedFileOnResult extends Activity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (savedInstanceState == null) {
            Intent installIntent = new Intent(getIntent());
            installIntent.setClass(this, PackageInstallerActivity.class);

            installIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
            startActivityForResult(installIntent, 0);
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        File sourceFile = new File(getIntent().getData().getPath());
        sourceFile.delete();

        setResult(resultCode, data);
        finish();
    }
}

这个Activity就非常简单了,只是作为一个过渡,启动真正大头戏份PackageInstallerActivity。如果PackageInstallerActivity安装失败了,就会退出PackageInstallerActivity界面返回到DeleteStagedFileOnResult的onActivityResult中删除这个临时文件。

  • 第5步 启动PackageInstallerActivity

这个类是真正的安装界面,我们先来看其onCreate()方法:

    @Override
    protected void onCreate(Bundle icicle) {
        getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);

        super.onCreate(null);

        if (icicle != null) {
            mAllowUnknownSources = icicle.getBoolean(ALLOW_UNKNOWN_SOURCES_KEY);
        }

        mPm = getPackageManager();
        mIpm = AppGlobals.getPackageManager();
        mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
        mInstaller = mPm.getPackageInstaller();
        mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);

        final Intent intent = getIntent();

        mCallingPackage = intent.getStringExtra(EXTRA_CALLING_PACKAGE);
        mSourceInfo = intent.getParcelableExtra(EXTRA_ORIGINAL_SOURCE_INFO);
        mOriginatingUid = intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID,
                PackageInstaller.SessionParams.UID_UNKNOWN);
        mOriginatingPackage = (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN)
                ? getPackageNameForUid(mOriginatingUid) : null;


        final Uri packageUri;

        if (PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction())) {
            final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
            final PackageInstaller.SessionInfo info = mInstaller.getSessionInfo(sessionId);
            if (info == null || !info.sealed || info.resolvedBaseCodePath == null) {
                Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
                finish();
                return;
            }

            mSessionId = sessionId;
            packageUri = Uri.fromFile(new File(info.resolvedBaseCodePath));
            mOriginatingURI = null;
            mReferrerURI = null;
        } else {
            mSessionId = -1;
            packageUri = intent.getData();
            mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
            mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
        }

        // if there's nothing to do, quietly slip into the ether
        if (packageUri == null) {
            Log.w(TAG, "Unspecified source");
            setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
            finish();
            return;
        }

        if (DeviceUtils.isWear(this)) {
            showDialogInner(DLG_NOT_SUPPORTED_ON_WEAR);
            return;
        }

        boolean wasSetUp = processPackageUri(packageUri);
        if (!wasSetUp) {
            return;
        }

        // load dummy layout with OK button disabled until we override this layout in
        // startInstallConfirm
        bindUi();
        checkIfAllowedAndInitiateInstall();
    }

从上面的代码我们可以看出:
1.如果使用PackageInstaller.ACTION_CONFIRM_INSTALL(也就是第1步里InstallStart接收到的action一直传下来的)模式进行安装,那么就会获取保存在Intent中的ApplicationInfo对象,获取其中的resolvedBaseCodePath也就是代码文件路径。这种处理文件的方式会通过Uri.fromFile(new File(info.resolvedBaseCodePath))将Uri的Scheme设置为file类型(这个file类型会在下个Activity里用到)。
2.如果是使用android.intent.action.INSTALL_PACKAGE,也就是带有Scheme的情况,则通过Intent的getData获取到保存在其中的临时文件(就是上一步中说的/data/no_backup/packagexxx.apk)。通过这种方式获取到的PackageUri的Scheme是package类型。
3.上面2步主要是为了获取代码文件,形成PackageUri,然后通过方法processPackageUri() 扫描路径对应的文件包,实例化PackageInfo对象mPkgInfo(安装的必要属性)。
4.bindUi()初始化安装UI
5.checkIfAllowedAndInitiateInstall()启动安装

下面是processPackageUri()方法源码:

    /**
     * Parse the Uri and set up the installer for this package.
     *
     * @param packageUri The URI to parse
     *
     * @return {@code true} iff the installer could be set up
     */
    private boolean processPackageUri(final Uri packageUri) {
        mPackageURI = packageUri;

        final String scheme = packageUri.getScheme();

        switch (scheme) {
            case SCHEME_PACKAGE: {
                try {
                    mPkgInfo = mPm.getPackageInfo(packageUri.getSchemeSpecificPart(),
                            PackageManager.GET_PERMISSIONS
                                    | PackageManager.MATCH_UNINSTALLED_PACKAGES);
                } catch (NameNotFoundException e) {
                }
                if (mPkgInfo == null) {
                    Log.w(TAG, "Requested package " + packageUri.getScheme()
                            + " not available. Discontinuing installation");
                    showDialogInner(DLG_PACKAGE_ERROR);
                    setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
                    return false;
                }
                mAppSnippet = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo),
                        mPm.getApplicationIcon(mPkgInfo.applicationInfo));
            } break;

            case ContentResolver.SCHEME_FILE: {
                File sourceFile = new File(packageUri.getPath());
                PackageParser.Package parsed = PackageUtil.getPackageInfo(this, sourceFile);

                // Check for parse errors
                if (parsed == null) {
                    Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
                    showDialogInner(DLG_PACKAGE_ERROR);
                    setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
                    return false;
                }
                mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
                        PackageManager.GET_PERMISSIONS, 0, 0, null,
                        new PackageUserState());
                mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
            } break;

            default: {
                throw new IllegalArgumentException("Unexpected URI scheme " + packageUri);
            }
        }

        return true;
    }

从前面几个Activity的处理逻辑中我们可以发现,直接通过ACTION_CONFIRM_INSTALL跳转到PackageInstallerActivity的Uri的Scheme会被转换成file类型;原来Scheme类型为content的在InstallStaging的StagingAsyncTask处理完毕后其Uri的Scheme也会转成file;只有原来Scheme类型为package的是从InstallStart直接跳转到PackageInstallerActivity的,其Scheme才是package,所以最终的Scheme只剩下packagefile,而processPackageUri()方法也只会解析这两种uri:
1.package协议开头的uri:这种uri会通过PackageManager的getPackageInfo()通过getSchemeSpecificPart获取对应apk文件对应的PackageInfo信息
2.file协议开头的uri,则使用PackageParser的generatePackageInfo()方法获取apk文件中的package信息。最终实现mPkgInfo的属性初始化。
3.这个方法里会引入PackageManager和PackageParser这2个类,后续会讲诉这俩类和PMS之间的关系,暂不多表。

我们看一下PackageParser的generatePackageInfo()方法:

    @UnsupportedAppUsage
    public static PackageInfo generatePackageInfo(PackageParser.Package p,
            int gids[], int flags, long firstInstallTime, long lastUpdateTime,
            Set<String> grantedPermissions, PackageUserState state, int userId) {
        if (!checkUseInstalledOrHidden(flags, state, p.applicationInfo) || !p.isMatch(flags)) {
            return null;
        }
        PackageInfo pi = new PackageInfo();
        pi.packageName = p.packageName;
        pi.splitNames = p.splitNames;
        pi.versionCode = p.mVersionCode;
        pi.versionCodeMajor = p.mVersionCodeMajor;
        pi.baseRevisionCode = p.baseRevisionCode;
        pi.splitRevisionCodes = p.splitRevisionCodes;
        pi.versionName = p.mVersionName;
        pi.sharedUserId = p.mSharedUserId;
        pi.sharedUserLabel = p.mSharedUserLabel;
        pi.applicationInfo = generateApplicationInfo(p, flags, state, userId);
        pi.installLocation = p.installLocation;
        pi.isStub = p.isStub;
        pi.coreApp = p.coreApp;
        if ((pi.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0
                || (pi.applicationInfo.flags&ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
            pi.requiredForAllUsers = p.mRequiredForAllUsers;
        }
        pi.restrictedAccountType = p.mRestrictedAccountType;
        pi.requiredAccountType = p.mRequiredAccountType;
        pi.overlayTarget = p.mOverlayTarget;
        pi.targetOverlayableName = p.mOverlayTargetName;
        pi.overlayCategory = p.mOverlayCategory;
        pi.overlayPriority = p.mOverlayPriority;
        pi.mOverlayIsStatic = p.mOverlayIsStatic;
        pi.compileSdkVersion = p.mCompileSdkVersion;
        pi.compileSdkVersionCodename = p.mCompileSdkVersionCodename;
        pi.firstInstallTime = firstInstallTime;
        pi.lastUpdateTime = lastUpdateTime;
        if ((flags&PackageManager.GET_GIDS) != 0) {
            pi.gids = gids;
        }
        if ((flags&PackageManager.GET_CONFIGURATIONS) != 0) {
            int N = p.configPreferences != null ? p.configPreferences.size() : 0;
            if (N > 0) {
                pi.configPreferences = new ConfigurationInfo[N];
                p.configPreferences.toArray(pi.configPreferences);
            }
            N = p.reqFeatures != null ? p.reqFeatures.size() : 0;
            if (N > 0) {
                pi.reqFeatures = new FeatureInfo[N];
                p.reqFeatures.toArray(pi.reqFeatures);
            }
            N = p.featureGroups != null ? p.featureGroups.size() : 0;
            if (N > 0) {
                pi.featureGroups = new FeatureGroupInfo[N];
                p.featureGroups.toArray(pi.featureGroups);
            }
        }
        if ((flags & PackageManager.GET_ACTIVITIES) != 0) {
            final int N = p.activities.size();
            if (N > 0) {
                int num = 0;
                final ActivityInfo[] res = new ActivityInfo[N];
                for (int i = 0; i < N; i++) {
                    final Activity a = p.activities.get(i);
                    if (state.isMatch(a.info, flags)) {
                        if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(a.className)) {
                            continue;
                        }
                        res[num++] = generateActivityInfo(a, flags, state, userId);
                    }
                }
                pi.activities = ArrayUtils.trimToSize(res, num);
            }
        }
        if ((flags & PackageManager.GET_RECEIVERS) != 0) {
            final int N = p.receivers.size();
            if (N > 0) {
                int num = 0;
                final ActivityInfo[] res = new ActivityInfo[N];
                for (int i = 0; i < N; i++) {
                    final Activity a = p.receivers.get(i);
                    if (state.isMatch(a.info, flags)) {
                        res[num++] = generateActivityInfo(a, flags, state, userId);
                    }
                }
                pi.receivers = ArrayUtils.trimToSize(res, num);
            }
        }
        if ((flags & PackageManager.GET_SERVICES) != 0) {
            final int N = p.services.size();
            if (N > 0) {
                int num = 0;
                final ServiceInfo[] res = new ServiceInfo[N];
                for (int i = 0; i < N; i++) {
                    final Service s = p.services.get(i);
                    if (state.isMatch(s.info, flags)) {
                        res[num++] = generateServiceInfo(s, flags, state, userId);
                    }
                }
                pi.services = ArrayUtils.trimToSize(res, num);
            }
        }
        if ((flags & PackageManager.GET_PROVIDERS) != 0) {
            final int N = p.providers.size();
            if (N > 0) {
                int num = 0;
                final ProviderInfo[] res = new ProviderInfo[N];
                for (int i = 0; i < N; i++) {
                    final Provider pr = p.providers.get(i);
                    if (state.isMatch(pr.info, flags)) {
                        res[num++] = generateProviderInfo(pr, flags, state, userId);
                    }
                }
                pi.providers = ArrayUtils.trimToSize(res, num);
            }
        }
        if ((flags&PackageManager.GET_INSTRUMENTATION) != 0) {
            int N = p.instrumentation.size();
            if (N > 0) {
                pi.instrumentation = new InstrumentationInfo[N];
                for (int i=0; i<N; i++) {
                    pi.instrumentation[i] = generateInstrumentationInfo(
                            p.instrumentation.get(i), flags);
                }
            }
        }
        if ((flags&PackageManager.GET_PERMISSIONS) != 0) {
            int N = p.permissions.size();
            if (N > 0) {
                pi.permissions = new PermissionInfo[N];
                for (int i=0; i<N; i++) {
                    pi.permissions[i] = generatePermissionInfo(p.permissions.get(i), flags);
                }
            }
            N = p.requestedPermissions.size();
            if (N > 0) {
                pi.requestedPermissions = new String[N];
                pi.requestedPermissionsFlags = new int[N];
                for (int i=0; i<N; i++) {
                    final String perm = p.requestedPermissions.get(i);
                    pi.requestedPermissions[i] = perm;
                    // The notion of required permissions is deprecated but for compatibility.
                    pi.requestedPermissionsFlags[i] |= PackageInfo.REQUESTED_PERMISSION_REQUIRED;
                    if (grantedPermissions != null && grantedPermissions.contains(perm)) {
                        pi.requestedPermissionsFlags[i] |= PackageInfo.REQUESTED_PERMISSION_GRANTED;
                    }
                }
            }
        }
        // deprecated method of getting signing certificates
        if ((flags&PackageManager.GET_SIGNATURES) != 0) {
            if (p.mSigningDetails.hasPastSigningCertificates()) {
                // Package has included signing certificate rotation information.  Return the oldest
                // cert so that programmatic checks keep working even if unaware of key rotation.
                pi.signatures = new Signature[1];
                pi.signatures[0] = p.mSigningDetails.pastSigningCertificates[0];
            } else if (p.mSigningDetails.hasSignatures()) {
                // otherwise keep old behavior
                int numberOfSigs = p.mSigningDetails.signatures.length;
                pi.signatures = new Signature[numberOfSigs];
                System.arraycopy(p.mSigningDetails.signatures, 0, pi.signatures, 0, numberOfSigs);
            }
        }

        // replacement for GET_SIGNATURES
        if ((flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0) {
            if (p.mSigningDetails != SigningDetails.UNKNOWN) {
                // only return a valid SigningInfo if there is signing information to report
                pi.signingInfo = new SigningInfo(p.mSigningDetails);
            } else {
                pi.signingInfo = null;
            }
        }
        return pi;
    }

1.这个方法看上去又臭又长,不做详细分析,其核心只是解析了四大组件的内容以及权限相关的标签,拿到了apk包所有运行需要的基础信息。
2.这个方法我们要留意2个类:PackageInfo和PackageParser.Package,后续我们会做分析。

bindUi()方法源码:

    private void bindUi() {
        mAlert.setIcon(mAppSnippet.icon);
        mAlert.setTitle(mAppSnippet.label);
        mAlert.setView(R.layout.install_content_view);
        mAlert.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.install),
                (ignored, ignored2) -> {
                    if (mOk.isEnabled()) {
                        if (mSessionId != -1) {
                            mInstaller.setPermissionsResult(mSessionId, true);
                            finish();
                        } else {
                            startInstall();
                        }
                    }
                }, null);
        mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel),
                (ignored, ignored2) -> {
                    // Cancel and finish
                    setResult(RESULT_CANCELED);
                    if (mSessionId != -1) {
                        mInstaller.setPermissionsResult(mSessionId, false);
                    }
                    finish();
                }, null);
        setupAlert();

        mOk = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
        mOk.setEnabled(false);
    }

该方法是对安装界面UI的初始化,默认会把“安装”按钮(mOK)隐藏掉,点击BUTTON_POSITIVE会执行startInstall(),待我们分析完onCreate()里所有方法后再回过头来看一下这个方法。

我们再来看onCreate()里的最后一个方法:checkIfAllowedAndInitiateInstall()

    /**
     * Check if it is allowed to install the package and initiate install if allowed. If not allowed
     * show the appropriate dialog.
     */
    private void checkIfAllowedAndInitiateInstall() {
        // Check for install apps user restriction first.
        final int installAppsRestrictionSource = mUserManager.getUserRestrictionSource(
                UserManager.DISALLOW_INSTALL_APPS, Process.myUserHandle());
        //允许安装未知来源的文件判断
        if ((installAppsRestrictionSource & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {
            showDialogInner(DLG_INSTALL_APPS_RESTRICTED_FOR_USER);
            return;
        } else if (installAppsRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
            startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
            finish();
            return;
        }

        if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {
            //核心代码
            initiateInstall();
        } else {
            // Check for unknown sources restrictions.
            final int unknownSourcesRestrictionSource = mUserManager.getUserRestrictionSource(
                    UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, Process.myUserHandle());
            final int unknownSourcesGlobalRestrictionSource = mUserManager.getUserRestrictionSource(
                    UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, Process.myUserHandle());
            final int systemRestriction = UserManager.RESTRICTION_SOURCE_SYSTEM
                    & (unknownSourcesRestrictionSource | unknownSourcesGlobalRestrictionSource);
            if (systemRestriction != 0) {
                showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER);
            } else if (unknownSourcesRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
                startAdminSupportDetailsActivity(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
            } else if (unknownSourcesGlobalRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
                startAdminSupportDetailsActivity(
                        UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY);
            } else {
                handleUnknownSources();
            }
        }
    }

继续看其核心方法:initiateInstall()

    private void initiateInstall() {
        String pkgName = mPkgInfo.packageName;
        // Check if there is already a package on the device with this name
        // but it has been renamed to something else.
        String[] oldName = mPm.canonicalToCurrentPackageNames(new String[] { pkgName });
        if (oldName != null && oldName.length > 0 && oldName[0] != null) {
            pkgName = oldName[0];
            mPkgInfo.packageName = pkgName;
            mPkgInfo.applicationInfo.packageName = pkgName;
        }
        // Check if package is already installed. display confirmation dialog if replacing pkg
        try {
            // This is a little convoluted because we want to get all uninstalled
            // apps, but this may include apps with just data, and if it is just
            // data we still want to count it as "installed".
            mAppInfo = mPm.getApplicationInfo(pkgName,
                    PackageManager.MATCH_UNINSTALLED_PACKAGES);
            if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
                mAppInfo = null;
            }
        } catch (NameNotFoundException e) {
            mAppInfo = null;
        }

        startInstallConfirm();
    }

通过PackageManager的getApplicationInfo方法,获取当前Android系统中是否已经安装了当前的app,如果能找到mAppInfo对象,说明是安装了,调用startInstallConfirm刷新按钮的显示的是更新,否则就是没有安装按钮展示的是安装。需要注意的是这里又用到了PackageManager这个类,大家请多留意。

继续看方法startInstallConfirm():

    private void startInstallConfirm() {
        View viewToEnable;

        if (mAppInfo != null) {
            viewToEnable = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
                    ? requireViewById(R.id.install_confirm_question_update_system)
                    : requireViewById(R.id.install_confirm_question_update);
        } else {
            // This is a new application with no permissions.
            viewToEnable = requireViewById(R.id.install_confirm_question);
        }

        viewToEnable.setVisibility(View.VISIBLE);

        mEnableOk = true;
        mOk.setEnabled(true);
    }

该方法很简单,其实就是把上文中bindUi()方法里隐藏的mOK按钮展示出来,用来点击进行更新/安装,所以我们可以回头看一下bindUi()的"mOK"按钮点击执行的方法startInstall()

    private void startInstall() {
        // Start subactivity to actually install the application
        Intent newIntent = new Intent();
        newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
                mPkgInfo.applicationInfo);
        newIntent.setData(mPackageURI);
        newIntent.setClass(this, InstallInstalling.class);
        String installerPackageName = getIntent().getStringExtra(
                Intent.EXTRA_INSTALLER_PACKAGE_NAME);
        if (mOriginatingURI != null) {
            newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI);
        }
        if (mReferrerURI != null) {
            newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI);
        }
        if (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
            newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid);
        }
        if (installerPackageName != null) {
            newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME,
                    installerPackageName);
        }
        if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
            newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
        }
        newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
        if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
        startActivity(newIntent);
        finish();
    }

这个方法的核心就是把mPackageURI这个字段放入Intent,然后启动InstallInstalling这个Activity。

后续流程请看Android APK安装流程(2)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容