【项目】Android 预置第三方应用可卸载功能的实现

原生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 文件中;

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章