APK安裝流程詳解16——Android包管理總結

APK安裝流程系列文章整體內容如下:
  • 1、設計思想
  • 2、PackageManagerService的抽象理解
  • 3、PackageManagerService里面的數據結構
  • 4、PackageManagerService的三大流程
  • 5、PackageManagerService的體系結構

一、設計思想

如果你是Android 系統中的架構師,讓你設計一個Android的安裝系統中的PackageManagerService,你會怎么設計? 既然要設計,咱們要首先弄清幾個問題,我希望大家看下面的問題的時候,多想兩個問題:1、如果讓你設計,你怎么設計。這個"類"存在意義是什么?

  • 1、為什么關機的時候手機是磚頭,而開機后,所有APP都可以運行了,這是為什么?
  • 2、Android系統是通過什么手段來加載已經安裝到手機上應用的?
  • 3、既然是加載,按照科學的架構設計,是不是應該存在一個管理者,來全局管理,那個這個類是什么?
  • 4、在安裝一個APK的時候,APK是"死的",Android系統是怎么把它變成一個"活的"APP,他是怎么加載到內存中去的

那我們就來依次來看下這幾個問題

1、為什么關機的時候手機是磚頭,而開機后,所有APP都可以運行了,它是怎么加載的?

  • 首先明確一點,手機關機以后,就是一個冰冷的磚頭,只能用來"砸核桃",那開機后,你點擊桌面上的任何一個圖片,都能開啟一個APP,這說明在開機過程中,系統把已經安裝好的APP加載到內存中,這到底是怎么做的?所以我們反推斷,在安卓系統中肯定存在這么一塊區域,用于存放已經安裝的APP的信息,在開機的時候,通過系統掃描,這塊區域,把對應的內容加載到內存中去。
  • 其次,通過上面的分析,我們知道了在Android系統中存在這樣一塊區域,在開機的的時候,加載這塊區域的信息,從而實現加載在內存中去。那么我們繼續反推斷,那這塊區域的信息,是怎么來的?應該在安裝這個APK的時候,把這個APK的信息寫入到該區域的。這樣就可以實現了在安卓系統一次安裝后,在刪除APK文件后,還可以運行APP了

其實上面的解答是基本上所有操作的系統的安裝思路,大家可以想一下在Windows下是不是也是如此。

上面說的Android區域其實就是:“/data目錄”下的system目錄,這個目錄用來保存很多系統文件。主要工作是創建了5個位于目錄/data/system的File對象,分別是:

  • packages.xml:記錄了系統中所有安裝的應用信息,包括基本信息、簽名和權限
  • pakcages-back.xml:packages.xml文件
  • pakcages-stoped.xml:記錄系統中被強制停止的運行的應用信息。系統在強制停止某個應用的時候,會將應用的信息記錄在該文件中。
  • pakcages-stoped-backup.xml:pakcages-stoped.xml文件的備份
  • 保存普通應用的數據目錄和uid等信息

這個5個文件中pakcages-back.xml和pakcages-stoped-backup.xml是備份文件。當Android對文件packages.xml和pakcages-stoped.xml寫之前,會先把它們備份,如果寫文件成功了,再把備份文件刪除。如果寫的時候,系統出問題了,重啟后在需要讀取這兩個文件時,如果發現備份文件存在,會使用備份文件的內容,因為源文件可能已經損壞了。其中packages.xmlPackageManagerServcie啟動時,需要用到的文件。
我把我的Nexus 6P手機Root后,在/data/system 截屏如下:

/data/system目錄.png

我把packages.xml導出來,文件內容太大,我就直接截屏了,內容如下:


截屏1.png
截屏2.png

圖片看不清,可以看下面的縮減版

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<packages>
    <version sdkVersion="23" databaseVersion="3" fingerprint="google/angler/angler:6.0.1/MTC20L/3230295:user/release-keys" />
    <version volumeUuid="primary_physical" sdkVersion="23" databaseVersion="23" fingerprint="google/angler/angler:6.0.1/MTC19T/2741993:user/release-keys" />
    <permission-trees>
        <item name="com.google.android.googleapps.permission.GOOGLE_AUTH" package="com.google.android.gsf" />
    </permission-trees>
    <permissions>
        <item name="android.permission.REAL_GET_TASKS" package="android" protection="18" />
        <item name="android.permission.REMOTE_AUDIO_PLAYBACK" package="android" protection="2" />
     .....
        <item name="com.android.voicemail.permission.ADD_VOICEMAIL" package="android" protection="1" />
    </permissions>
    <package name="com.google.android.youtube" codePath="/system/app/YouTube" nativeLibraryPath="/system/app/YouTube/lib" primaryCpuAbi="arm64-v8a" publicFlags="945307205" privateFlags="0" ft="11e9134c000" it="11e9134c000" ut="11e9134c000" version="107560144" userId="10075">
        <sigs count="1">
            <cert index="0" key="30820252308201bb02044934987e300d06092a864886f70d01010405003070310b3009060355040613025553310b3009060355040813024341311630140603550407130d4d6f756e7461696e205669657731143012060355040a130b476f6f676c652c20496e6331143012060355040b130b476f6f676c652c20496e633110300e06035504031307556e6b6e6f776e301e170d3038313230323032303735385a170d3336303431393032303735385a3070310b3009060355040613025553310b3009060355040813024341311630140603550407130d4d6f756e7461696e205669657731143012060355040a130b476f6f676c652c20496e6331143012060355040b130b476f6f676c652c20496e633110300e06035504031307556e6b6e6f776e30819f300d06092a864886f70d010101050003818d00308189028181009f48031990f9b14726384e0453d18f8c0bbf8dc77b2504a4b1207c4c6c44babc00adc6610fa6b6ab2da80e33f2eef16b26a3f6b85b9afaca909ffbbeb3f4c94f7e8122a798e0eba75ced3dd229fa7365f41516415aa9c1617dd583ce19bae8a0bbd885fc17a9b4bd2640805121aadb9377deb40013381418882ec52282fc580d0203010001300d06092a864886f70d0101040500038181004086669ed631da4384ddd061d226e073b98cc4b99df8b5e4be9e3cbe97501e83df1c6fa959c0ce605c4fd2ac6d1c84cede20476cbab19be8f2203aff7717ad652d8fcc890708d1216da84457592649e0e9d3c4bb4cf58da19db1d4fc41bcb9584f64e65f410d0529fd5b68838c141d0a9bd1db1191cb2a0df790ea0cb12db3a4" />
        </sigs>
        <perms>
            <item name="com.google.android.c2dm.permission.RECEIVE" granted="true" flags="0" />
            <item name="android.permission.USE_CREDENTIALS" granted="true" flags="0" />
            <item name="com.google.android.providers.gsf.permission.READ_GSERVICES" granted="true" flags="0" />
            <item name="com.google.android.youtube.permission.C2D_MESSAGE" granted="true" flags="0" />
            <item name="android.permission.MANAGE_ACCOUNTS" granted="true" flags="0" />
            <item name="android.permission.NFC" granted="true" flags="0" />
            <item name="android.permission.CHANGE_NETWORK_STATE" granted="true" flags="0" />
            <item name="android.permission.RECEIVE_BOOT_COMPLETED" granted="true" flags="0" />
            <item name="com.google.android.gms.permission.AD_ID_NOTIFICATION" granted="true" flags="0" />
            <item name="android.permission.INTERNET" granted="true" flags="0" />
            <item name="android.permission.ACCESS_NETWORK_STATE" granted="true" flags="0" />
            <item name="android.permission.VIBRATE" granted="true" flags="0" />
            <item name="android.permission.ACCESS_WIFI_STATE" granted="true" flags="0" />
            <item name="android.permission.WAKE_LOCK" granted="true" flags="0" />
        </perms>
        <proper-signing-keyset identifier="11" />
        <domain-verification packageName="com.google.android.youtube" status="0">
            <domain name="youtu.be" />
            <domain name="m.youtube.com" />
            <domain name="youtube.com" />
            <domain name="www.youtube.com" />
        </domain-verification>
    </package>

