安卓PackageInstaller无法安装APK

7

大家好,StackOverflow的用户们:

我有一个在Play Store之外的Android应用程序。它通过下载新的APK并使用Intent调用安装程序对话框来更新自己。然而,在Android 10上,更新功能已经失效了。

现在我需要在Android 10上使用PackageInstaller API,但我无法让它正常工作。我的应用程序不是设备或配置文件所有者,但我不想进行静默安装,所以我认为这应该没问题。

我的问题是,一旦我提交会话,就没有任何反应了。

PackageInstaller installer = activity.PackageManager.PackageInstaller;
PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(PackageInstallMode.FullInstall);
int sessionId = installer.CreateSession(sessionParams);
PackageInstaller.Session session = installer.OpenSession(sessionId);

var input = new FileStream(pfad, FileMode.Open, FileAccess.Read);
var packageInSession = session.OpenWrite("package", 0, -1);
input.CopyTo(packageInSession);
packageInSession.Close();
input.Close();

//That this is necessary could be a Xamarin bug.
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

Intent intent = new Intent(activity, activity.Class);
intent.SetAction("com.example.android.apis.content.SESSION_API_PACKAGE_INSTALLED");
PendingIntent pendingIntent = PendingIntent.GetActivity(activity, 0, intent, 0);
IntentSender statusReceiver = pendingIntent.IntentSender;

// Commit the session (this will start the installation workflow).
session.Commit(statusReceiver);

我查看了DDMS,但没有发现相关信息。一个可能有用的提示是,当我Dispose()流时,会出现IOException:write failed (EBADF) bad file descriptor,这表明APK有问题。但我怀疑不是这个原因,因为我可以使用文件管理器无障碍地安装APK。谷歌搜索错误也没有帮助。

我该如何解决这个问题?


首先,请参考此线程的答案 https://dev59.com/dD0DtIcB2Jgan1znu5CD,这个异常与使用的流有关,但它无法关闭。请检查您的应用程序是否关闭了所有流。 - Leon
@LeonLu-MSFT,我没有保持流处于打开状态。当我释放(而不是关闭)流时,会出现EBADF错误。 - AlphaNERD
将来自Uri的InputStream读取的APK字节写入由该会话提供的OutputStream,就像上面的链接一样。请调用close()以关闭会话。 - Leon
@LeonLu-MSFT,方法CopyTo已经将所有字节复制到目标流中。关闭会话并没有什么区别。 - AlphaNERD
@AlphaNERD,你找到了EBADF问题的解决方案吗? - smedasn
我这里有一个可用的Xamarin安装程序 - 没有遇到GC问题:https://dev59.com/p2445IYBdhLWcg3w_O8m#69774124 - Bondolin
1个回答

8

在Android Q中,为了确保apk安装成功,您需要注意以下几点:

  • 在AddApkToInstallSession方法内部,不要使用using语句或尝试对任何内容进行Dispose。Dispose会导致安装失败。请改用try/finally和close语句:

private static void AddApkToInstallSession(Context context, Android.Net.Uri apkUri, PackageInstaller.Session session)
{
  var packageInSession = session.OpenWrite("package", 0, -1);
  var input = context.ContentResolver.OpenInputStream(apkUri);

  try
  {
      if (input != null)
      {
          input.CopyTo(packageInSession);
      }
      else
      {
          throw new Exception("Inputstream is null");
      }
  }
  finally
  {
      packageInSession.Close();
      input.Close();
  }

  //That this is necessary could be a Xamarin bug.
  GC.Collect();
  GC.WaitForPendingFinalizers();
  GC.Collect();
}
  • 你必须覆盖“OnNewIntent”方法,因为你需要一个意图来确认APK文件的安装:

