在Android 5.0-6.0中,从应用程序内部存储器编程启动APK安装。

3
这个帖子回答了一个类似的问题,但我想在Android 5.0-6.0上从应用程序内部存储(SD卡上)以编程方式安装APK文件。
如果不是来自应用程序内部存储,我可以使用一个意图(其操作为ACTION_VIEW),并使用具有“file://”方案的数据URI(使用Uri.fromFile())调用startActivity()。但我尝试过,这对于应用程序内部存储无效,因为其他应用程序无法读取它。我基于像下面的日志消息做出这个结论:
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)

我尝试过通过子类化ContentProvider来使用content://协议。但是显然(参见CommonsWare在这里的答案),直到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://...

有其他关于静默安装应用的答案,但这不是我项目的要求。还有使用反射获取未公开功能或需要设备root的解决方案,但这些技术对于这个项目来说太脆弱或用户不友好。

还有将APK从应用程序内部存储复制到外部存储以启动安装程序的选项。我们可能不得不去那里,但首先将APK置于内部存储中是出于隐私和安全性的考虑。(我意识到这还远非铁壁铜墙,但总比没有好,这也是我们客户想要的。)

我愿意根据当前Android版本进行条件判断,但目前我仍然不知道在5.0-6.0上该怎么做。

感谢任何建议。


“内部存储”通常是为像getFilesDir()这样的东西保留的。 SD 卡最好称为“可移动存储”。 关于你的问题,据我所知,你没有运气了——在 Android 7.0 之前,APK 必须在外部存储 (getExternalFilesDir() 等) 中。 我曾经大声抱怨过这个问题,但毫无用处。 - CommonsWare
据我所知,它在可移动存储上的事实并不会影响这个问题 - 自Android 4.4起,应用程序无法任意读写可移动存储,这就是为什么Uri.fromFile()对您无效的原因。只有您可以访问该目录;其他应用程序(包括安装程序)不能访问。如果使用getFilesDir(),您将遇到相同的问题,因为其他应用程序无法访问该位置。而且,在7.0之前,您不能使用content作为方案,这使您只能使用外部存储...就是这样,据我所知。 - CommonsWare
@CommonsWare:没错,但应用程序也无法对可移动存储进行任意读写访问。这就是为什么我说它并不真正影响这个问题。 - LarsH
好的,那你重点是在可移动存储和“外部存储”之间的差异上,而不是可移动和不可移动存储之间的差异。 - LarsH
好的,这三种都有各自的特点。外部存储是唯一可以在所有应用程序之间共享的存储方式,或者至少是已经获得用户授权的子集。 - CommonsWare
显示剩余2条评论
1个回答

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接