上面是我手機packages.xml的一個片段。我們看下里面的"youtube"應用。通過標簽<package>記錄了一個應用的基本信息,簽名和聲明的權限。

(1)<package>表示包信息,下面我們就來解釋下標簽<package>中的屬性
  • name表示應用的包名
  • codePath表示的是apk文件的路徑
  • nativeLibraryPath表示應用的native庫的存儲路徑
  • flags是指應用的屬性,如FLAG_SYSTEM、FLAG_PERSISTENT等
  • it表示應用安裝的時間
  • ut表示應用最后一次修改的時間
  • version表示應用的版本號
  • userId表示所屬于的id
(2)<sign>表示應用的簽名,下面我們就來解釋下 標簽<sign>中的屬性
  • count表示標簽中包含有多少個證書
  • cert表示具體的證書的值
(3)<perms>表示應用聲明使用的權限,每一個子標簽代表一項權限

通過上面的內容,我們知道Android系統通過packages.xml文件來存儲應用信息的,所以我們舉一反三,新安裝的APK,肯定是把新安裝的APK相關信息寫入這個packages.xml文件中,那么怎么把這個xml文件,映射到內存中的? 那我們就來看第二個問題

2、Android系統是通過什么手段來加載已經安裝到手機上應用的

上面提到了,應用的信息都存儲在packages.xml中的<package>標簽里面,那我們是怎么加載到內存中去的?大家平時是存儲數據庫的時候都是怎么做的?對的,一般都是一個實體類對應數據庫中的一個表;其中每一個對象對應的是數據庫中的一條數據。同理,Android系統也是這樣設計的,<package>標簽里面記錄的包信息其實是一一對應的PackageSetting類。

PackageSetting類的繼承關系.png

PackageSetting繼承了PackageSettingBase類,PackageSettingBase類繼承自GrantedPremisson類。應用的基本信息保存在PackageSettingBase類的成員變量中,聲明的權限保存在GrantedPremissions類,簽名則保存在SharedUserSetting類的成員變量signatures中。標簽<package>所標識的應用PackageSetting對象都保存在Setting的mPackages中,定義如下:

// com.android.server.pm.Settings.java

    final HashMap<String, PackageSetting> mPackages =
            new HashMap<String, PackageSetting>();

在packages.xml中除了標簽<package>,還有<updated-package>、<cleaning-package>和<renamed-package> 這三種標簽。

  • <updated-package> 標簽表示升級包覆蓋的系統應用,對應的是PackageSetting,在Settings里面同樣用mPackages 變量表示
  • <cleaning-package> 標簽用來記錄那些已經刪除,但是數據目錄還暫時保留的應用的信息。對應的是PackageCleanItem。在Settings里面用mPackagesToBeCleaned變量表示
  • <renamed-package> 標簽用來記錄系統中改名的應用。它的記錄信息都插入到mSettings的mRenamedPackages對象中。

其中mPackagesToBeCleaned和mRenamedPackages在mSettings.java的定義如下:

// com.android.server.pm.Settings.java

    // Packages that have been uninstalled and still need their external
    // storage data deleted.
    final ArrayList<PackageCleanItem> mPackagesToBeCleaned = new ArrayList<PackageCleanItem>();
    
    // Packages that have been renamed since they were first installed.
    // Keys are the new names of the packages, values are the original
    // names.  The packages appear everwhere else under their original
    // names.
    final HashMap<String, String> mRenamedPackages = new HashMap<String, String>();

上面用大量的文筆說Settings,那么它是什么東西?下面就讓我繼續來看下一個問題

3、既然是加載,按照科學的架構設計,是不是應該存在一個管理者,來全局管理,那個這個類是什么?

這個類就是Settings

Settings是Android的包的全局管理者,用于協助PackageManagerService保存所有的安裝包信息,同時用于存儲系統執行過程中的一些設置,PackageManagerService和Settings之間的類圖關系如下:

PackageManagerService和Settings的關系.png

大圖地址1

Settings里面有3個重要的成員變量:mShareUsers,mPackages,mSharedUsers 。如下:

    final ArrayMap<String, SharedUserSetting> mSharedUsers =
            new ArrayMap<String, SharedUserSetting>();
    final ArrayMap<String, PackageSetting> mPackages =
            new ArrayMap<String, PackageSetting>();
  final ArraySet<String, SharedUserSetting> mSharedUsers =
            new ArraySet<String, SharedUserSetting>();
  • mShareUsers是一個以String類型的name為"key",ShareUserSetting對象為"value"的ArrayMap。
  • mPackages是一個以String類型的name為"key",PackageSetting對象為"value"的ArrayMap。
  • mSharedUsers 是一個以String類型的name(比如"android.uid.system")為"key",以SharedUserSetting 對象為"value"的HashMap

