jQuery鼠标指针特效

Android T PackageInstaller解析

PackageInstallerActivity 解析

Apk安装方式

  • 通过adb命令安装 adb install -r
  • 用户下载的Apk,通过系统安装器packageinstaller安装该Apk
  • 系统内置应用,开机时自动安装系统应用

执行到最后都是由PackageManagerService来进行处理.

解析系统安装器PackageInstaller安装Apk

InstallStart

InstallStart是PackageInstaller中的入口Activity,其中PackageInstaller是系统内置的应用程序,用于安装和卸载应用。
当我们调用PackageInstaller来安装应用时会跳转到InstallStart,并调用它的onCreate方法。

packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallStart.java

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        ...
        Intent intent = getIntent();
        ...
        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
        //判断intent的action是否等于PackageInstaller.ACTION_CONFIRM_INSTALL,
        //如果是,这次安装为PackageInstaller session安装 会去intent中取出SESSION_ID,
        //并且得到SessionInfo信息,并从中得到callingPackage、callingAttributionTag。
        //当前Activity是运行在安装应用进程中,PackageInstaller session是在系统进程中。
        //在系统进程中,如果检测到权限不够,需要用户来确认,这时,会启动InstallStart以便进入用户确认界面。  
        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;
            callingAttributionTag =
                    (sessionInfo != null) ? sessionInfo.getInstallerAttributionTag() : null;
        }
        ...
        //判断是否勾选“未知来源”选项,若未勾选跳转到设置安装未知来源界面    
        if (!isTrustedSource && originatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
            final int targetSdkVersion = getMaxTargetSdkVersionForUid(this, originatingUid);
            if (targetSdkVersion < 0) {
                Log.w(LOG_TAG, "Cannot get target sdk version for uid " + originatingUid);
                // Invalid originating uid supplied. Abort install.
                mAbortInstall = true;
            } else if (targetSdkVersion >= Build.VERSION_CODES.O && !isUidRequestingPermission(
                    originatingUid, Manifest.permission.REQUEST_INSTALL_PACKAGES)) {
                //对于大于等于 Android 8.0 版本,会先检查是否申请安装权限,若没有则中断安装    
                Log.e(LOG_TAG, "Requesting uid " + originatingUid + " needs to declare permission "
                        + Manifest.permission.REQUEST_INSTALL_PACKAGES);
                mAbortInstall = true;
            }
        }
        ...
        
        Intent nextActivity = new Intent(intent);
        nextActivity.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT
                | Intent.FLAG_GRANT_READ_URI_PERMISSION);
        ...
        //isSessionInstall 为true,是PackageInstaller session安装,直接跳转PackageInstallerActivity
        if (isSessionInstall) {
            nextActivity.setClass(this, PackageInstallerActivity.class);
        } else {
            Uri packageUri = intent.getData();
            //如果得到的URI的getScheme()是ContentResolver.SCHEME_CONTENT,则会跳转到InstallStaging
            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
                nextActivity.setClass(this, InstallStaging.class);
            
            //如果得到的URI的getScheme()是PackageInstallerActivity.SCHEME_PACKAGE,
            //则会跳转到PackageInstallerActivity中		        
            } else if (packageUri != null && packageUri.getScheme().equals(
                    PackageInstallerActivity.SCHEME_PACKAGE)) {
                    
                nextActivity.setClass(this, PackageInstallerActivity.class);
            } else {
                //如果不是以上情况,则返回PackageManager.INSTALL_FAILED_INVALID_URI给调用者Activity。
                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();        
    }

判断 Uri 的 Scheme 协议,若是 SCHEME_CONTENT 则调用 InstallStaging。
若是 SCHEME_PACKAGE 则调用 PackageInstallerActivity。

PS:file协议的Uri通常以"file://"开头,用于标识本地文件系统上的文件路径。
而content协议的Uri则以"content://"开头,用于访问通过内容提供者(Content Provider)暴露的数据。

Android7.0以及更高版本我们会使用FileProvider来处理URI ,FileProvider会隐藏共享文件的真实路径,将路径转换成content://Uri路径,
这样就会跳转到InstallStaging::onResum()方法内,大致流程如下:

packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java

/** Currently running task that loads the file from the content URI into a file */
    private @Nullable StagingAsyncTask mStagingTask;
    
/** The file the package is in */
    private @Nullable File mStagedFile;
    
    @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());
        }
    } 

mStagedFile用于存储临时数据,mStagingTask拿到了content协议的Uri,追踪StagingAsyncTask拿到这个Uri后会做啥?

StagingAsyncTask是InstallStaging的内部类。

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();
            }
        }

}

doInBackground方法中将packageUri(content协议的Uri)的内容写入到mStagedFile中,
如果写入成功,onPostExecute方法中会跳转到PackageInstallerActivity中,并将mStagedFile传进去。
绕了一圈又回到了PackageInstallerActivity,这里可以看出InstallStaging主要起了转换的作用,将content协议的Uri转换为File协议,
然后跳转到PackageInstallerActivity,这样就可以像此前版本(Android7.0之前)一样启动安装流程了。

PackageInstallerActivity

PackageInstallerActivity才是应用安装器PackageInstaller真正的入口Activity。
简单说一下几个函数的作用。

    @Override
    protected void onCreate(Bundle icicle) {
       ...
       //根据Uri的Scheme进行预处理,对apk进行解析
       boolean wasSetUp = processPackageUri(packageUri);
       ... 
    }
    
    @Override
    protected void onResume() {
        super.onResume();
        ...
        if (mAppSnippet != null) {
            // load dummy layout with OK button disabled until we override this layout in
            // startInstallConfirm
            bindUi();
            checkIfAllowedAndInitiateInstall();//检查是否允许安装该软件包,如果允许,则启动安装。如果不允许,会弹出对应的对话框。
        }
        ...
        

    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
        //检查包是否已安装。如果替换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();
    }   

在PackageInstallerActivity调用startInstallConfirm方法初始化安装确认界面后。
之后的流程:

PackageInstallerActivity::startInstall()-->InstallInstalling::onCreate()

--->InstallInstalling::onResume()--->InstallInstalling::onPostExecute(){
    ...
    //PackageInstaller.Session session
    session.commit(pendingIntent.getIntentSender());
    ...
}


./frameworks/base/core/java/android/content/pm/PackageInstaller.java

说明要通过IPackageInstallerSession来进行进程间的通信,
最终会调用PackageInstallerSession的commit方法,
这样代码逻辑就到了Java框架层的。

1.将APK的信息通过IO流的形式写入到PackageInstaller.Session中。
2.调用PackageInstaller.Session的commit方法,将APK的信息交由PMS处理。

禁止安装三方应用
PackageManagerService 作用

PackageInstaller 静默安装实现

MTK-Android13-包安装器PackageInstaller 静默安装实现-需要改动文件过多
MTK Android12静默安装接口-只能用于系统签名apk

 /**
     * 根据包名判断app是否具有系统签名
     */
    private static boolean isSystemSign (Context context){
        return context.getPackageManager().checkSignatures(Binder.getCallingUid(), android.os.Process.SYSTEM_UID) == PackageManager.SIGNATURE_MATCH;
    }
posted @ 2025-03-15 15:32  僵小七  阅读(195)  评论(0)    收藏  举报