UnrealEngineのLaunchURLがAndroidで動作しないことがある問題とその解決
はじめに
この記事ではUnreal Engine側のコードを修正して解決していますが、UEのコードビルドは必要なくEpicランチャー版でも修正可能です。
修正は自己責任でお願いします。
問題の内容
AndroidではSDK30で端末にある他のパッケージを見る権限の制限が強くなりました。その結果、正しい実装をしないとgetPackageManager()
が想定通り動作しないことがあります。
その影響を受けてか、UnrealEngineのAndroidビルドで以下のような現象があることを確認しました。
- ブループリントで
LaunchURL
を実行したとき、引数のURLを処理できるアプリが端末内にない場合はブラウザが開くが、逆の場合は何も起きない- Twitter、Lineなど特定のURLを処理できるアプリをインストール済みの端末で、特定のURL(TwitterのURLやLineURLスキーム)をLaunchURLで実行しても何も起きない
- Twitter、Lineなど特定のURLを処理できるアプリをアンインストールして同じURLを実行するとブラウザが開く
この問題はフォーラムに上がっていて、UE4.27で最低限のSDK30対応がされているようですが、それが不十分なようです。
※フォーラムでリンクされているGithubIssueはUEのリポジトリ閲覧権限(無料)を持っていないと参照できません。閲覧権限の申請方法はこちら。
またフォーラムで触れられている通りQUERY_ALL_PACKAGES
の権限をManifestに追加するのは完全に悪手です。追加の権限申告フォームへの記入が求められますし、ポリシーを満たさない場合や申告せずに使っている場合はマーケットからアプリが削除される可能性があります。
問題の原因
LaunchURL
の実行はUnrealEngineのAndroid用Javaコードに移譲されており、その中でURLを処理できるActivityがあるかどうかの判断しています。
その中のif分岐に問題があります。
public void AndroidThunkJava_LaunchURL(String URL)
{
Log.debug("[JAVA} AndroidThunkJava_LaunchURL: URL = " + URL);
if (!URL.contains("://"))
{
// add http:// if there isn't a scheme before a colon
if (URL.indexOf(":") < 1)
{
URL = "http://" + URL;
Log.debug("[JAVA} AndroidThunkJava_LaunchURL: corrected URL = " + URL);
}
}
try
{
Intent BrowserIntent = new Intent(Intent.ACTION_VIEW, android.net.Uri.parse(URL));
BrowserIntent.addCategory(Intent.CATEGORY_BROWSABLE);
// open browser on its own task
BrowserIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
BrowserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
// make sure there is a web browser to handle the URL before trying to start activity (or may crash!)
if (BrowserIntent.resolveActivity(getPackageManager()) != null)
{
Log.debug("[JAVA} AndroidThunkJava_LaunchURL: Starting activity");
startActivity(BrowserIntent);
}
else
{
Log.debug("[JAVA} AndroidThunkJava_LaunchURL: Could not find an application to receive the URL intent");
}
}
catch (Exception e)
{
Log.debug("[JAVA} AndroidThunkJava_LaunchURL: Failed with exception " + e.getMessage());
}
}
if (BrowserIntent.resolveActivity(getPackageManager()) != null)
の部分が問題で、URLが処理できるアプリがあるとBrowserIntent.resolveActivity(getPackageManager())
がNullで返ってきます。このせいでstartActivity(BrowserIntent)
が発行されずアプリ上では何も起きません。ブラウザ以外で引数のURLを処理できるアプリがあるとNullで、ないとNotNullなのは不自然な動きにしか思えませんが……もはやgetPackageManager()
を使って処理できるパッケージがあるかどうかを判断するのは時代遅れなのかもしれません。
解決策
そもそもAndroidの公式ではどのようなコードでURLのintentを発行しているかを見てみるのが良さそうです。
try {
val intent = Intent(ACTION_VIEW, Uri.parse(url)).apply {
// The URL should either launch directly in a non-browser app (if it's
// the default), or in the disambiguation dialog.
addCategory(CATEGORY_BROWSABLE)
flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_REQUIRE_NON_BROWSER
}
startActivity(intent)
} catch (e: ActivityNotFoundException) {
// Only browser apps are available, or a browser is the default.
// So you can open the URL directly in your app, for example in a
// Custom Tab.
openInCustomTabs(url)
}
こんな感じでstartActivity(intent)
でintentをぶん投げちゃって、try-catch
で握り潰してます。公式ではcatch
の中でCustomTabを開くようになってますが、UEのAndroidビルドにCustomTabを入れるのは難儀なのでなくても良いでしょう。ブラウザアプリがひとつでもあればcatch
には入らないですしね。
そして実はUEのコードの問題のif文の前に// make sure there is a web browser to handle the URL before trying to start activity (or may crash!)
と書いてますが、そもそもtry-catch
してるのでクラッシュはしません。resolveActivity()
を使ったif文が余計で、これがなくてもなんら問題ないです。
つまり、この問題の解決方法はBrowserIntent.resolveActivity(getPackageManager())
を使ってるif分岐を外して確実に startActivity(BrowserIntent)
を実行することです。
public void AndroidThunkJava_LaunchURL(String URL)
{
Log.debug("[JAVA} AndroidThunkJava_LaunchURL: URL = " + URL);
if (!URL.contains("://"))
{
// add http:// if there isn't a scheme before a colon
if (URL.indexOf(":") < 1)
{
URL = "http://" + URL;
Log.debug("[JAVA} AndroidThunkJava_LaunchURL: corrected URL = " + URL);
}
}
try
{
Intent BrowserIntent = new Intent(Intent.ACTION_VIEW, android.net.Uri.parse(URL));
BrowserIntent.addCategory(Intent.CATEGORY_BROWSABLE);
// open browser on its own task
BrowserIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
BrowserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
// make sure there is a web browser to handle the URL before trying to start activity (or may crash!)
//if (BrowserIntent.resolveActivity(getPackageManager()) != null)
//{
Log.debug("[JAVA} AndroidThunkJava_LaunchURL: Starting activity");
startActivity(BrowserIntent);
//}
//else
//{
// Log.debug("[JAVA} AndroidThunkJava_LaunchURL: Could not find an application to receive the URL intent");
//}
}
catch (Exception e)
{
Log.debug("[JAVA} AndroidThunkJava_LaunchURL: Failed with exception " + e.getMessage());
}
}
この修正を入れたUEでAndroidビルドすれば、引数のURLを拾えるActivityがいい感じに起動してくれるようになります。めでたしめでたし。
Discussion