其中ShareUserSetting類繼承自GrantedPermissions ,內部包含一個ArraySet類型的packages ,這個packages保存了聲明相同的shareUserId的Package的權限設置信息(PackageSetting )通過上面的問題,我們知道PackageSetting繼承自PackageSettingBase,同時PackageSetting中保存著package的多種信息。

如下圖:


PackageSetting.png

上面提到了一個概念是SharedUserSetting,那么ShareUserSetting的作用什么是什么?那我們就來看下:

SharedUserSetting用來描述具有相同的sharedUserId的應用信息,它的成員變量packages保存了所有具有相同sharedUserId的應用信息引用。這些應用的簽名時相同的,所有只需要在成員變量signatures中保存一份。通過這個對象,Android運行時很容易檢索到某個應用擁有相同的sharedUserId的其他應用。其中應用的簽名保存在ShardUserSetting類的成員變量signatures中。

我們在看系統應用的AndroidManifest.xml中會發現

<manifest 
    xmlns:android="http://schemas.android.com/apk/res/android" 
    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" 
    package="com.android.settings" coreApp="true"
    android:sharedUserId="android.uid.system" 
    android:versionCode="1" 
    android:versionName="1.0" >
 </manifest>

shareUserId與UID相關,作用是:

  • 1、兩個或多個APK或者進程聲明了同一種sharedUserId的APK可以共享批次的數據,并且可以在運行在同一進程中(相當于進程是系統的用戶,某些進程可以歸為同一用戶使用,相當于Linux系統中的GroupId)
  • 2、通過聲明特定的sharedUserId,該APK所在的進程將被賦予指定的
      2.通過聲明特定的sharedUserId,該APK所在的進程將被賦予指定的UID,將被賦予該UID特定的權限。

在Settings中和用戶有關的還有兩個重要變量,即mUserIds 和mOtherUserIds

    private final ArrayList<Object> mUserIds = new ArrayList<Object>();
    private final SparseArray<Object> mOtherUserIds =
            new SparseArray<Object>();

他們都是以UID為索引,得到對應的ShardUserSetting對象。更多的關于Android系統中關于"用戶"的信息,在后面"用戶模塊"再單獨講解

4、在安裝一個APK的時候,APK是"死的",Android系統是怎么把它變成一個"活的"APP,他是怎么加載到內存中去的

這里就不得不提一下PackageParser這個類,這個類負責在APK文件安裝的時候,解析AndroidManifest。

在Android中,Settings提供可持續化的包信息管理,PackageSetting是一個存儲單元,表示一個pkg信息。我們在解析APK安裝包的時候,會用到PackageParser,在PackageParser里面有一個字段是PackageParse.Package。這個PackageParse.Package其實是對應的上面packages.xml里面的<package>標簽。同時PackageParse.Package也可以理解為pkg信息在內存中的一個實時信息,關機后變消失,重啟后重新生成,所以PackageParse.Package中的信息一致保證最新。PackageParse.Packag、Settings和PackageSetting三者的關系如下:

關系.png

Settings中保存了一個包名和PackagesSetting的映射表,PackageParse.Package中的mExtras引用指向了對應的PackageSetting實例,而PackageParse中保存了一個PackageParse.Package列表

PackageParse.png

從上到下,介紹如下:
1:PackageParser.Package對應一個apk完整的原始數據
2:PackageSetting包含一個PackageParser對象實例,說明它也對應一個apk包的數據,不同的是,它還包括apk相關配置數據,比如apk內部哪些component是被disable等。
3:Settings包含了PackageSetting對象列表,也就是說它包含了系統所有apk數據,還有就是PackageParser,顧名思義,負責APK數據解析
4:PackageManagerService是全局的包管理器

5、補充一點:

Settings里面的主要關聯關系如下圖:


主要關聯關系.png

二、PackageManagerService的抽象理解

上面說了很多,我們再上升一個高度,PackageManagerService到底應該怎么去理解它?

每一個組織結構,都有一套自己的管理機制,比如任何一家公司,都會存在下面三個元素:管理者(經理)、被管理者(員工)、管理機制(公司的規章制度及KPI考核等)。同理在Android的系統的世界里面,也有一家公司叫"包管理"。如果要研究Android的包管理機制,同樣可以從以下幾個角度來思考?

  • 管理者是誰,他的職責是什么?
  • 被管理者是誰,他的職責是什么?
  • 管理機制是什么,它是如何運轉的?

所謂包,其實就是一種文件的格式,比如APK包,JAR包等,在Android中存活著很多包,所有的應用程序都是APK包,很多構成Android運行環境的都是JAR包,還有一些以so為后綴的庫文件,包管理者很重要的一個職責就是識別不同的包,統一維護這些包的信息。當有一個包進入(安裝)或者離開(卸載)Android世界,都需要向包管理者申報,其他管理部分要獲取包的具體信息,也都需要向包管理者申請。

如同一家公司是由人與人協作工作的,不同包之間也需要進行協作。既然有協作,自然就有協作的規范,一個包可以干什么,不可以干什么,都需要有一個明確的范圍界定,這就是包管理中的權限設計。涉及到的內容非常廣泛,Android的權限管理、SELinux,都是包管理中權限設計的組成部分。同理Android的世界就像一個井然有序的一家公司,既有包管理部門,也有其他各種管理部門,比如電源管理部門,窗口管理部門等等。大家不僅各司其職,而且也有來往。比如在APK安裝到Activity的顯示,看著很簡單的過程,其實卻需要大量的管理部門參與進來,不斷地進行數據解析、封裝、傳遞、呈現,其內部機制十分復雜。

現在大家想一下上面三個問題的答案,我詳細大部分人的前兩個答案是一致的,管理者是PackageManagerService,被管理是各種"包",最后一個答案是各有千秋,這里是沒有標準答案的,希望大家能自己找到自己的答案。

三、PackageManagerService里面的數據結構

