在 Android 10 上安装更新同一应用的 apk 失败; java.lang.SecurityException:文件仍然打开

Posted

技术标签:

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

我们的应用从我们的服务器下载一个 APK,然后运行它来升级自己。如android 10 - No Activity found to handle Intent 和Xamarin 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 升级相同的应用程序。使用包安装程序是否可以正常工作?如果没有,我们是否需要使用第二个应用程序来升级原来的应用程序? * 我们还在应用程序中运行了一个通知服务,这会导致“文件仍然打开”的问题吗?

【问题讨论】:

【参考方案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 的形式出现。你有什么主意吗 ?谢谢你

以上是关于在 Android 10 上安装更新同一应用的 apk 失败; java.lang.SecurityException:文件仍然打开的主要内容,如果未能解决你的问题,请参考以下文章

如何在同一设备上使用同一个包发布和安装同一个 android 应用的两个版本

AndroidAndroid应用安装失败及无法打开

Android同一应用多个service间传值

Android应用更新-自动检测版本及自动升级

可以在同一台机器上安装两个不同版本的 Android Studio 吗?

Android APK如何签名