【问题标题】:Programatically launch an APK installation from app internal storage in Android 5.0 - 6.0以编程方式从 Android 5.0 - 6.0 中的应用程序内部存储启动 APK 安装
【发布时间】:2017-11-02 20:39:19
【问题描述】:

Programmatically install an apk in Android 7 / api24 回答了类似的问题,但我也想以编程方式从 Android 5.0 - 6.0 中的应用程序内部存储(在 SD 卡上)安装 APK。

如果它不是来自应用程序内部存储,我可以 startActivity() 带有一个意图,其操作是 ACTION_VIEW 并且其数据 URI 具有 file:// 方案(使用 Uri.fromFile())。但我已经尝试过了,它不适用于应用程序内部存储,其他应用程序无法读取。我基于日志消息,如

W/asset: Asset path /storage/C5DF-1113/Android/data/com.example/files/foo.apk is neither a directory nor file (type=1).
W/InstallFlowAnalytics: Failed to hash APK contents
  java.io.FileNotFoundException: /storage/C5DF-1113/Android/data/com.example/files/foo.apk: open failed: ENOENT (No such file or directory)

我尝试使用content:// 方案对 ContentProvider 进行子类化。但显然(请参阅CommonsWare's answer here),包安装程序在 Android 7.0 之前不支持content:// 方案。我在 Android 6.0 中收到如下日志消息:

I/ActivityManager: START u0 {act=android.intent.action.VIEW dat=content://com.example.provider/internal/foo.apk typ=application/vnd.android.package-archive flg=0x10000001} from uid 10159 on display 0
E/Updates: installNewApk
     android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.VIEW dat=content://com.example.provider/internal/foo.apk typ=application/vnd.android.package-archive flg=0x10000001 }
        at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:1805)

与包安装程序匹配的意图和不匹配的意图之间唯一明显的区别是数据 URI 从 file://... 更改为 content://....

在其他地方默默地安装了一个应用程序的答案,这对于我的项目不是一个要求。还有一些解决方案使用反射来获取未记录的功能,或者需要设备植根,但这些技术对于这个项目来说太脆弱或对用户不友好。

还可以选择在启动安装程序之前将 APK 从应用内部存储复制到外部存储。我们可能不得不去那里,但首先将 APK 放在内部存储中的原因是出于隐私和安全的目的。 (我意识到这远非万无一失,但总比没有好,这正是我们的客户想要的。)

我愿意基于当前版本的Android做一个条件,但是此时我仍然不知道在5.0 - 6.0中要做什么。

感谢您的任何建议。

【问题讨论】:

  • “内部存储”通常为getFilesDir() 之类的东西保留。 SD 卡最好称为“可移动存储”。关于您的问题,AFAIK,您不走运 - APK 必须在 Android 7.0 之前的外部存储(getExternalFilesDir() 等)上。我大声抱怨这件事,但无济于事。
  • @CommonsWare:感谢您的回答。关于“可移动存储”,我同意这是一个很好的存储在 SD 卡上的术语,但这个问题最重要的区别是它不在公共可读区域。如果是这样,我可以在没有 ContentProvider 的情况下使用 Uri.fromFile()(在 7.0 之前)。 AFAIK,它在可移动存储上的事实并没有真正影响这个问题。
  • “AFAIK,它位于可移动存储上的事实并不会真正影响这个问题”——从 Android 4.4 开始,应用程序没有对可移动存储的任意读/写访问权限,这就是为什么 @ 987654336@ 不适合你。只有您有权访问该目录;其他应用程序(包括安装程序)则不会。使用getFilesDir() 会遇到同样的问题,因为其他应用程序无权访问该位置。在 7.0 之前,您不能使用 content 作为方案,这会给您留下外部存储空间……就是这样,AFAIK。
  • @CommonsWare:是的,但应用程序也没有对非可移动存储的任意读/写访问权限。这就是为什么我说它并没有真正影响这个问题。
  • “但应用程序也没有对不可移动存储的任意读/写访问权限”——是的,但安装程序应用程序可以访问外部存储,但不能访问您的特定可移动存储段或到您的特定内部存储段。因此,在您的场景中,外部存储和可移动存储之间存在很大差异。

标签: android android-contentprovider android-install-apk


【解决方案1】:

要从内部存储目录中获取 apk 文件的 Uri,您可以使用此方法对我有用:

  public static Uri getApkUri(Activity activity, String path) {

    // Before N, a MODE_WORLD_READABLE file could be passed via the ACTION_INSTALL_PACKAGE
    // Intent. Since N, MODE_WORLD_READABLE files are forbidden, and a FileProvider is
    // recommended.
    boolean useFileProvider = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
    int fileMode = useFileProvider ? Context.MODE_PRIVATE : Context.MODE_WORLD_READABLE;

    String tempFilename = "tmp.apk";
    byte[] buffer = new byte[16384];

    try (InputStream is = new FileInputStream(path);
         FileOutputStream fout = activity.openFileOutput(tempFilename, fileMode)) {
        int n;
        while ((n = is.read(buffer)) >= 0) {
            fout.write(buffer, 0, n);
        }
    } catch (IOException e) {
        Logger.e(TAG, ":getApkUri + " + "Failed to write temporary APK file", e);
    }

    if (useFileProvider) {
        File toInstall = new File(activity.getFilesDir(), tempFilename);
        return FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID, toInstall);
    } else {
        return Uri.fromFile(activity.getFileStreamPath(tempFilename));
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-06-13
    • 1970-01-01
    • 2016-09-13
    • 2019-06-06
    • 2011-06-25
    • 2011-09-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多