PackageManagerService涉及的數據結構非常多,在分析源碼時,很容易陷入各種數據結構之間的關系,難以自拔,以至于看不到包管理的全貌。我在這里簡單的總結了一下各個數據結構的職能如下:

  • PackageManangerService :包管理的核心服務
  • com.android.server.pm.Settings :所有包的管理信息
    • com.android.server.pm.PackageSetting :單一包的信息
    • com.android.server.pm.BasePermission :系統中已有的權限
    • com.android.server.pm.PermissionState :授權狀態

—————————————分隔符—————————————

  • PackageParser:包解析器
    • PackageParser.Package :解析得到的包信息
    • PackageParser.Component :組件的基類,其子類對應到AndroidManifest.xml中定義的不同組件
      • PackageParser.Activity 對應AndroidManifest.xml中定義<Activity>和<Receiver>標簽
      • PackageParser.Service :對應AndroidManifest.xml中定義<Service/>標簽
      • PackageParser.Provider :對應AndroidManifest.xml中定義<Provider/> 標簽
      • PackageParser.Instrumentation :對應AndroidManifest.xml中定義<Instrumentation/> 標簽
      • PackageParser.Permission :對應AndroidManifest.xml中定義<permission/> 標簽
      • PackageParser.PermissionGroup :對應AndroidManifest.xml中定義<permission-group/> 標簽
    • PackageLite :輕量的包信息
    • ApkLite :輕量級的APK信息
    • IntentInfo :組件所定義的<intent-filter/>信息,保存了每個<intent-filter/>節點的信息,是基類,它的子類是ActivityIntentInfo、ServiceIntentInfo和ProviderIntentInfo
      • ActivityIntentInfo :保存<activity/>和<Receiver/>節點下的<intent-filter/>節點
      • ServiceIntentInfo :保存<service/>節點下的<intent-filter/>節點
      • ProviderIntentInfo:保存<provider/> 節點下的<intent-filter/>節點
PackageParser的數據結構.png

—————————————分隔符—————————————

  • PackageInfo :跨進程傳遞的包數據,包解析時生成
    • PackageItemInfo :一個應用包內所有組件項和通用信息的基類。提供最基本的屬性集,如:label、icon、meta-data等。
      • ApplicationInfo:代表一個特定應用的基本信息,對應AndroidManifest里面的<application>
      • InstrumentationInfo:用作進行instrumentation的測試的片段,對應AndroidManifest里面的<instrumentation>
      • PermissionInfo:代表一個特定的權限,對應AndroidManifest里面的<permission/>
      • PermissionGroupInfo :一個特定的權限組,對應AndroidManifest里面的<permission-group/>
      • ComponentInfo:代表一個應用內組件(如activityInfo、serviceInfo、ProviderInfo)通用信息的基類。一般不會直接使用該類,它設計為了不同應用的組件共享統一的定義。
        • ActivityInfo :對應AndroidManifest.xml里面的注冊的<activity/>標簽和<receiver/>標簽。代表一個Activity或者receiver
        • ServiceInfo :對應AndroidManifest.xml里面的注冊的<service/>標簽。代表一個service
        • ProviderInfo :對應AndroidManifest.xml里面的注冊的<service/>標簽。代表一個Provider
  • Intent:根據特定的條件找到匹配的組件
  • IntentFilter :Intent過濾器
    • ResolveInfo
    • IntentResolver :Intent解析器,其子類用于不同組件的Intent解析
      保存了所有<activity/>或者<receiver/>節點信息。(Activity或者BroadcastReceiver信息就是用該自定義類保存的)
      • ActivityIntentResolver :保存所有<activity/>和<receiver/>節點信息。(Activity或者BroadcastReceiver信息就是用該自定義類保存的)
        保存了所有<service/>節點信息。(Service信息就是用該自定義類保存的)。
      • ServiceIntentResolver :保存了所有<service /> 節點信息。(Service信息就是用該自定義類保存的)
      • ProviderIntentReslover:保存了所有 <provider /> 節點信息
  • PackageHandler :包管理的消息處理器
    • HandlerParams :消息的數據載體
      • InstallParams : 用于APK的安裝
      • MeasureParams:用于查詢某個已安裝的APK占據存儲空間的大小(例如在設置程序中得到某個APK的緩存文件大小)
      • MoveParams :用于已安裝APK的位置移動
    • InstallArgs :APK的安裝參數
      • FileInstallArgs :針對是安裝在內部存儲的APK
      • AsecInstallArgs :針對安裝在SD卡上的APK
      • MoveInfoArgs : 移動APK
        這么龐大的數據結構,其各個數據結構的類圖如下:

數據結構.png

大圖地址3

四、PackageManagerService的三大過程組

如果大家想對Android系統有一個大致的了解,就必須要要了解PackageManagerService的三大流程

  • 1、包掃描的過程組:
    即Android將一個APK文件的靜態信息轉化為可以管理的數據結構
  • 2、包安裝的過程組:
    即包管理接納一個新成員的體現。
  • 3、包查詢的過程組:
    即Intent的定義和解析是包查詢的核心,通過包查詢服務可以獲取到一個包的信息

下面我們來一一進行簡單的介紹

(一)、包掃描過程組——即開機掃描過程

1、為什么要進行包掃描?

掃描目錄的目的:

掃描Android系統的幾個目標文件中的APK,從而建立合適的數據結構以及管理諸如Package信息、四大組件、授權信息等各種信息。抽象的地看,PackageManagerService像一個工廠,它解析實際的物理文件(APK文件),以及生成符合自己要求的產品。比如PackageManagerService將解析APK包中的AndroidManifest.xml,并根據其中聲明的Activity標簽來創建與此對應的對象,并保存到PackageParser.Package類型的變量中,然后通過PackageManagerService的scanPackageDirtyLI()方法將解析后的組件數據統計到PackageManagerService的本地變量中,用于管理查詢調用,當系統中任意某個APK的package發生改變時,如卸載,升級等操作都會更新package的統計數據到PackageManagerService,PackageManagerService正式基于擁有系統中所有的Package的信息才能勝任"包管理"這個管理者的角色。PackageManagerService的工作流程相對簡單,復雜的是其中用于保存各種信息的數據結構和它們的關聯關系,以及對應影響結果的策略控制(比如系統應用和普通應用)

2、包掃描過程組的不同理解

如果把包掃描過程組看成一件事,那么這件事就是:

