【问题标题】:Installing apk that updates the same app fails on Android 10; java.lang.SecurityException: Files still open在 Android 10 上安装更新同一应用的 apk 失败; java.lang.SecurityException:文件仍然打开
【发布时间】:2020-02-10 22:41:36
【问题描述】:

我们的应用从我们的服务器下载一个 APK,然后运行它来升级自己。如Android 10 - No Activity found to handle IntentXamarin Android 10 Install APK - No Activity found to handle Intent 中所述,如果移动设备已升级到 Android 10,则无法像以前那样工作,得到“未找到处理 Intent 的活动”。

我们已尝试使用 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 升级相同的应用程序。使用包安装程序是否可以正常工作?如果没有,我们是否需要使用第二个应用程序来升级原来的应用程序?
* 我们还在应用程序中运行了一个通知服务,这会导致“文件仍然打开”的问题吗?

【问题讨论】:

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


    【解决方案1】:

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

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

    【讨论】:

    • 传递给 addApkToInstallSession 的文件名是什么?我收到错误 java.io.FileNotFoundException:没有内容提供者:sample.com.sampleandroid.apk。我将文件名作为需要安装的 sample.com.sampleandroid.apk 传递
    • @manjunathkallannavar:就我而言,文件名是实际文件的完整路径,如下所示:file:///sdcard/temp/myapkfilename.apk
    • 谢谢我能够解析apk路径。现在我的 onNewIntent 方法在 session.commit(statusReceiver) 之后没有被调用。什么时候调用这个方法?
    • @manjunathkallannavar:如果我没记错的话,onNewIntent 只会在您返回应用程序时调用。例如。启动应用程序后,单击主页,然后再次启动应用程序;如果应用程序已经在运行,则现在调用 onNewIntent。
    • 感谢您的快速回复。我们使用 Cordova 应用程序并在本机中执行应用程序安装代码。我有扩展 Activity 的类,并使用以下代码 context = this.classname ;我无法访问 getPackageManager().getPackageInstaller();使用以下代码的上下文 API context.getPackageManager().getPackageInstaller();我得到空指针异常。 getPackageManager() 以 null 的形式出现。你有什么主意吗 ?谢谢你
    猜你喜欢
    • 2016-12-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多