java - 在 Android 10 上安装更新相同应用的 apk 失败; java.lang.SecurityException : Files still open 错误

标签 java android apk android-install-apk android-10.0

我们的应用程序从我们的服务器下载一个 APK,并运行它来升级自身。如 Android 10 - No Activity found to handle Intent 中所述和 Xamarin Android 10 Install APK - No Activity found to handle Intent ,如果移动设备已升级到 Android 10,这将无法像以前那样工作,得到“找不到处理 Intent 的 Activity ”。

我们尝试使用示例中的 PackageInstaller 重写它 https://android.googlesource.com/platform/development/+/master/samples/ApiDemos/src/com/example/android/apis/content/InstallApkSessionApi.java ,但现在得到这个错误:

signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------
Abort message: 'JNI DETECTED ERROR IN APPLICATION: JNI GetStaticMethodID called with pending exception java.lang.SecurityException: Files still open
  at java.lang.Exception android.os.Parcel.createException(int, java.lang.String) (Parcel.java:2071)
  at void android.os.Parcel.readException(int, java.lang.String) (Parcel.java:2039)
  at void android.os.Parcel.readException() (Parcel.java:1987)
  at void android.content.pm.IPackageInstallerSession$Stub$Proxy.commit(android.content.IntentSender, boolean) (IPackageInstallerSession.java:593)
  at void android.content.pm.PackageInstaller$Session.commit(android.content.IntentSender) (PackageInstaller.java:1072)
  at void com.mycompany.myApp.QtJavaCustomBridge.JIntentActionInstallApk(java.lang.String) (QtJavaCustomBridge.java:301)
  at void org.qtproject.qt5.android.QtNative.startQtApplication() (QtNative.java:-2)
  at void org.qtproject.qt5.android.QtNative$7.run() (QtNative.java:374)
  at void org.qtproject.qt5.android.QtThread$1.run() (QtThread.java:61)
  at void java.lang.Thread.run() (Thread.java:919)
Caused by: android.os.RemoteException: Remote stack trace:
    at com.android.server.pm.PackageInstallerSession.assertNoWriteFileTransfersOpenLocked(PackageInstallerSession.java:837)
    at com.android.server.pm.PackageInstallerSession.sealAndValidateLocked(PackageInstallerSession.java:1094)
    at com.android.server.pm.PackageInstallerSession.markAsCommitted(PackageInstallerSession.java:987)
    at com.android.server.pm.PackageInstallerSession.commit(PackageInstallerSession.java:849)
    at android.content.pm.IPackageInstallerSession$Stub.onTransact(IPackageInstallerSession.java:306)
(Throwable with no stack trace)

这是我们使用的代码:

public static final String TAG = "JAVA"; 
public static final String PACKAGE_INSTALLED_ACTION = "com.mycompany.myApp.SESSION_API_PACKAGE_INSTALLED";

public static void JIntentActionInstallApk(final String filename)
{
    PackageInstaller.Session session = null;
    try {
        Log.i(TAG, "JIntentActionInstallApk " + filename);
        PackageInstaller packageInstaller = MyAppActivity.getActivityInstance().getPackageManager().getPackageInstaller();
        Log.i(TAG, "JIntentActionInstallApk - got packageInstaller");
        PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
        Log.i(TAG, "JIntentActionInstallApk - set SessionParams");
        int sessionId = packageInstaller.createSession(params);
        session = packageInstaller.openSession(sessionId);
        Log.i(TAG, "JIntentActionInstallApk - session opened");

        // Create an install status receiver.
        Context context = MyAppActivity.getActivityInstance().getApplicationContext();
        addApkToInstallSession(context, filename, session);
        Log.i(TAG, "JIntentActionInstallApk - apk added to session");

        Intent intent = new Intent(context, MyAppActivity.class);
        intent.setAction(MyAppActivity.PACKAGE_INSTALLED_ACTION);
        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
        IntentSender statusReceiver = pendingIntent.getIntentSender();
        // Commit the session (this will start the installation workflow).
        session.commit(statusReceiver);
        Log.i(TAG, "JIntentActionInstallApk - commited");
    } catch (IOException e) {
        throw new RuntimeException("Couldn't install package", e);
    } catch (RuntimeException e) {
        if (session != null) {
            session.abandon();
        }
        throw e;
    }
}