調用PackageManagerService類的靜態方法main()方法來獲取PackageManagerService對象

如果把包掃描過程組看成兩件事,那么這兩件事就是

1、創建PackageManagerService對象
2、將PackageManagerService向ServiceManager注冊,即加入SMS,方便后續其他進程或者app通過ServiceManager獲得PackageManagerService服務。

如果把包掃描過程組看成三件事,那么這三件事是:

1、先讀取保存在packages.xml中記錄的系統關機前記錄所有安裝的APP信息, 將其保存在PackageManagerServiced中mSettings中的mPackages中。
2、掃描指定的若干目錄中的app,并把信息記錄在PackageManagerServiced的mPackages中。
3、最后上面的兩者進行對比,看是否有升級的APP,然后進行相關處理,最后寫入package.xml中

當然換一個角度,以掃描角度來看,也可以把包掃描分解成另外三個階段:

  • 掃描目標文件夾之前的準備工作
  • 掃描目標文件夾
  • 掃描目標文件夾之后的工作

如果把包掃描過程組看成四件事,那么這四件事是:

1、讀取響應的配置文件
2、優化APK和Jar包
3、掃描系統中所有安裝的應用
4、把掃描出的所有應用信息進行保存

如果把包掃描過程組劃分的更細,則我將其分為6大步驟

  • 1、變量初始化,包括mSettings,mInstaller,mPackageDexOptimizer等等
  • 2、讀取配置文件
  • 3、掃描系統Package,包含Dex優化
  • 4、保存掃描信息
  • 5、掃描非系統應用
  • 6、更新數據

如果把包掃描過程組劃分的更細,則我將其分為9大步
第一步:創建Settings對象,并調用其addSharedUserLPw()方法,保存ShareUserSetting信息
第二步:創建Installer對象,用于Native進程installd交互
第三步:創建ThreadHandler線程,并以其Looper為參數創建PackageHandler對象,用于程序的安裝和卸載
第四步:根據Installer對象和/data/user文件對象創建UserManager對象,用于多用戶管理
第五步:調用readPermissions()方法,從/system/etc/permissions目錄下的XML文件讀取權限信息
第六步:調用Settings對象的readLPw()方法解析/data/system目錄下的文件:
第七步:掃描/system/frameworks目錄以及BOOTCLASSPATHplatform.xml定義的系統目錄下的jar和APK文件是否需要dex優化,如果需要則調用Installer.dexopt()方法來發送消息給installd讓它優化;如果任意一個文件執行了dex優化操作,刪除/data/dalvik-cache目錄下的緩存文件
第八步:創建AppDirObserver對象監聽/system/frameworks、/system/app、/vendor/app(廠商定制)、/data/app、/data/app-private5個目錄,并調用scanDirLI()方法掃描其中的APK文件:
第九步:匯總上面掃描XML和APK得到的信息,并寫入文件;

3、如果把包掃描過程組劃分為"方法級"的流程,如下圖:

開機掃描流程.png

大圖地址3

4、溫馨提醒

  • 在packages.xml中<package>標簽記錄的APP的安裝信息。有獨立uid的APP,后面再反序列化的時候,會映射為PackageSetting對象,保存在mSettings的mPackages中;有sharedUid的APP,后面反序列化的時候,會映射為PendingPackage對象,保存在mSettings的mPendingPackages中。
  • 對于<share-user>標簽記錄的的share uid信息,封裝為SharedUserSetting對象,保存到mSettings里面的mSharedUser中,在此過程中遇到的uid和 shared uid都保存在mUserIds中,并讓每個uid指向與之關聯的PackageSetting對象,或者SharedUserSetting對象

5、小結

  • PackageManagerService是伴隨著系統進程啟動而啟動的,最終會構造一個PackageManagerService對象,此后,PackageManagerService將成為Android世界的包管理者,對外提供包的增、刪、改、查的操作
  • 在PackageManagerService的啟動過程中,最重要的是對所有靜態APK文件進行掃描,生成一個在內存中的數據結構Package,PackageManagerService實際上就是維護這所有在內存中的數據結構。已有的包的歷史信息會寫入磁石,PackageManagerService的Settings專門來管理寫入磁盤的包信息。
  • 所有包的信息掃描完成后,需要對應用進行授權,這是Android權限管理的一部分。隨著 Android版本的升級,授權機制略有區別,總體框架是:每個APK都可以聲明權限,并為權限設定保護級別,其他APK需要使用這些權限的時候,需要先申請,再由系統判定是否進行授權。

(二)、包安裝的過程組——即安裝一個新的APK

安裝一個APK的其大致流程如下:


大致流程.png

通常,安裝一個APK 通常分為以下4種方式

  • 安裝系統應用
  • 網絡下載應用安裝
  • ADB工具安裝
  • 第三方應用安裝

下面我們就依次介紹下

1、 安裝系統應用

系統的應用的安裝主要在PackageManagerService的main方法里面進行操作的

其順序如下:
第一步:PackageManagerService.main()初始化注冊
第二步:建立Java層的installer與C層的intalld的socket聯接
第三步:建立PackageHandler消息循環
第四步:調用成員變量mSettings的readLPw()方法恢復上一次的安裝信息
第五步:.jar文件的detopt優化
第六步:scanDirLI函數掃描特定目錄的APK文件解析
第七步:updatePermissionsLPw()函數分配權限
第八步:調用mSettings.writeLPr()保存安裝信息

2、 網絡下載應用安裝

其順序如下:
第一步:調用PackageManagerService的installPackage方法
第二步:上面的方法調用installPackageWithVerfication(),進行權限校驗,發送INIT_COPY的msg
第三步:進入PackageManagerService的doHandleMessage方法的INIT_COPY分支
第四步:成功綁定了com.android.defcontainer.DefaultContainerService服務,進入MCS_BOUND分支
第五步:里面調用PackageManagerService中內部抽象類HandlerParams的子類InstallParams的startCopy方法。
第六步:抽象類的HandlerParams的startCopy方法調用了HandlerParams子類的handleStartCopy和handlerReturnCode兩個方法
第七步:handlesStartCopy方法調用了InstallArgs的子類copyApk,它負責將下載的APK文件copy到/data/app
第八步:handleReturnCode調用handleReturnCode方法
第九步:調用PackageManagerService服務的installPackageLI(PackageParser.Package, int, int, UserHandle, String, String,PackageInstalledInfo)方法進行APK掃描。
第十步:上面的方法判斷是否APP應安裝,調用installNewPackageLI或replacePackageLI方法
第十一步:調用updateSettingsLI方法進行更新PackageManagerService的Settings
第十二步:發送what值為POST_INSTALL的Message給PackageHandler進行處理
第十三步:發送what值為MCS_UNBIND的Message給PackageHandler,進而調用PackageHandler.disconnectService()中斷連接

