Android App安装弹窗显示流程

作者: 巴菲伟 | 来源:发表于2022-05-16 21:56 被阅读0次

    一、APP的安装

    1、常见安装方式

    • 系统应用和预制应用安装――开机时完成,没有安装界面,在PKMS的构造函数中完成安装

    • 网络下载或第三方应用安装――调用PackageManager.installPackages(),有安装界面。

    • ADB工具安装――没有安装界面,它通过启动pm脚本的形式,然后调用com.android.commands.pm.Pm类,之后调用到PMS.installStage()完成安装。


      image.png

    2、APK的签名校验理解

    V1签名apk-signature-v1-location.png只是校验了apk资源,并没有约束zip,签名信息存储在zip/META-INF中。
    
    v2签名是一个对全文件进行签名的方案,能提供更快的应用安装时间、对未授权APK文件的更改提供更多保护.
    

    3、APK安装过程

    • 开机后扫描应用安装目录和系统App目录,解析其中的apk文件将相关信息加载到PKMS中的数据结构中,同时对于没有对应数据目录的App生成对应的数据目录
    • 注册包名App等信息、以及相关的四大组件到PMS中
    • 将解析到的数据同步到/data/system/packages.xml中

    4、App安装涉及的目录理解

    • 系统App安装目录

    1、 /system/app: Android系统App路径
    2、/system/priv-app: 同上,但比/system/app权限优先级更高,可以拿到ApplicationInfo.PRIVATE_FLAG_PRIVILEGED特殊权限
    3、/vendor/app: odm或者oem厂商预制系统App目录
    4、/vendor/priva-app: 同上

    • 普通应用App安装目录

    /data/app:用户App程序安装的目录。安装时Apk会被拷贝至此目录

    • 用户数据目录

    /data/data:存放应用程序的数据,无论是系统App还是普通App,App产生的用户数据都存放在/data/data/包名/目录下。

    • App注册表目录

    /data/system
    1、packages.xml:
    记录apk的permissions,,flags,ts,version,uesrid等信息,这些信息主要通apk的AndroidManifest.xml解析获取,当系统进行程序安装、卸载和更新等操作时,均会更新该文件。
    2、packages-backup.xml : 备份文件
    3、packages-stopped.xml : 记录被用户强行停止的应用的Package信息
    4、packages-stopped-backup.xml : pakcages-stoped.xml文件的备份
    5、packages.list : 记录非系统自带的APK的数据信息,这些APK有变化时会更新该文件

    5、package.xml文件解析

    <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
    <packages>
        <version sdkVersion="xxx" databaseVersion="xxx" fingerprint="xxx" />
        <version volumeUuid="xxx" sdkVersion="xxx" databaseVersion="xxx" fingerprint="xxx" />
    
        <permissions>
            <item name="android.permission.REAL_GET_TASKS" package="android" protection="18" />
            ...
        </permissions>   
      
        <package name="com.android.providers.telephony" codePath="/system/priv-app/TelephonyProvider" nativeLibraryPath="/system/priv-app/TelephonyProvider/lib" publicFlags="1007402501" privateFlags="8" ft="11e8dc5d800" it="11e8dc5d800" ut="11e8dc5d800" version="25" versionName="7.1.2" applicationName="电话和短信存储" sharedUserId="1001" isOrphaned="true">
            <sigs count="1">
                <cert index="1" key="xxx" />
            </sigs>
            <perms>
                <item name="android.permission.WRITE_SETTINGS" granted="true" flags="0" />
                ...
            </perms>
            <proper-signing-keyset identifier="1" />
        </package>  
        ...       
        
        <updated-package name="xxx.xxx.xxx" codePath="/system/app/xxx" ft="11e8dc5d800" it="11e8dc5d800" ut="11e8dc5d800" version="11" nativeLibraryPath="/system/app/xxx/lib" primaryCpuAbi="armeabi-v7a" sharedUserId="1000" />
    
    
        <shared-user name="android.media" userId="10005">
            <sigs count="1">
                <cert index="2" />
            </sigs>
            <perms>
                <item name="android.permission.ACCESS_CACHE_FILESYSTEM" granted="true" flags="0" />
                ...
            </perms>
        </shared-user>
        ...
    </packages>   
    

    package.xml对应的类图关系

    image.png
    • BasePermission

    BasePermission对应packages.xml中permissions标签的子标签item,对于上述所定义的每一项权限都会生成一个BasePermission。
    protection :等级分为四个
    1、普通权限(normal)
    2、运行时权限(dangerous)
    3、签名权限(signature)
    4、特殊权限(privileged)

        <permissions>
            <item name="android.permission.REAL_GET_TASKS" package="android" protection="18" />
            ...
        <permissions/>  
    
    • PermissionsState


      image.png

    PermissionState对应的是<package>标签中的子标签<perms>标签中的内容

    <package name="com.android.providers.telephony" codePath="/system/priv-app/TelephonyProvider" nativeLibraryPath="/system/priv-app/TelephonyProvider/lib" publicFlags="1007402501" privateFlags="8" ft="11e8dc5d800" it="11e8dc5d800" ut="11e8dc5d800" version="25" versionName="7.1.2" applicationName="电话和短信存储" sharedUserId="1001" isOrphaned="true">
            <perms>
                <item name="android.permission.WRITE_SETTINGS" granted="true" flags="0" />
                ...
            </perms>
    </package>
    
    
    • PackageSignatures

    PackageSignatures对应的是<package>标签中的子标签<sigs>标签中的内容

    <package name="com.android.providers.telephony" codePath="/system/priv-app/TelephonyProvider" nativeLibraryPath="/system/priv-app/TelephonyProvider/lib" publicFlags="1007402501" privateFlags="8" ft="11e8dc5d800" it="11e8dc5d800" ut="11e8dc5d800" version="25" versionName="7.1.2" applicationName="电话和短信存储" sharedUserId="1001" isOrphaned="true">
            <sigs count="1">
                <cert index="1" key="xxx" />
            </sigs>
    </package>
    
    
    • PackageSetting

    PackageSetting这个数据结构类是packages.xml里面记录安装包信息标签<package>相对应的类,可以看到PackageSetting继承了PackageSettingBase类,PackageSettingBase类继承自SettingBase类。应用的基本信息保存在PackageSettingBase类的成员变量中,签名则保存在PackageSignatures中,权限状态保存在父类的SettingBase的PermissionsState中。

     <package name="com.android.providers.telephony" codePath="/system/priv-app/TelephonyProvider" nativeLibraryPath="/system/priv-app/TelephonyProvider/lib" publicFlags="1007402501" privateFlags="8" ft="11e8dc5d800" it="11e8dc5d800" ut="11e8dc5d800" version="25" versionName="7.1.2" applicationName="电话和短信存储" sharedUserId="1001" isOrphaned="true">
            <sigs count="1">
                <cert index="1" key="xxx" />
            </sigs>
            <perms>
                <item name="android.permission.WRITE_SETTINGS" granted="true" flags="0" />
                ...
            </perms>
            <proper-signing-keyset identifier="1" />
    </package>  
    
    • SharedUserSetting

    SharedUserSetting这个数据结构类是packages.xml里面记录安装包信息标签<shared-user>相对应的类,它和PackageSetting有一个共同的父类即SettingBase,即都是通过父类的PermissionsState来保存权限信息。SharedUserSetting被设计的用途主要用来描述具有相同的sharedUserId的应用信息,它的成员变量packages保存了所有具有相同sharedUserId的应用信息引用,而成员变量userId则是记录多个APK共享的UID。共享用户的应用的签名是相同的,签名保存在成员变量signatures中(这里有一点需要注意,由于签名相同,Android运行时很容易检索到某个应用拥有相同的sharedUserId的其他应用)。


    image.png
     <shared-user name="android.media" userId="10005">
            <sigs count="1">
                <cert index="2" />
            </sigs>
            <perms>
                <item name="android.permission.ACCESS_CACHE_FILESYSTEM" granted="true" flags="0" />
                ...
            </perms>
    </shared-user>
    
    • Settings : package.xml 终极大管家类


      image.png

    二、APP安装整体流程

    代码仓库:http://androidxref.com/9.0.0_r3/xref/packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallStart.java

    1、安装APP代码入口

        <activity android:name=".InstallStart"
        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="file"/>
            <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="file"/>
            <data android:scheme="package"/>
            <data android:scheme="content"/>
        </intent-filter>
        <intent-filter android:priority="1">
            <action android:name="android.content.pm.action.CONFIRM_PERMISSIONS"/>
            <category android:name="android.intent.category.DEFAULT"/>
        </intent-filter>
        </activity>
    

    2、根据Uri的Scheme协议不同,跳转到不同的界面

    content协议跳转到InstallStaging,package协议跳转到PackageInstallerActivity

     protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            ......
            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);
    
            //1、content的Uri协议 : InstallStaging 
            //2、package的Url协议:PackageInstallerActivity 
            if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {
                nextActivity.setClass(this, PackageInstallerActivity.class);
            } else {
                Uri packageUri = intent.getData();
    
                if (packageUri != null && (packageUri.getScheme().equals(ContentResolver.SCHEME_FILE)
                        || packageUri.getScheme().equals(ContentResolver.SCHEME_CONTENT))) {
                    // Copy file to prevent it from being changed underneath this process
                       //1、content的Uri协议 : InstallStaging 
                    nextActivity.setClass(this, InstallStaging.class);
                } else if (packageUri != null && packageUri.getScheme().equals(
                        PackageInstallerActivity.SCHEME_PACKAGE)) {
                    //package的Url协议:PackageInstallerActivity 
                    nextActivity.setClass(this, PackageInstallerActivity.class);
                } else {
                    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();
        }
    

    3、InstallStaging类的介绍

    主要内容:将content协议的Uri转换为package协议的Uri,然后通过IO形式写入到mStagedFile文件中
    作用:主要起了转换的作用,将content协议的Uri转换为package协议,然后跳转到PackageInstallerActivity

    @Override
      protected void onResume() {
          super.onResume();
          if (mStagingTask == null) {
              if (mStagedFile == null) {
                  try {
                      mStagedFile = TemporaryFileManager.getStagedFile(this);
                  } catch (IOException e) {
                      showError();
                      return;
                  }
              }
              mStagingTask = new StagingAsyncTask();
              mStagingTask.execute(getIntent().getData());
          }
      }
    
    
     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)) {
                    if (in == null) {
                        return false;
                    }
                    try (OutputStream out = new FileOutputStream(mStagedFile)) {
                        byte[] buffer = new byte[4096];
                        int bytesRead;
                        while ((bytesRead = in.read(buffer)) >= 0) {
                            if (isCancelled()) {
                                return false;
                            }
                            out.write(buffer, 0, bytesRead);
                        }
                    }
                } catch (IOException | SecurityException e) {
                    Log.w(LOG_TAG, "Error staging apk from content URI", e);
                    return false;
                }
                return true;
            }
            @Override
            protected void onPostExecute(Boolean success) {
               if (session != null) {
             Intent broadcastIntent = new Intent(BROADCAST_ACTION);
             broadcastIntent.setPackage(
                     getPackageManager().getPermissionControllerPackageName());
             broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId);
             PendingIntent pendingIntent = PendingIntent.getBroadcast(
                     InstallInstalling.this,
                     mInstallId,
                     broadcastIntent,
                     PendingIntent.FLAG_UPDATE_CURRENT);
            //APP安装的启动入口
             session.commit(pendingIntent.getIntentSender());
             mCancelButton.setEnabled(false);
             setFinishOnTouchOutside(false);
         } else {
             getPackageManager().getPackageInstaller().abandonSession(mSessionId);
             if (!isCancelled()) {
                 launchFailure(PackageManager.INSTALL_FAILED_INVALID_APK, null);
             }
         }
    }
    

    4、PackageInstallerActivity类的介绍

    • 它就是在安装应用显示弹窗的Activity
    @Override
    protected void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        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);
        ...
        //根据Uri的Scheme进行预处理
        boolean wasSetUp = processPackageUri(packageUri);
        if (!wasSetUp) {
            return;
        }
        bindUi(R.layout.install_confirm, false);
        //判断是否是未知来源的应用,如果开启允许安装未知来源选项则直接初始化安装
        checkIfAllowedAndInitiateInstall();
    }
    
    • 分别对content和package两种不同协议处理
    private boolean processPackageUri(final Uri packageUri) {
         mPackageURI = packageUri;
         final String scheme = packageUri.getScheme();//1
         switch (scheme) {
             case SCHEME_PACKAGE: {
                 try {
                  ...
             } break;
             case SCHEME_FILE: {
                 File sourceFile = new File(packageUri.getPath());
                 //得到sourceFile的包信息
                 PackageParser.Package parsed = PackageUtil.getPackageInfo(this, sourceFile);
                 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;
                 }
                 //对parsed进行进一步处理得到包信息PackageInfo
                 mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
                         PackageManager.GET_PERMISSIONS, 0, 0, null,
                         new PackageUserState());//3
                 mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
             } break;
             default: {
                 Log.w(TAG, "Unsupported scheme " + scheme);
                 setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
                 finish();
                 return false;
             }
         }
         return true;
     }
    
    • 弹窗上显示是否是非法安装的处理
    private void checkIfAllowedAndInitiateInstall() {
           //判断如果允许安装未知来源或者根据Intent判断得出该APK不是未知来源
           if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {
               //初始化安装
               initiateInstall();
               return;
           }
           // 如果管理员限制来自未知源的安装, 就弹出提示Dialog或者跳转到设置界面
           if (isUnknownSourcesDisallowed()) {
               if ((mUserManager.getUserRestrictionSource(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
                       Process.myUserHandle()) & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {    
                   showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER);
                   return;
               } else {
                   startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
                   finish();
               }
           } else {
               handleUnknownSources();
           }
       }
    
    • InstallStaging.java session.commit() 去执行系统framework层
     protected void onPostExecute(Boolean success) {
               if (session != null) {
             Intent broadcastIntent = new Intent(BROADCAST_ACTION);
             broadcastIntent.setPackage(
                     getPackageManager().getPermissionControllerPackageName());
             broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId);
             PendingIntent pendingIntent = PendingIntent.getBroadcast(
                     InstallInstalling.this,
                     mInstallId,
                     broadcastIntent,
                     PendingIntent.FLAG_UPDATE_CURRENT);
            //APP安装的启动入口
             session.commit(pendingIntent.getIntentSender());
             mCancelButton.setEnabled(false);
             setFinishOnTouchOutside(false);
         } else {
             getPackageManager().getPackageInstaller().abandonSession(mSessionId);
             if (!isCancelled()) {
                 launchFailure(PackageManager.INSTALL_FAILED_INVALID_APK, null);
             }
         }
    
    • PackageInstaller.java 类
    public void commit(@NonNull IntentSender statusReceiver) {
               try {
                   mSession.commit(statusReceiver);
               } catch (RemoteException e) {
                   throw e.rethrowFromSystemServer();
               }
           }
    
    • PackageInstallerSession.java类
      PackageInstallObserverAdapter继承PackageInstallObserver : 监听安装APP的过程
      mSessionId是安装包的会话id,mInstallId是等待的安装事件id
    @Override
       public void commit(IntentSender statusReceiver) {
           Preconditions.checkNotNull(statusReceiver);
           ...
           mActiveCount.incrementAndGet();
           final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(mContext,
                   statusReceiver, sessionId, mIsInstallerDeviceOwner, userId);
          //Handler发送一个类型为MSG_COMMIT的消息,通知PMS安装应用
           mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget();
       }
    
    private final Handler.Callback mHandlerCallback = new Handler.Callback() {
          @Override
          public boolean handleMessage(Message msg) {
              final PackageInfo pkgInfo = mPm.getPackageInfo(
                      params.appPackageName, PackageManager.GET_SIGNATURES
                              | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
              final ApplicationInfo appInfo = mPm.getApplicationInfo(
                      params.appPackageName, 0, userId);
              synchronized (mLock) {
                  if (msg.obj != null) {
                      mRemoteObserver = (IPackageInstallObserver2) msg.obj;
                  }
                  try {
                      //PMS开始安装应用
                      commitLocked(pkgInfo, appInfo);
                  } catch (PackageManagerException e) {
                      final String completeMsg = ExceptionUtils.getCompleteMessage(e);
                      Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg);
                      destroyInternal();
                       //安装时候出现异常问题
                      dispatchSessionFinished(e.error, completeMsg, null);
                  }
                  return true;
              }
          }
      };
    
    private void commitLocked(PackageInfo pkgInfo, ApplicationInfo appInfo)
              throws PackageManagerException {
         ...
        //通知 PMS开始安装应用
          mPm.installStage(mPackageName, stageDir, stageCid, localObserver, params,
                  installerPackageName, installerUid, user, mCertificates);
      }
    

    总结:

    • 根据Uri的Scheme协议不同,跳转到不同的界面,content协议跳转到InstallStaging,package跳转到PackageInstallerActivity。
    • InstallStaging将content协议的Uri转换为File协议,然后跳转到PackageInstallerActivity。
    • PackageInstallerActivity会分别对package协议和file协议的Uri进行处理,如果是file协议会解析APK文件得到包信息PackageInfo。
    • PackageInstallerActivity中会对未知来源进行处理,如果允许安装未知来源或者根据Intent判断得出该APK不是未知来源,就会初始化安装确认界面,如果管理员限制来自未知源的安装, 就弹出提示Dialog或者跳转到设置界面。

    参考链接:https://maoao530.github.io/2017/01/18/package-install/

    相关文章

      网友评论

        本文标题:Android App安装弹窗显示流程

        本文链接:https://www.haomeiwen.com/subject/sgkburtx.html