如何在不使用文件或文件路径的情况下获取APK文件的信息?

时间:2019-05-25 22:18:22

标签: android apk inputstream android-package-managers scoped-storage

背景

我的应用(here)可以扫描整个文件系统中的APK文件,显示有关每个文件的信息,从而可以删除,共享,安装...

作为Android Q范围存储功能的一部分,Google宣布SAF(存储访问框架)将取代常规存储权限。这意味着,即使您尝试使用存储权限,也只会授予访问特定类型文件的权限,以使文件和文件路径可以使用或完全沙盒化(写成here)。

这意味着许多框架将需要依赖SAF而不是File和file-path。

问题

其中之一是packageManager.getPackageArchiveInfo,它给出了文件路径,返回PackageInfo,我可以得到有关以下内容的各种信息:

  1. 名称(在当前配置上),也称为“标签”,使用packageInfo.applicationInfo.loadLabel(packageManager)
  2. 软件包名称,使用packageInfo.packageName
  3. 版本代码,使用packageInfo.versionCodepackageInfo.longVersionCode
  4. 版本号,使用packageInfo.versionName
  5. 在当前配置上使用各种方式的
  6. 应用图标:

a。 BitmapFactory.decodeResource(packageManager.getResourcesForApplication(applicationInfo),packageInfo.applicationInfo.icon, bitmapOptions)

b。如果安装了AppCompatResources.getDrawable(createPackageContext(packageInfo.packageName, 0), packageInfo.applicationInfo.icon )

c。 ResourcesCompat.getDrawable(packageManager.getResourcesForApplication(applicationInfo), packageInfo.applicationInfo.icon, null)

我尝试过的

使用我从SAF获得的Uri并从中获得InputStream很容易:

@TargetApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    setSupportActionBar(toolbar)
    packageInstaller = packageManager.packageInstaller

    val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
    intent.addCategory(Intent.CATEGORY_OPENABLE)
    intent.type = "application/vnd.android.package-archive"
    startActivityForResult(intent, 1)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
    super.onActivityResult(requestCode, resultCode, resultData)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && requestCode == 1 && resultCode == Activity.RESULT_OK && resultData != null) {
        val uri = resultData.data
        val inputStream = contentResolver.openInputStream(uri)

但是现在您陷入了困境,因为我见过的所有框架都需要一个File或file-path。

我尝试使用Android框架找到任何替代方法,但找不到任何替代方法。不仅如此,我发现的所有库也不提供这种功能。

问题

  1. 如何从APK文件的InputStream中获取APK信息(至少是我在列表中提到的内容)?

  2. 如果常规框架上没有其他选择,我在哪里可以找到允许的选择?有没有流行的库可以为Android提供它?

注意:当然,我可以将InputStream复制到文件中,然后使用它,但这效率很低,因为我必须为找到的每个文件执行该操作,并且这样做会浪费时间和时间,因为文件已经存在。

3 个答案:

答案 0 :(得分:6)

好吧,我想我找到了一种使用Android框架的方法(reddit上的某个人给了我这个解决方案),可以使用文件路径并使用它,但它并不完美。一些注意事项:

  1. 不像以前那样直接。
  2. 好消息是,甚至可以处理设备存储之外的文件。
  3. 这似乎是一种解决方法,我不确定它能工作多长时间。
  4. 由于某种原因,我无法加载应用标签(它总是只返回包名称),并且应用图标也是如此(始终为null或默认图标)。

简而言之,解决方案正在使用此方法:

val fileDescriptor = contentResolver.openFileDescriptor(uri, "r") ?: return
val packageArchiveInfo = packageManager.getPackageArchiveInfo("/proc/self/fd/" + fileDescriptor.fd, 0)

我认为可以在需要文件路径的所有情况下使用相同的方法。

这是一个示例应用程序(也可以here使用):

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        startActivityForResult(
                Intent(Intent.ACTION_OPEN_DOCUMENT).addCategory(Intent.CATEGORY_OPENABLE)
                        .setType("application/vnd.android.package-archive"), 1
        )
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        try {
            val uri = data?.data ?: return
            val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
            contentResolver.takePersistableUriPermission(uri, takeFlags)
            val isDocumentUri = DocumentFile.isDocumentUri(this, uri)
            if (!isDocumentUri)
                return
            val documentFile = DocumentFile.fromSingleUri(this, uri) ?: return
            val fileDescriptor = contentResolver.openFileDescriptor(uri, "r") ?: return
            val packageArchiveInfo = packageManager.getPackageArchiveInfo("/proc/self/fd/" + fileDescriptor.fd, 0)
            Log.d("AppLog", "got APK info?${packageArchiveInfo != null}")
            if (packageArchiveInfo != null) {
                val appLabel = loadAppLabel(packageArchiveInfo.applicationInfo, packageManager)
                Log.d("AppLog", "appLabel:$appLabel")
            }
        } catch (e: Exception) {
            e.printStackTrace()
            Log.e("AppLog", "failed to get app info: $e")
        }
    }

    fun loadAppLabel(applicationInfo: ApplicationInfo, packageManager: PackageManager): String =
            try {
                applicationInfo.loadLabel(packageManager).toString()
            } catch (e: java.lang.Exception) {
                ""
            }
    }
}

答案 1 :(得分:0)

正如您提到的,getPackageArchiveInfo(String archiveFilePath, int flags)通过使用PackageInfo来检索archiveFilePath。但是您想要另一种方法。

解决方案

您可以使用以下意图将所有packagesNames安装在设备中 使用getPackageInfo(String packageName, int flags)的方法可以轻松访问其信息。(某种程度上是安全和隐私漏洞!)

Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
List<ResolveInfo> pkgAppsList=context.getPackageManager().queryIntentActivities(mainIntent,0);
for(int i = 0 ; i< pkgAppsList.size() ;i++)
{
  PackageInfo info = context.getPackageManager().getPackageInfo(pkgAppsList.get(i).packageName,1);
  // TODO
  // do what you need for each package .
}

答案 2 :(得分:-1)

使用以下代码

/**
* Get the apk path of this application.
*
* @param context any context (e.g. an Activity or a Service)
* @return full apk file path, or null if an exception happened (it should not happen)
*/
public static String getApkName(Context context) {
    String packageName = context.getPackageName();
    PackageManager pm = context.getPackageManager();
    try {
        ApplicationInfo ai = pm.getApplicationInfo(packageName, 0);
        String apk = ai.publicSourceDir;
        return apk;
    } catch (Throwable x) {
        return null;
    }
}