3、 ADB工具安裝

Android Debug Bridge (adb)是SDK自帶的管理設備的工具,通過ADB命令的方式也可以為手機或者模擬器安裝應用,其入口函數為pm.java

Android Debug Bridge (adb) 是SDK自帶的管理設備的工具,通過ADB命令行的方式也可以為手機或模擬器安裝應用,其入口函數源文件為pm.java

其順序如下:
第一步:pm.java的runInstall()方法
第二步:參數不對會調用showUsage方法,彈出使用說明
第三步:正常情況runInstall會調用mPm變量的installPackageWithVerification方法
第四步:由于pm.java中的變量mPm是PackageManagerService的實例,所以實際上是調用PackageManagerService的installPackageWithVerfication()方法
第五步:進入PackageManagerService的doHandleMessage方法的INIT_COPY分支
第六步:成功綁定了com.android.defcontainer.DefaultContainerService服務,進入MCS_BOUND分支
第七步:里面調用PackageManagerService中內部抽象類HandlerParams的子類InstallParams的startCopy方法。
第八步:抽象類的HandlerParams的startCopy方法調用了HandlerParams子類的handleStartCopy和handlerReturnCode兩個方法
第九步:handlesStartCopy方法調用了InstallArgs的子類copyApk,它負責將下載的APK文件copy到/data/app
第十步:handleReturnCode調用handleReturnCode方法
第十一步:調用PackageManagerService服務的installPackageLI(PackageParser.Package, int, int, UserHandle, String, String,PackageInstalledInfo)方法進行APK掃描。
第十二步:上面的方法判斷是否APP應安裝,調用installNewPackageLI或replacePackageLI方法
第十三步:調用updateSettingsLI方法進行更新PackageManagerService的Settings
第十四步:發送what值為POST_INSTALL的Message給PackageHandler進行處理
第十五步:發送what值為MCS_UNBIND的Message給PackageHandler,進而調用PackageHandler.disconnectService()中斷連接

ADB安裝.png
4、 第三方應用安裝

第一步:調用PackageInstallerActivity的onCreate方法初始化安裝界面
第二步:初始化界面以后調用initiateInstall方法
第三步:上面的方法調用startInstallConfirm方法,彈出確認和取消安裝的按鈕
第四步:點擊確認按鈕,打開新的activity:InstallAppProgress
第五步:InstallAppProgress類初始化帶有進度條的界面之后,調用PackageManager的installPackage方法
第六步:PackageManager是PackageManagerService實例,所以就是調用PackageManagerService的installPackage方法
第七步:調用PackageManagerService的installPackage方法
第八步:上面的方法調用installPackageWithVerfication(),進行權限校驗,發送INIT_COPY的msg
第九步:進入PackageManagerService的doHandleMessage方法的INIT_COPY分支
第十步:成功綁定了com.android.defcontainer.DefaultContainerService服務,進入MCS_BOUND分支
第十一步:里面調用PackageManagerService中內部抽象類HandlerParams的子類InstallParams的startCopy方法。
第十二步:抽象類的HandlerParams的startCopy方法調用了HandlerParams子類的handleStartCopy和handlerReturnCode兩個方法
第十三步:handlesStartCopy方法調用了InstallArgs的子類copyApk,它負責將下載的APK文件copy到/data/app
第十四步:handleReturnCode調用handleReturnCode方法
第十五步:調用PackageManagerService服務的installPackageLI(PackageParser.Package, int, int, UserHandle, String, String,PackageInstalledInfo)方法進行APK掃描。
第十六步:上面的方法判斷是否APP應安裝,調用installNewPackageLI或replacePackageLI方法
第十七步:調用updateSettingsLI方法進行更新PackageManagerService的Settings
第十八步:發送what值為POST_INSTALL的Message給PackageHandler進行處理
第十九步:發送what值為MCS_UNBIND的Message給PackageHandler,進而調用PackageHandler.disconnectService()中斷連接

流程.png

點擊放大查看高清無碼大圖

小結:

  • 1、安裝和卸載都是通過PackageManager,實質上是實現了PackageManagerService來完成具體的操作,所有細節和邏輯均可以在PackageManagerService中跟蹤查看。
  • 2、所有安裝方式殊途同歸,最終就是回到PackageManagerService中,然后調用底層本地代碼的installd來完成的。
  • 3、APK的安裝過程主要分為以下幾步:
    - 拷貝到apk文件到指定目錄
    - 解壓縮apk,拷貝文件,創建應用的數據目錄
    - 解析apk的AndroidManifest.xml文件
    - 向Launcher應用申請添加創建快捷方式

(三)、包查詢的過程組——即解析Intent并找到其配備的組件

1、包管理是以什么樣的形式對外提供服務那?

在寫應用程序時,我們通常會利用應用自身的上下文環境Context來獲取包管理服務,如下:

// 獲取一個PackageManager的對象實例
PackageManager pm = context.getPackageManager();
// 通過PackageManager對象獲取指定包名的包信息
PackageInfo pi = pm.getPackageInfo("com.android.contacts", 0);

這么一段簡單的代碼,其實蘊含很多的深意

  • 1、上面已經講解過了PackageManagerService和其管理的各種數據結構,都是運行在系統進程之中。在應用進程中獲取的PackageManager對象,只是PackageManagerService在應用進程中的一個代理,不同的應用進程都有不同的代理,意味著不同應用進程中的PackageManager是不同的,但是管理者PackageManagerService有且只有一個
  • 2、運行在應用進程中的PackageManager要與運行在系統進程中的PackageManagerService進行通信,通信手段是Android中最常見的Binder機制。因此會有一個IPackageManager.aidl文件,用于描兩者通信的接口。另外,應用進程中的PackageInfo對象。PackageInfo其實就是由系統進程傳遞到應用進程的對象