private static void addApkToInstallSession(Context context, String filename, PackageInstaller.Session session)
{
       Log.i(TAG, "addApkToInstallSession " + filename);
       // It's recommended to pass the file size to openWrite(). Otherwise installation may fail
       // if the disk is almost full.
       try {
            OutputStream packageInSession = session.openWrite("package", 0, -1);
            InputStream input;
            Uri uri = Uri.parse(filename);
            input = context.getContentResolver().openInputStream(uri);

                if(input != null) {
                   Log.i(TAG, "input.available: " + input.available());
                   byte[] buffer = new byte[16384];
                   int n;
                   while ((n = input.read(buffer)) >= 0) {
                       packageInSession.write(buffer, 0, n);
                   }
                }
                else {
                    Log.i(TAG, "addApkToInstallSession failed");
                    throw new IOException ("addApkToInstallSession");
                }

       }
       catch (Exception e) {
           Log.i(TAG, "addApkToInstallSession failed2 " + e.toString());
       }
}

...

  @Override
    protected void onNewIntent(Intent intent) {
        Bundle extras = intent.getExtras();
        if (PACKAGE_INSTALLED_ACTION.equals(intent.getAction())) {
            int status = extras.getInt(PackageInstaller.EXTRA_STATUS);
            String message = extras.getString(PackageInstaller.EXTRA_STATUS_MESSAGE);
            switch (status) {
                case PackageInstaller.STATUS_PENDING_USER_ACTION:
                    // This test app isn't privileged, so the user has to confirm the install.
                    Intent confirmIntent = (Intent) extras.get(Intent.EXTRA_INTENT);
                    startActivity(confirmIntent);
                    break;
                case PackageInstaller.STATUS_SUCCESS:
                    Toast.makeText(this, "Install succeeded!", Toast.LENGTH_SHORT).show();
                    break;
                case PackageInstaller.STATUS_FAILURE:
                case PackageInstaller.STATUS_FAILURE_ABORTED:
                case PackageInstaller.STATUS_FAILURE_BLOCKED:
                case PackageInstaller.STATUS_FAILURE_CONFLICT:
                case PackageInstaller.STATUS_FAILURE_INCOMPATIBLE:
                case PackageInstaller.STATUS_FAILURE_INVALID:
                case PackageInstaller.STATUS_FAILURE_STORAGE:
                    Toast.makeText(this, "Install failed! " + status + ", " + message,
                            Toast.LENGTH_SHORT).show();
                    break;
                default:
                    Toast.makeText(this, "Unrecognized status received from installer: " + status,
                            Toast.LENGTH_SHORT).show();
            }
        }
    }

目标 SDK 设置为 API 23,以便能够支持一些客户拥有的旧设备。我们使用 Qt 作为应用程序框架,但使用 Java 作为原生 Android 功能。

对此的一些看法:
* 在 Xamarin Android 10 Install APK - No Activity found to handle Intent ,他们提到他们需要在 Xamarin 中进行额外的垃圾收集。也许是因为我们这里有同样的问题?如果是这样,我们如何直接在 Java 中传递它?
* 我们正在尝试使用下载的 apk 升级同一个应用程序。这会使用软件包安装程序吗?如果没有,我们是否需要使用第二个应用程序来升级原始应用程序?
* 我们还在应用程序中运行了一项用于通知的服务,这是否会导致“文件仍处于打开状态”问题?

最佳答案

我能够通过关闭 InputStream 和 OutputStream 来解决这个问题。此外,我必须检查 21 之前的 SDK 版本,因为我们有最低 API 16,并且 PackageInstaller 已添加到 API 21。