protected override void OnNewIntent(Intent intent)
{
    Bundle extras = intent.Extras;
    if (PACKAGE_INSTALLED_ACTION.Equals(intent.Action))
    {
        var status = extras.GetInt(PackageInstaller.ExtraStatus);
        var message = extras.GetString(PackageInstaller.ExtraStatusMessage);
        switch (status)
        {
            case (int)PackageInstallStatus.PendingUserAction:
                // Ask user to confirm the installation
                var confirmIntent = (Intent)extras.Get(Intent.ExtraIntent);
                StartActivity(confirmIntent);
                break;
            case (int)PackageInstallStatus.Success:
                //TODO: Handle success
                break;
            case (int)PackageInstallStatus.Failure:
            case (int)PackageInstallStatus.FailureAborted:
            case (int)PackageInstallStatus.FailureBlocked:
            case (int)PackageInstallStatus.FailureConflict:
            case (int)PackageInstallStatus.FailureIncompatible:
            case (int)PackageInstallStatus.FailureInvalid:
            case (int)PackageInstallStatus.FailureStorage:
                //TODO: Handle failures
                break;
        }
    }
}
  • 您需要覆盖 "OnNewIntent" 方法的活动必须将 LaunchMode 设置为 LaunchMode.SingleTop
  • 用户必须已经授予您尝试安装 APK 文件的应用程序安装 APK 所需的权限。您可以通过调用 PackageManager.CanRequestPackageInstalls() 来检查是否如此。如果此函数返回 false,则可以使用以下代码打开应用程序选项窗口:
StartActivity(new Intent(
            Android.Provider.Settings.ActionManageUnknownAppSources,
            Android.Net.Uri.Parse("package:" + Android.App.Application.Context.PackageName)));

因此用户可以轻松设置开关以启用此功能。

  • 这是我初始化APK安装的主要方法:

public void InstallPackageAndroidQAndAbove(Android.Net.Uri apkUri)
{
    var packageInstaller = PackageManager.PackageInstaller;
    var sessionParams = new PackageInstaller.SessionParams(PackageInstallMode.FullInstall);
    int sessionId = packageInstaller.CreateSession(sessionParams);
    var session = packageInstaller.OpenSession(sessionId);

    AddApkToInstallSession(apkUri, session);

    // Create an install status receiver.
    var intent = new Intent(this, this.Class);
    intent.SetAction(PACKAGE_INSTALLED_ACTION);
    var pendingIntent = PendingIntent.GetActivity(this, 0, intent, 0);
    var statusReceiver = pendingIntent.IntentSender;

    // Commit the session (this will start the installation workflow).
    session.Commit(statusReceiver);
}
  • 如果您在小米设备上进行调试,则必须在开发人员选项下禁用MIUI优化。否则,安装将失败并显示权限被拒绝的错误。

我正在尝试在Android 10上使用Xamarin做同样的事情。我遵循了您的指南,没有错误,但意图从未被调用。如果我强制调用start activity activity.StartActivityForResult(intent, 0); 我会得到以下错误:{Android.Content.ActivityNotFoundException: Unable to find explicit activity class {ch.....logymobile/android.app.Application}; have you declared this activity in your AndroidManifest.xml?我的清单文件有什么问题或者我漏掉了什么吗? - Olivier
我看到你正在使用未声明的变量PACKAGE_INSTALLED_ACTION,就好像它是一个常量一样,所以我理解你只是从其他条目中复制代码(有很多条目都在复制那个不起作用的代码)。该常量被声明为PACKAGE_INSTALLED_ACTION =“com.example.android.apis.content.SESSION_API_PACKAGE_INSTALLED”,但这没有意义。你为什么要使用那个字符串?它在任何脚本参考中都不存在。如果你甚至没有让它工作,请停止从其他网站复制代码。 - Windgate
@Windgate PACKAGE_INSTALLED_ACTION 只是一个意图操作名称。我认为对于任何阅读解决方案的人来说,这应该是显而易见的。我只是按照官方的Android示例进行操作:https://android.googlesource.com/platform/development/+/master/samples/ApiDemos/src/com/example/android/apis/content/InstallApkSessionApi.java我在Xamarin论坛上发布了相同的答案。 如果这对你不起作用,那么显然你正在做错事情。没有必要因此责怪别人。 - brz
首先,感谢您的澄清。我花了几天时间在Xamarin论坛中尝试使用该内容,在您发布的相同链接中的内容以及其他网站上的类似内容中使其正常工作。有很多条目混合使用const(PACKAGE_INSTALLED_ACTION)和硬编码字符串(“com.example.android.apis.content.SESSION_API_PACKAGE_INSTALLED”)作为操作,我认为这只是一个过时的复制+粘贴,并且存在任何翻译问题。我将清除所有项目并从头开始遵循官方的Android示例,谢谢。 - Windgate
我建议先在模拟器上测试所有内容,而不是实际设备上。一些手机制造商(如小米和其他一些品牌)喜欢在其Android构建中包含代码,这会破坏默认安装的应用程序侧载功能。在找到问题所在之前,我花了多天时间,一直想知道为什么它不能正常工作。 - brz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接