原生Android 的狀況是:
手機會預置一些第三方APP ,用戶不可刪除。
現在實現用戶可刪除的預置應用的功能
1.修改預置應用安裝路徑:
1.1 /system 下創建/third_app 文件夾
1.把預留應用放在system/third-app下;
2.第一次開機 ,PKMS初始化掃描data/app之前,這些應用源文件從 /system/third-app copy到 data/app下;
恢復出廠設置僅僅就格式化/data 分區,不會格式化/system 分區,回覆出廠設置後第一次就直接copy到/system/app下,因此恢復出廠設置後仍然有效
1.1.1 修改預置第三放APP Android.mk
include $(CLEAR_VARS)
LOCAL_MODULE := SogouInput
LOCAL_SRC_FILES := $(LOCAL_MODULE).apk
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
LOCAL_CERTIFICATE := PRESIGNED
LOCAL_DEX_PREOPT := false
LOCAL_MODULE_PATH := $(TARGET_OUT)/third_app
include $(BUILD_PREBUILT)
1.1.2 添加copy 代碼
//install the third apps when system is first boot
private void installThirdApps(){
//the source directory not exists
File storeDir = new File("/system/third_app");
if(!storeDir.exists()){
Log.e(TAG,"/system/third_app is not exist");
return;
}
//get the apk files in /system/third_app
String apkFilesNames[] = storeDir.list();
if(apkFilesNames == null){
Log.e(TAG,"apk file name is null");
return;
}
//copy the apk files to /data/app
boolean installSucc = false;
for(int i = 0; i < apkFilesNames.length; i++){
//Uri srcFileUri = Uri.parse(storeDir+"/"+apkFilesNames[i]);
File srcFile = new File("/system/third_app",apkFilesNames[i]);
Log.e(TAG,"srcFile="+srcFile);
File destFile = new File("/data/app",apkFilesNames[i]);
Log.e(TAG,"destFile="+destFile.toString());
boolean installResult = copyThirdApps(srcFile,destFile);
if(!installResult){
Log.d(TAG,"install failed");
return;
}
}
}
/**
* File copy function.
* It will be used when installThirdApps
* @param srcFile just like '/system/third_app/***.apk'
* @param destDir just like '/data/app/***.apk'
* @return
*/
private boolean copyThirdApps(File srcFile, File destDir) {
//do some check actions
if (srcFile == null || destDir == null || !srcFile.exists()) {
Log.e(TAG, "invalid arguments for movePreinstallApkFile()");
Log.e(TAG, "move " + srcFile + " to " + destDir + " failed");
return false;
}
//create new file
try{
destDir.createNewFile();
}catch(Exception e){
Log.e(TAG, "create file faild! due to:" + e);
return false;
}
//set permission
try{
Runtime.getRuntime().exec("chmod 644 "+destDir.getAbsolutePath());
}catch(Exception e){
Log.e(TAG, "chmod file faild! due to:"+e);
}
//do copy
try{
boolean ret = FileUtils.copyFile(srcFile,destDir);
if(!ret){
Log.e(TAG,"copy file faild!");
return false;
}
}catch(Exception e){
Log.e(TAG, "copy file faild! due to:"+e);
}
return true && destDir.exists();
}
1.1.3 PKMS 中插入copy代碼
if (!mOnlyCore) {
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
SystemClock.uptimeMillis());
if(isFirstBoot()){//判斷第一次開機
Log.i(TAG, "It's first boot, install the third apps");
installThirdApps();//安裝三方應用(copy到data/app下)
}
scanDirLI(mAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);
scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK,
scanFlags | SCAN_REQUIRE_KNOWN, 0);
在PKMS的構造函數,開始處理非系統應用的時候,但是一定要在掃描data/app之前,這樣才能後面掃描到data/app這些複製進去的app,纔會第一次開機安裝成功
=====================功能優化=========================
以上方法會有如下問題:
1.調用接口安裝的,可能Launcher啓動後還沒安裝完。
2.而copy到data/app下又會有兩份apk問題。
現在提新的方法:
1.就是放在system/third_app下,開機的時候直接掃描這個目錄;
2.我們在data/system下建一個xml文件,當應用卸載的時候,我們再xml上記錄該應用被卸載了,當再次開機的時候,掃描到該應用就直接跳過;
3.恢復出廠設置時data目錄重置,xml文件被刪除。system/third_app又會被重新掃描;
4.所有的apk就全部安裝上了,而當我們卸載時,因爲system/third_app的權限問題,PKMS刪除不了,正好恢復出廠設置的時候可以重新恢復;
簡單的邏輯就是把/system/third_app 下的APP專門由PKMS.mVendorSettings.mVendorPackages 進行管理(與PKMS.mSettings.mPackages 對應),但是,PKMS.mSettings.mPackages仍然包含PKMS.mVendorSettings.mVendorPackages中的PKG
1.增加“services/core/java/com/android/server/pm/VendorSettings.java”
package com.android.server.pm;
... ...
final class VendorSettings {
... ...
private final File mSystemDir; //"/data/system"
private final File mVendorSettingsFilename; //"/data/system/custom-packages.xml"
private final File mVendorBackupSettingsFilename; //“/data/system/custom-packages-backup.xml”
final HashMap<String, VendorPackageSettings> mVendorPackages =
new HashMap<String, VendorPackageSettings>();
VendorSettings() {
this(Environment.getDataDirectory());
}
VendorSettings(File dataDir) {
mSystemDir = new File(dataDir, "system");;
mSystemDir.mkdirs();
FileUtils.setPermissions(mSystemDir.toString(),
FileUtils.S_IRWXU|FileUtils.S_IRWXG
|FileUtils.S_IROTH|FileUtils.S_IXOTH,
-1, -1);
//=1= : 創建"/data/system/custom-packages.xml 和 其備份文件,類似/data/system/package-list.xml"
mVendorSettingsFilename = new File(mSystemDir, "custom-packages.xml");
mVendorBackupSettingsFilename = new File(mSystemDir, "custom-packages-backup.xml");
}
// =2= : mVendorPackages 中增加新的pkg
void insertPackage(String packageName, boolean installStatus) {
VendorPackageSettings vps = mVendorPackages.get(packageName);
if (vps != null) {
vps.setIntallStatus(installStatus);
} else {
vps = new VendorPackageSettings(packageName, installStatus);
mVendorPackages.put(packageName, vps);
}
}
// =3= :
void setPackageStatus(String packageName, boolean installStatus) {
VendorPackageSettings vps = mVendorPackages.get(packageName);
... ...
vps.setIntallStatus(installStatus);
... ...
}
// =4= : mVendorPackages 中減去的pkg
void removePackage(String packageName) {
... ...
mVendorPackages.remove(packageName);
... ...
}
void readLPw() {
// 1.讀取/system/etc/custom-packages.xml ,將其中的VendorPackageSettings 信息 讀取在PKMS.mVendorSettings.mVendorPackages數組中.
}
void writeLPr() {
// 1.將PKMS.mVendorSettings.mVendorPackages 同步到/system/etc/custom-packages.xml 中.
}
}
2.增加/services/core/java/com/android/server/pm/VendorPackageSettings.java
package com.android.server.pm;
final class VendorPackageSettings {
final String mPackageName;
boolean mIntallStatus = true;
VendorPackageSettings(String packageName) {
this.mPackageName = packageName;
}
VendorPackageSettings(String packageName, boolean intallStatus) {
this.mPackageName = packageName;
this.mIntallStatus = intallStatus;
}
boolean getIntallStatus() {
return mIntallStatus;
}
void setIntallStatus(boolean mIntallStatus) {
this.mIntallStatus = mIntallStatus;
}
String getPackageName() {
return mPackageName;
}
}
3.PackageManagerService 中的修改:
3.1. 增加PKMS.mVendorSettings 成員變量 和PKMS.mVendorPackages 成員變量
public class PackageManagerService extends IPackageManager.Stub {
... ...
+ final HashMap<String, PackageParser.Package> mVendorPackages =
+ new HashMap<String, PackageParser.Package>();
... ...
final Settings mSettings;
... ...
+ final VendorSettings mVendorSettings;
boolean mRestoredSettings;
3.2. PKMS 構造函數
public class PackageManagerService extends IPackageManager.Stub {
... ...
mSettings = new Settings(mPackages);
//(1): 初始化PKMS.mVendorSettings
+ mVendorSettings = new VendorSettings();
... ...
mRestoredSettings = mSettings.readLPw(this, sUserManager.getUsers(false),
mSdkVersion, mOnlyCore);
//(2): 調用VendorSettings.readLPw,初始化mVendorSettings.mVendorPackages
+ mVendorSettings.readLPw();
... ...
... ...
scanDirLI( "/vendor/overlay" , ...);
scanDirLI( "/system/framework" , ...);
scanDirLI( "/system/priv-app" , ...);
scanDirLI( "/system/app" , ...);
//(3): PKMS 開始掃描/system/app等APP 時,同樣調用scanDirLI掃描/system/third_app下的app
+ final File operatorAppDir = new File("/system/third_app");
+
+ //Add PARSE_IS_VENDOR for operator apps
+ final File[] operatorAppFiles = operatorAppDir.listFiles();
+ for (File file : operatorAppFiles) {
+ scanDirLI(file, PackageParser.PARSE_IS_VENDOR, scanFlags, 0);
+ }
... ...
//(4):1. PKMS.mVendorSettings.mVendorPackages包含解析custom-packages.xml 中記錄的所有third_app信息 ;
//(4):2. PKMS.mVendorPackages 包含所有scanDirLI() 解析/system/third_app 新出來的PackageParser.Package 對象
//(4):3. PKMS.mVendorPackages如果沒有PKMS.mVendorSettings.mVendorPackages中的記錄,說明現實中的third_app 比 custom-packages.xml 中的少,需要跟新custom-packages.xml
+ Iterator<VendorPackageSettings> vpsit = mVendorSettings.mVendorPackages.values().iterator();
+ while (vpsit.hasNext()) {
+ VendorPackageSettings vps = vpsit.next();
+ final PackageParser.Package scannedVendorPkg = mVendorPackages.get(vps.getPackageName());
+ if (scannedVendorPkg == null) {
+ vpsit.remove();
+ Slog.w(TAG, "Vendor package: " + vps.getPackageName()
+ + " has been removed from system");
+ }
+ }
... ...
mSettings.writeLPr();
//(5): 1.PKMS 初始化完成,調用 mSettings 和 mVendorSettings 的writeLPr()函數,分別將mSettings.mPackages 同步到package-list.xml 和 將mVendorSettings.mVendorPackages 同步到custom-packages.xml
+ mVendorSettings.writeLPr();
}
4. 掃描/system/third_app 相關的修改
4.1 增加PackageParser.PARSE_IS_VENDOR 這個flags
public final static int PARSE_IS_VENDOR = 1<<10;
代表 pkg 是 /system/third_app
4.2 scanPackageLI () 的修改
private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,long currentTime, UserHandle user) throws PackageManagerException {
... ...
PackageParser pp = new PackageParser();
... ...
+ //If the newly installed package is vendor app,
+ //add or update it in vendor settings
+ if ((parseFlags & PackageParser.PARSE_IS_VENDOR) != 0) {
+ mVendorPackages.put(pkg.packageName,true);
+ }
+
+ //Check whether we should skip the scan of current package
+ //We should only check vendor packages
+ if ((parseFlags & PackageParser.PARSE_IS_VENDOR) != 0) {
+ VendorPackageSettings vps = +
+ mVendorSettings.mVendorPackages.get(pkg.packageName);
+ if (vps != null) {
+ if (!vps.getIntallStatus()) {
+ //Skip the vendor package that was uninstalled by user
+ Log.i(TAG, "Package " + vps.getPackageName()+ " skipped due to + +
+ uninstalled");
+ return null;
+ }
+
+ }
+ }
4.2.1 在scanDirLI () 中,將有 PackageParser.PARSE_IS_VENDOR 標誌位的放入PKMS.mVendorSettings.mVendorPakages中;
4.2.2 scanDirLI 正在掃描的third_pkg 也在mVendorPackages中,但是“uninstalled”狀態,就返回return,結束安裝 (/system/third_app 下的資源文件不可能被刪除,當一個third_app 被刪除時,VendorPackageSettings.mIntallStatus = false 代表被已經刪除,放在 (VendorPackageSettings) PKMS.mVendorSettings.mPackages[i].mIntallStatus :
1.scanDirLI 在掃描/system/third_app/deleted_third_app.apk 時,就直接return null ,此時,而PKMS.mPackage 的更新是在scanPackageDirtyLI中,所以PKMS.mPackages<PackageParser.package> 中是沒有deleted_third_app.apk對應的PackageParser.package對象的,所以package-list.xml 中不會有記錄;
2.
)
4.3 scanPackageDirtyLI ()
private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, int parseFlags, ... ) {
... ...
synchronized (mPackages) {
// (1) : scanPackageDirtyLI () 中開始進行PKMS.mSettings.mPackage<PackageSettings> 的更新
// Add the new setting to mSettings
mSettings.insertPackageSettingLPw(pkgSetting, pkg);
//(2) : PKMS.mPackages 的更新
// Add the new setting to mPackages
mPackages.put(pkg.applicationInfo.packageName, pkg);
//(3) : 清楚PKMS.mSettings.mPackages 多餘項
// Make sure we don't accidentally delete its data.
final Iterator<PackageCleanItem> iter = mSettings.mPackagesToBeCleaned.iterator();
while (iter.hasNext()) {
PackageCleanItem item = iter.next();
if (pkgName.equals(item.packageName)) {
iter.remove();
}
}
}
... ...
//(4) : 更新PKMS.mVendorSettings.mVendorPackage[i].mInstallStatus = true ,表示已經安裝
+ //If the newly installed package is vendor app,
+ //add or update it in vendor settings
+ if ((parseFlags & PackageParser.PARSE_IS_VENDOR) != 0) {
+ mVendorSettings.insertPackage(pkg.packageName,true);
+ }
... ...
}
在PKMS.mPackages 和 PKMS.mSettings.mPackages 進行同步後,更新PKMS.mVendorSettings.mVendorPackage[i].mInstallStatus = true ,表示已經安裝狀態
5. 刪除third_app 的處理:
在removePackageDataLI() 中處理:
private void removePackageDataLI(PackageSetting ps,
int[] allUserHandles, boolean[] perUserInstalled,
PackageRemovedInfo outInfo, int flags, boolean writeSettings) {
... ...
final PackageSetting deletedPs;
+ final VendorPackageSettings delVps;
... ...
deletedPs = mSettings.mPackages.get(packageName);
+ delVps = mVendorSettings.mVendorPackages.get(packageName);
... ...
mHandler.post(new Runnable() {
@Override
public void run() {
// This has to happen with no lock held.
killApplication(deletedPs.name, deletedPs.appId,
KILL_APP_REASON_GIDS_CHANGED);
}
});
... ...
+ if (delVps != null) {
+ //If the deleted package is vendor package
+ //remove it from vendor settins
+ mVendorSettings.setPackageStatus(packageName, false);
+ mVendorSettings.writeLPr();
+ }
}
注意:PKMS.mPackages中本身是包含需要刪除的deleting_third_app.apk 的,所以沒有必要專門爲PKMS.mVendorPackages 做特殊的刪除流程!
在刪除流程中,僅僅只需要設置PKMS.mVendorPackages[i].mInstalStatus = false;
=====================================================
***知識點***:
1. scanDirLI(file, PackageParser.PARSE_IS_VENDOR, scanFlags, 0) :
pkg被scanDirLI掃描時 加上的PackageParser.PARSE_IS_VENDOR的flag,system 的app 也是這個時候加上的flag;
注意:scanDirLI是掃描PKG 最開始的函數,scanDirLI (... , PackageParser.PARSE_IS_VENDOR, ...) 此時添加了PackageParser.PARSE_IS_VENDOR 這個flag ,那麼後續掃描都有在PackageParser.PARSE_IS_VENDOR 這個flag 的基礎上繼續添加新的flag。
2.這幾個路徑下是system_app
// /vendor/overlay
scanDirLI(vendorOverlayDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags | SCAN_TRUSTED_OVERLAY, 0);
// /system/framework
scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR
| PackageParser.PARSE_IS_PRIVILEGED,
scanFlags | SCAN_NO_DEX, 0);
// /system/priv-app
scanDirLI(privilegedAppDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR
| PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0);
// /system/app
scanDirLI(systemAppDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
// /vendor/app
scanDirLI(vendorAppDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
scanDirLI(oemAppDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
1./vendor/overlay
2./system/framework
3./system/priv-app
4./system/app
5./vendor/app
以上目錄下的app 都是flags = system_app
3. 幾個存儲信息的成員變量的比較:
3.1 PKMS.mPackages<PackageParser.package>
PKMS.mVendorPackages<PackageParser.package>
3.2 PKMS.mSettings.mPackages<PackageSettings>
PKMS.mVendorSettings.mVendorPackages<VendorPackageSettings>
PKMS.mPackage是scanDirLI () 掃描了XXXX.apk 後生成的 成員是PackageParser.package 的數組,是當前系統中app 的信息;
PKMS.mSettings.mPackages是Settings 調用readPlw 讀取/data/system/package-list.xml 後生成的文件解析信息;
PKMS 初始化最後會將PKMS.mPackage 同步到PKMS.mSettings.mPackages 中並最後寫進package-list.xml 文件中;