public static void JIntentActionInstallApk(final String filename)
    {
        PackageInstaller.Session session = null;
        try {
            Log.i(TAG, "JIntentActionInstallApk " + filename);

            if(Build.VERSION.SDK_INT < 21) {
                //as PackageInstaller was added in API 21, let's use the old way of doing it prior to 21
                Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
                Uri apkUri = Uri.parse(filename);
                Context context = MyAppActivity.getQtActivityInstance().getApplicationContext();
                ApplicationInfo appInfo = context.getApplicationInfo();
                intent.setData(apkUri);
                intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false);
                intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
                intent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME,
                     appInfo.packageName);
                MyAppActivity.getQtActivityInstance().startActivity(intent);
            }
            else  {
                // API level 21 or higher, we need to use PackageInstaller
                PackageInstaller packageInstaller = MyAppActivity.getQtActivityInstance().getPackageManager().getPackageInstaller();
                Log.i(TAG, "JIntentActionInstallApk - got packageInstaller");
                PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                        PackageInstaller.SessionParams.MODE_FULL_INSTALL);
                Log.i(TAG, "JIntentActionInstallApk - set SessionParams");
                int sessionId = packageInstaller.createSession(params);
                session = packageInstaller.openSession(sessionId);
                Log.i(TAG, "JIntentActionInstallApk - session opened");

                // Create an install status receiver.
                Context context = MyAppActivity.getQtActivityInstance().getApplicationContext();
                addApkToInstallSession(context, filename, session);
                Log.i(TAG, "JIntentActionInstallApk - apk added to session");

                Intent intent = new Intent(context, MyAppActivity.class);
                intent.setAction(MyAppActivity.PACKAGE_INSTALLED_ACTION);
                PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
                IntentSender statusReceiver = pendingIntent.getIntentSender();
                // Commit the session (this will start the installation workflow).
                session.commit(statusReceiver);
                Log.i(TAG, "JIntentActionInstallApk - commited");
            }
        } catch (IOException e) {
            throw new RuntimeException("Couldn't install package", e);
        } catch (RuntimeException e) {
            if (session != null) {
                session.abandon();
            }
            throw e;
        }
    }

    private static void addApkToInstallSession(Context context, String filename, PackageInstaller.Session session)
    {
           Log.i(TAG, "addApkToInstallSession " + filename);
           // It's recommended to pass the file size to openWrite(). Otherwise installation may fail
           // if the disk is almost full.
           try {
                OutputStream packageInSession = session.openWrite("package", 0, -1);
                InputStream input;
                Uri uri = Uri.parse(filename);
                input = context.getContentResolver().openInputStream(uri);

                if(input != null) {
                   Log.i(TAG, "input.available: " + input.available());
                   byte[] buffer = new byte[16384];
                   int n;
                   while ((n = input.read(buffer)) >= 0) {
                       packageInSession.write(buffer, 0, n);
                   }
                }
                else {
                    Log.i(TAG, "addApkToInstallSession failed");
                    throw new IOException ("addApkToInstallSession");
                }
                packageInSession.close();  //need to close this stream
                input.close();             //need to close this stream
           }
           catch (Exception e) {
               Log.i(TAG, "addApkToInstallSession failed2 " + e.toString());
           }
   }

关于java - 在 Android 10 上安装更新相同应用的 apk 失败; java.lang.SecurityException : Files still open 错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58374527/

相关文章:

android - 如何解决错误 - Proguard - Android

Android:将文本写入txt

java - Android 搜索栏使用共享首选项保存进度值

Android 在构建 apk 时选择资源

android - 我的 Android 智能手机无法从我的 Apache 服务器下载 apk,这是 Mime 类型的问题吗?

java - 用Java计算标准差、方差

java - 我需要一种仅使用 java 中的 SWT 自动单击程序中按钮的方法

java - 下载springframework源码导入idea成功,创建测试模块,依赖不起作用

java - 如何@SQLDelete、软删除、具有组合键的实体

java - 权限拒绝 : opening provider android. support.v4.content.FileProvider