IPackageManager.png

PackageManagerService作為包管理的最核心組成部分,伴隨著系統的啟動而創建,并一直運行系統進程中。當應用程序需要獲取包管理服務時,會生成一個PackageManager對PackageManagerService進行通信。在包解析時就會生成包信息,即XXInfo這一類數據結構,PackageManagerService將這些數據傳遞給需要的應用進程。

管理者對內設計了復雜的管理機制,對外封裝了簡單的使用接口。這種設計在Android中大量出現,比如ActivityManagerService、WindowManagerService、PowerManagerService等,基本所有的系統服務都遵循這種設計規范。對于應用程序而言,不需關心管理者的實現原理,只需要理解接口的使用場景

Android在全局定義了IPackageManager,接口,描述了包管理者對外提供的功能,運行在系統進程中的PackageManagerService實現了IPackageManager接口,作為包管理的服務端,客戶端通過IPackageManager接口請求包服務。為了方便客戶端進行包服務,Android做了多層的封裝。應用進程作為客戶端,通過PackageManager便可使用包服務,客戶端實際存在的對象是ApplicationPackageManager,它封裝了IPackageManager的所有接口。在應用進程來看,客戶端和服務端的概念是模糊的,明確的只有運行環境的概念,即Context。包服務就存在于應用進程的運行環境中,需要時直接拿出來使用即可。

“運行環境(Context)”是Android的設計哲學之一,Android有意弱化進程,強化運行環境,這是面向應用開發者的設計。運行環境是什么并不是一個很好回到的問題。可以將其類比為我們的工作環境,當我們需要辦公設備時,只需要向管理部門申請,并不需要關心辦公設備如何采購,辦公設備對一般的工作人員而言,就像是工作環境中天然存在的東西。

2、包管理的具體服務形式——Intent的解析:

在Android中,使用Intent來表達意圖,最終會有一個響應者。當系統產生一個Intent后,如何找到它的響應者?這需要對Intent進行解析。作為所有包信息管理者的中樞,PackageManagerService自然有義務承擔解析Intent的責任。要解析Intent,就需要了解Intent的結構,標識了一個Intent身份的信息由兩部分構成:

  • 主要信息:主要信息Action和Data。Action用于表明Intent所要執行的操作,譬如ACTION_VIEW,ACTION_EDIT;Data用于表明執行操作的數據,譬如聯系人數據,數據是以URI來表達的。再舉兩個Action和Data成對出現的例子:
    - ACTION_VIEW:content://contacts/people/1 :標識查看聯系人數據庫中,ID為1的聯系人信息
    - ACTION_DIAL:tel:119 :表達撥打電話給119
    上面兩個例子的URI并不一樣,完整的URI格式為scheme://host:port/path
  • 次要信息:除了主要標記的信息,Intent還可以附加很多額外的信息,比如Category,Type,Componten和Extra:
    - Category:標識Intent的類別,譬如CATEGROY_LAUNCHER標識對屬于左面的圖標這一類的對象執行操作
    - Type:標識Intent所有操作的數據類型,就是MIMEType,譬如要操作PNG圖片,那Type就可以設置為png
    - Component:標識Intent要操作的對象
    - Extra:標識Intent所傳遞的數據
    上述這些數據都實現了Parcelable接口。

之所以Intent信息的主次之分,是因為解析Intent的規則需要有一個依據,主要信息是最能表達意圖的,而次要信息則是解析規則的一個補充。這就像大家在做自我介紹的時候,總是先說姓名、籍貫這些主要的信息,再額外補充愛好、特長這些次要信息,這樣一來在和其他人交朋友的時候,其他人就可以先根據籍貫、姓名鎖定我。如果我們只介紹愛好、特長,那么別人鎖定的范圍就比較廣,因為有相同愛好或者特長的人比較多。

Intent身份信息,其實就是Android的一種設計語言,譬如"打電話給119",只需要發出Action為ACTION_DIAL,URI為“tel:119”的Intent即可,剩下的就交給Android系統去理解這個意圖。任何組件只要按照規則發生,都會被Android系統正確的理解。

而根據Intent的方式不同,可以將Intent分為兩類:

  • 顯示(Explicit):明確指明需要誰來響應Intent。這一類Intent的解析過程比較簡單
  • 隱式(Implicit):有系統找出合適的目標來響應Intent。這一類Intent的解析過程比較復雜,由于目標明確,所以需要經過層層篩選才能找到最合適的響應者。

之所以Intent有顯式和隱式之分,是因為解析Intent的方式不同,如果我指定要和某某交朋友,那么發出的這一類請求,就是顯式Intent;如果沒有指定交朋友的對象,只是說找到跟我愛好相同的人,那發出的這一類請求,就是隱式的Intent。對待這兩種Intent顯然有不同的解析方式。

如果和"運行環境Context"一樣,Intent也是面向應用程序設計,同樣是弱化了了進程的概念。應用程序只需表明"我想要什么",不需要關心索要的東西在什么地方,如何找到索要的東西。Intent是Android通信的手段之一,可以承載要傳遞的信息,至于信息怎么從發起進程傳遞到目標進程,應用程序可以毫不關心。

Intent最后一個響應者是一個Android組件,Android組件都可以定義IntentFilter,前面說了包解析器的時候,說到了每一個Component類中都有一個IntentInfo對象的數組,而IntentInfo則是IntentFilter的子類。既然一個Android組件可以定義多個IntentFilter,那么Intent想要匹配到最終的組件,則需要通過組件所定義的所有IntentFilter:

IntentFilter.png

多個IntentFilter之間是"或"的關系,哪怕其他所有IntentFilter都匹配失敗,只要有一個IntentFilter通過,最終Intent還是找到了可以響應的組件。

