IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android PackageManagerService 总结(一) -> 正文阅读

[移动开发]Android PackageManagerService 总结(一)

前言:

????????本篇文章是对系统包安装流程的总结,基于Android12 上 com.android.packageinstaller

源码的分析,第三方应用商城(华为商城,小米商城,应用宝,豌豆荚,酷安等)下载安装应

用,在普通安装和静默安装app两种方式下,对代码流程的梳理和讲解。


触发安装:

? ? ? ? 当你在商城界面中点击安装按钮,应用会自动下载,下载完成后就会调起系统安装应用的界面,此种触发方式一般是通过Intent 隐式调用的,我们先阅读如下代码:

????????Android7.0之前的跳转:

Uri uri = Uri.fromFile(file);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(uri ,"application/vnd.android.package-archive");
startActivity(intent);

? ? ? ? 但是在Android7.0 之后,直接调用的话会报Caused by:android.os.FileUriExposedExceptiony异常,原因是,安卓官方为了提高私有文件的安全性,在?Android 7.0 或更高版本的应用私有目录被限制访问,如果要访问私有文件的话,官方推荐使用FileProvider机制,系统源码文件管理(com.android.documentsui)中有参考例子,我们可以拿这个来讲解一下

? ? ? ? 1. 首先在AndroidManifest.xml 定义一个FileProvder, 可以自定义,也可以直接用系统的,下面的代码就是直接用androidx.core.content.FileProvider 这个类:

<manifest>
   ...
   <application>
       ...
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.android.documentsui.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true"
            tools:replace="android:authorities">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>
       ...
   </application>
</manifest>

????????如果自定义FileProvider类的话,就需要继承FileProvider,如下:

#1. 继承FileProvider 
public class MyFileProvider extends FileProvider {
   public MyFileProvider() {
       super(R.xml.file_paths)
   }
}

#2. AndroidManifest.xml中组件声明:
<manifest>
   ...
   <application>
       ...
       <provider
           android:name="com.sample.MyFileProvider"
           android:authorities="com.mydomain.fileprovider"
           android:exported="false"   #FileProvider 是不需要公开的
           android:grantUriPermissions="true">  #允许您授予对文件的临时访问权限
           ...
       </provider>
       ...
   </application>
</manifest>

? ? ? ? 2.配置file_paths.xml文件后,FileProvider 就能为预先指定的目录中的文件生成可以被访问的内容URI,在源码FileProvider.java 文件中有配置的说明

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">

	<!--external-path 对应 Environment.getExternalStorageDirectory()-->
    <external-path name="name" path="."  />
    
    <!--files-path对应Context.getFilesDir()-->
    <files-path name="name" path="." />
    
	<!--cache-path对应Context.getCacheDir()-->
	<cache-path name="name" path="." />

	<!--external-files-path对Context#getExternalFilesDir(String) Context.getExternalFilesDir(null)-->
	<external-files-path name="name" path="path" /> 

	<!--external-cache-path对应Context.getExternalCacheDir()-->
	<external-cache-path name="name" path="path" /> 

	<!--cache-path对应Context.getExternalMediaDirs()(API21+)-->
	<external-media-path name="name" path="path" /> 
</paths>

? ? ? ? 3. 那么你定了FilerProvider目的是什么呢? 就是可以通过Content URI 与另外一个应用程序共享此文件,这句话我的理解:比如文管中的apk,通过创建Content Uri,然后通过intent调用起包安装应用程序进行安装,这不就是相当于共享了此文件。那怎样为文件生成Content URI呢? 可以通过FileProvider.getUriForFile()方法,好了,到这里,通过如下代码,在来理解一下在应用商城中下载应用后会调用起系统安装应用界面的场景:

#第二个参数,就是定义FileProvider 中 android:authorities: 标签内容     
public static Uri getUriForFile(@NonNull Context context, @NonNull String authority,
            @NonNull File file) {
       
}

Intent intent = new Intent(Intent.ACTION_VIEW);
Uri uri;
# 高于或等于版本Android7.0
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
    #赋予临时权限给Uri
	intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    #生成Content Uri
	uri=FileProvider.getUriForFile(this,"android:authorities标签内容",file);
}else{
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    uri = Uri.fromFile(file);
}
#设置intent的data和type参数
intent.setDataAndType(uri ,"application/vnd.android.package-archive");
startActivity(intent);

#Android7.0可以不用加   Android8.0 以上还需要在AndroidManifest.xml 加上此权限
//申请未知来源权限
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

? ? ? ? 4. 配置好intent的启动参数后,调用startActivity(intent),那我们看看是启动了一个什么界面,这里就跳转到包安装(com.android.packageinstaller)的InstallStart这个界面,理由呢?代码即事实:

<activity android:name=".InstallStart"
                android:theme="@android:style/Theme.Translucent.NoTitleBar"
                android:exported="true"
                android:excludeFromRecents="true">
            #通过这个intent-filter匹配条件 启动了此Activity
            <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="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="package" />
                <data android:scheme="content" />
            </intent-filter>
            <intent-filter android:priority="1">
                <action android:name="android.content.pm.action.CONFIRM_INSTALL" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
