在 Android 10 上安装更新同一应用程序的 apk 失败; java.lang.SecurityException: 文件仍然打开
Installing apk that updates the same app fails on Android 10; java.lang.SecurityException: Files still open
我们的应用程序从我们的服务器下载一个 APK,并运行它来升级自身。如 and 中所述,如果移动设备已升级到 Android 10,这将无法像以前那样工作,得到 "No Activity found to handle 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.run() (QtNative.java:374)
at void org.qtproject.qt5.android.QtThread.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 中进行额外的垃圾收集。也许是因为我们这里有同样的问题?如果是这样,我们如何才能直接在 Java 中传递它?
* 我们正在尝试使用下载的 apk 升级同一个应用程序。这会使用软件包安装程序吗?如果没有,我们是否需要使用第二个应用程序来升级原始应用程序?
* 我们在应用程序中还有一项服务 运行 以及通知,这是否会导致 "Files still open" 问题?
我可以通过关闭 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());
}
}
我们的应用程序从我们的服务器下载一个 APK,并运行它来升级自身。如
我们已尝试使用示例中的 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.run() (QtNative.java:374)
at void org.qtproject.qt5.android.QtThread.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 函数,就像这样。
对此的一些想法:
* 在
* 我们正在尝试使用下载的 apk 升级同一个应用程序。这会使用软件包安装程序吗?如果没有,我们是否需要使用第二个应用程序来升级原始应用程序?
* 我们在应用程序中还有一项服务 运行 以及通知,这是否会导致 "Files still open" 问题?
我可以通过关闭 InputStream 和 OutputStream 来解决这个问题。此外,我必须检查 21 之前的 SDK 版本,因为我们至少有 API 16,并且在 API 21.
中添加了 PackageInstallerpublic 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());
}
}