每一個IntentFilter就像是一個定義了白名單規則的過濾器,只有滿足白名單的要求才會放行。Intent的過濾規則,其實就是針對Intent的身份信息的匹配規則,當Intent的身份信息與IntentFilter所規定的要求匹配上,則允許通過;否則,Intent就被過濾掉了。IntentFilter的過濾規則包含以下三個方面:

  • Action:每個IntentFilter可以定義零個或多個<action標簽>,如果Intent想要通過這個IntentFilter,則Intent所轄的Action需要匹配其中至少一個。
  • Category:每一個IntentFilter可以定義零個或者多個<category>標簽,如果Intent想要通過這個IntentFilter,則Intent所轄的Categroy必須是IntentFilter所定義的Category的子集,才能通過IntentFileter。譬如Intent設置了兩個Category:CATEGORY_LAUNCHER和CATEGORY_MAIN,只有那些至少定義了兩項Category的IntentFilter,才會放行該Intent。啟動Activity時,會為Intent設置默認的Category,即CATEGORY_DEFAULT。目標Activity需要添加一個category偽CATEGORY_DEFAULT的IntentFilter來匹配這一類隱式的Intent。
  • Data:每一個IntentFilter可以定義零個或多個<data>,數據可以通過類型(MIMEType)和位置(URI)來描述,如果Intent想要通過這個IntentFilter,則Intent所轄的Data需要匹配其中至少一個。

在了解Intent的身份信息和IntentFilter的規則定義之后,就可以介紹Intent解析的過程了,PackageManagerService有四大組件的Intent解析器,分別是ActivityIntentResolver用于解析發往Activity或Broadcast的Intent,ServiceIntentResolver用于解析發往Service的Intent,ProviderIntentResolver用于解析發往Provider的Intent,系統每收到一個Intent的解析請求時,就會使用對應的解析器,他們都是IntentResolver的子類。

IntentResolver的職能就是解析Intent,它包含了所有IntentFilter,同時有一個重要的成員函數queryIntent(),接受Intent作為參數,返回查詢結構:一個ResolveInfo對象的數據。因為可能有多個組件來響應一個Intent,所以返回結果是一個數組。可想而知,該函數就是針對輸入的Intent,按照前面所述的過濾規則,逐個與IntentFilter進行匹配,直到找到最終的響應者,便加入返回結果的列表。

  • ResolveInfo是最終的Intent解析結果的數據結構,并不復雜,就是各類組件信息的一個包裝。需要注意的是,其中的組件信息ActivityInfo、ProviderInfo、ServiceInfo只有一個不為空,這樣就可以區分不同組件的解析結果。
  • 解析"顯式"的Intent,如果Intent中有設置Component,則說明已經顯式的指明由誰來響應Intent。根據Component可以獲取到對應的 ActivityInfo,再封裝成ResolveInfo便可以作為結果返回了。
  • 解析"隱式"的Intent,該處邏輯比較復雜,后面講解Activity的啟動流程時再詳細講解。

3、小結

  • 包管理對外提供服務的形式基于Bidner機制,服務端是運行在系統進程中的PackageManagerService,包查詢服務是使用范圍很廣的一類服務,很多其他服務都需要用到包信息,都是通過PackageManagerService獲取的。
  • 包查詢服務的核心是Intent解析,PackageManagerService中實現了不同組件的解析器。針對一個輸入的Intent,解析得到可以響應的組件。Android為此設計了IntentFilter機制,定義了Intent匹配規則,最終解析實現在IntentResolver.queryIntent()函數中

五、PackageManagerService的體系結構

image.png

大圖地址

六、總結

本片文章主要講解了包管理的三個大的過程:包掃描過程、包查詢過程、包安裝過程,其中重點的是包安裝的過程。我們再來復習一下:

APK的安裝流程如下:

復制APK安裝包到/data/app目錄下,解壓縮并掃描安裝包,向資源管理器注入APK資源,解析AndroidManifest文件,并在/data/data目錄下創建對應的應用數據目錄,然后針對Dalvik/ART環境優化dex文件,保存到dalvik-cache目錄,將AndroidManifest文件解析出的組件、權限注冊到PackageManagerService并發送廣播

具體流程如下:

├── PMS.installPackage()
    └── PMS.installPackageAsUser()
         |傳遞 InstallParams 參數
        PackageHandler.doHandleMessage().INIT_COPY
         |
        PackageHandler.doHandleMessage().MCS_BOUND
         ├── HandlerParams.startCopy()
         │    ├── InstallParams.handleStartCopy()
         │    │    └──InstallArgs.copyApk()
         │    └── InstallParams.handleReturnCode()
         │         └── PMS.processPendingInstall()
         │              ├── InstallArgs.doPreInstall()
         │              ├── PMS.installPackageLI()
         │              │    ├── PackageParser.parsePackage()
         │              │    ├── PackageParser.collectCertificates()
         │              │    ├── PackageParser.collectManifestDigest()
         │              │    ├── PackageDexOptimizer.performDexOpt()
         │              │    ├── InstallArgs.doRename()
         │              │    │    └── InstallArgs.getNextCodePath()
         │              │    ├── replacePackageLI()
         │              │    │    ├── shouldCheckUpgradeKeySetLP()
         │              │    │    ├── compareSignatures()
         │              │    │    ├── replaceSystemPackageLI()
         │              │    │    │    ├── killApplication()
         │              │    │    │    ├── removePackageLI()
         │              │    │    │    ├── Settings.disableSystemPackageLPw()
         │              │    │    │    ├── createInstallArgsForExisting()
         │              │    │    │    ├── deleteCodeCacheDirsLI()
         │              │    │    │    ├── scanPackageLI()
         │              │    │    │    └── updateSettingsLI()
         │              │    │    └── replaceNonSystemPackageLI()
         │              │    │         ├── deletePackageLI()
         │              │    │         ├── deleteCodeCacheDirsLI()
         │              │    │         ├── scanPackageLI()
         │              │    │         └── updateSettingsLI()
         │              │    └── installNewPackageLI()
         │              │         ├── scanPackageLI()
         │              │         └── updateSettingsLI()
         │              ├── InstallArgs.doPostInstall()
         │              ├── BackupManager.restoreAtInstall()
         │              └── sendMessage(POST_INSTALL)
         │                   |
         │                  PackageHandler.doHandleMessage().POST_INSTALL
         │                   ├── grantRequestedRuntimePermissions()
         │                   ├── sendPackageBroadcast()
         │                   └── IPackageInstallObserver.onPackageInstalled()
         └── PackageHandler.doHandleMessage().MCS_UNBIND
              └── PackageHandler.disconnectService()

至此,整個APK安裝流程詳解全部說完,謝謝!

官人[飛吻],你都把臣妾從頭看到尾了,喜歡就點個贊唄(眉眼)!!!!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,892評論 2 372