</activity>

启动安装?:

? ? ? ? 通过上面代码,这个时候我们已经调用起开始安装InstallStart这个界面, 具体看看这个Activity的代码:

? ? ? ? 1. 如果为静默安装方式,则直接跳转到PackageInstallerActivity.java 这个安装界面

? ? ? ? 2. 如果为普通安装方式,解析packageUri 的android:scheme标签,是"package" 还是"content", 在本文章中 触发安装 中intent的uri为Content Uri?? 所以此处解析出来的android:scheme为 content,根据判断条件就跳转到InstallStaging.java 界面。

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {

    #是否为静默安装方式,目前三方应用商城中,除了华为应用商城是静默安装外,其他大部分都是普通安装
    final boolean isSessionInstall =
                PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction());



   if (isSessionInstall) {
         nextActivity.setClass(this, PackageInstallerActivity.class);
   } else {
            Uri packageUri = intent.getData();

            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);
            } else if (packageUri != null && packageUri.getScheme().equals(
                    PackageInstallerActivity.SCHEME_PACKAGE)) {
                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;
            }
   }

}

?看看InstallStaging文件中代码的功能:

? ? ? ? 1. 在onResume方法中,执行StagingAsyncTask 这个异步任务,通过阅读代码,我们可以得出,通过Content Uri 地址,把apk源文件 通过io流,拷贝到(com.android.packageinstaller)应用程序中,文件为mStagedFile?

@Override
    protected void onResume() {
        super.onResume();
        ......
        #创建临时文件
        mStagedFile = TemporaryFileManager.getStagedFile(this);

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

? ? ? ? 接下来就跳转到?DeleteStagedFileOnResult.java 这个界面,然后就直接跳转到PackageInstallerActivity.java 界面中

public class DeleteStagedFileOnResult extends Activity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (savedInstanceState == null) {
            Intent installIntent = new Intent(getIntent());
            installIntent.setClass(this, PackageInstallerActivity.class);

            installIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
            startActivityForResult(installIntent, 0);
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        File sourceFile = new File(getIntent().getData().getPath());
        sourceFile.delete();

        setResult(resultCode, data);
        finish();
    }
}

在来看看?PackageInstallerActivity.java 这个类,阅读代码发现有如下功能:

1. 可以获取应用的安装来源,比如是通过华为商城,应用宝,小米商城等,代码实现如下:

 #这个uid非常重要,判断安装来源的重要变量
 private int mOriginatingUid = PackageInstaller.SessionParams.UID_UNKNOWN;


 #安装来源的应用包名:
 mOriginatingPackage = (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN)
                ? getPackageNameForUid(mOriginatingUid) : null;

 
#通过uid来获取应用的包名
private String getPackageNameForUid(int sourceUid) {
        String[] packagesForUid = mPm.getPackagesForUid(sourceUid);
        if (packagesForUid == null) {
            return null;
        }
        if (packagesForUid.length > 1) {
            if (mCallingPackage != null) {
                for (String packageName : packagesForUid) {
                    if (packageName.equals(mCallingPackage)) {
                        return packageName;
                    }
                }
            }
            Log.i(TAG, "Multiple packages found for source uid " + sourceUid);
        }
        return packagesForUid[0];
    }


#通过包名来获取应用名称



????????

? ? ? ??

2. 对同一个app,如果已经安装了此app,当再安装一个高版本时,会提示更新安装

3. 还有一些异常场景的判断,比如内部不够,安装包解析错误,安装过程中报错,如下代码:

 private DialogFragment createDialog(int id) {
        switch (id) {
            case DLG_PACKAGE_ERROR:
                return SimpleErrorDialog.newInstance(R.string.Parse_error_dlg_text);
            case DLG_OUT_OF_SPACE:
                return OutOfSpaceDialog.newInstance(
                        mPm.getApplicationLabel(mPkgInfo.applicationInfo));
            case DLG_INSTALL_ERROR:
                return InstallErrorDialog.newInstance(
                        mPm.getApplicationLabel(mPkgInfo.applicationInfo));
            case DLG_NOT_SUPPORTED_ON_WEAR:
                return NotSupportedOnWearDialog.newInstance();
            case DLG_INSTALL_APPS_RESTRICTED_FOR_USER:
                return SimpleErrorDialog.newInstance(
                        R.string.install_apps_user_restriction_dlg_text);
            case DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER:
                return SimpleErrorDialog.newInstance(
                        R.string.unknown_apps_user_restriction_dlg_text);
            case DLG_EXTERNAL_SOURCE_BLOCKED:
                return ExternalSourcesBlockedDialog.newInstance(mOriginatingPackage);
            case DLG_ANONYMOUS_SOURCE:
                return AnonymousSourceDialog.newInstance();
        }
        return null;
    }

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-05-05 11:29:59  更:2022-05-05 11:34:31 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 22:26:26-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码