UE-外壳对接方案
<p>[TOC]</p>
<table>
<thead>
<tr>
<th>作者</th>
<th>QFord</th>
</tr>
</thead>
<tbody>
<tr>
<td>创建日期</td>
<td>2024-7-23</td>
</tr>
<tr>
<td>更新日期</td>
<td>2024-7-25</td>
</tr>
<tr>
<td>版本</td>
<td>V1.1.0</td>
</tr>
</tbody>
</table>
<h1>需求</h1>
<p><em>游浠 2024/7/22 11:58:37
我早上和永弟说了哈 你这边这周先安排做 对接灵峰这边的事情</em></p>
<p>经过和灵峰对接掌握的信息,他负责外壳的开发,主要需求如下:</p>
<ol>
<li>期望采用独立的进程进行通信。</li>
<li>颗粒播放器关闭后需通知外壳。
> 永弟反馈外壳对接的是UE-颗粒播放器,不能写UE-物件播放器。</li>
</ol>
<h1>方案</h1>
<p>永弟表示,<strong>优先处理PC端的</strong>。
其他端待后续出包后,提供给外壳开发人员进行正式接入和测试,而此方案实现不依赖我们提供的UE颗粒播放器版本。</p>
<p>下面的实现方案由本人创建的 <strong>[AI-Unreal Engine开发专家](<a href="https://ai-hub-sg.sdp.ndaeweb.com/explore/installed/cf2f15d7-b957-4dd0-918e-62b2e3d65e9d">https://ai-hub-sg.sdp.ndaeweb.com/explore/installed/cf2f15d7-b957-4dd0-918e-62b2e3d65e9d</a> "AI-Unreal Engine开发专家")</strong> 提供,可供参考:</p>
<h2>PC-Win、Android和iOS</h2>
<h3>提示词</h3>
<p>两个使用Unreal开发的程序A和B,如何从A启动B程序并置顶显示,当B程序退出后通知A程序,并将A程序置顶。两个程序之间的通信格式使用JSON。需要同时满足PC端、安卓端和iOS端。</p>
<h3>回答</h3>
<p><strong>Summary:</strong></p>
<ul>
<li>为了同时适配PC端、安卓端和iOS端,需要使用Unreal Engine的跨平台API来启动应用程序,并使用平台特定的机制进行进程间通信。</li>
</ul>
<p><strong>Detailed Explanation:</strong></p>
<ol>
<li>
<p><strong>从A启动B程序并置顶显示:</strong></p>
<ul>
<li>使用Unreal Engine的<code>FPlatformProcess::CreateProc</code>在PC端启动B程序。</li>
<li>在安卓和iOS端,使用平台特定的API启动B程序。</li>
</ul>
</li>
<li>
<p><strong>B程序退出后通知A程序:</strong></p>
<ul>
<li>使用平台特定的机制进行进程间通信,例如使用本地通知或Socket通信。</li>
</ul>
</li>
</ol>
<p><strong>Code Example:</strong></p>
<ol>
<li><strong>从A启动B程序并置顶显示:</strong></li>
</ol>
<pre><code class="language-cpp">void LaunchBProgram()
{
#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX
FString BProgramPath = TEXT(&quot;C:/Path/To/BProgram.exe&quot;);
FProcHandle ProcHandle = FPlatformProcess::CreateProc(*BProgramPath, nullptr, true, false, false, nullptr, 0, nullptr, nullptr);
if (ProcHandle.IsValid())
{
// 获取B程序的窗口句柄并置顶显示(仅适用于Windows)
#if PLATFORM_WINDOWS
HWND BProgramHWND = FindWindow(nullptr, TEXT(&quot;BProgramWindowTitle&quot;));
if (BProgramHWND)
{
SetForegroundWindow(BProgramHWND);
SetWindowPos(BProgramHWND, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
}
#endif
}
#elif PLATFORM_ANDROID
if (JNIEnv* Env = FAndroidApplication::GetJavaEnv())
{
jclass Class = FAndroidApplication::FindJavaClass(&quot;com/yourcompany/yourapp/YourActivity&quot;);
jmethodID Method = Env-&gt;GetStaticMethodID(Class, &quot;LaunchBProgram&quot;, &quot;()V&quot;);
Env-&gt;CallStaticVoidMethod(Class, Method);
}
#elif PLATFORM_IOS
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@&quot;yourappB://&quot;]];
#endif
}</code></pre>
<p><strong>②. B程序退出后通知A程序:</strong></p>
<pre><code class="language-cpp">void NotifyAProgram()
{
#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX
// 使用HTTP请求通知A程序
FString URL = TEXT(&quot;http://localhost:8080/notify&quot;);
TSharedRef&lt;IHttpRequest, ESPMode::ThreadSafe&gt; Request = FHttpModule::Get().CreateRequest();
Request-&gt;SetURL(URL);
Request-&gt;SetVerb(TEXT(&quot;POST&quot;));
Request-&gt;SetHeader(TEXT(&quot;Content-Type&quot;), TEXT(&quot;application/json&quot;));
FString JsonPayload = TEXT(&quot;{\&quot;message\&quot;: \&quot;BProgramExited\&quot;}&quot;);
Request-&gt;SetContentAsString(JsonPayload);
Request-&gt;OnProcessRequestComplete().BindLambda([](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
if (bWasSuccessful &amp;&amp; Response-&gt;GetResponseCode() == 200)
{
UE_LOG(LogTemp, Log, TEXT(&quot;Notification sent to A program successfully.&quot;));
}
else
{
UE_LOG(LogTemp, Error, TEXT(&quot;Failed to send notification to A program.&quot;));
}
});
Request-&gt;ProcessRequest();
#elif PLATFORM_ANDROID
if (JNIEnv* Env = FAndroidApplication::GetJavaEnv())
{
jclass Class = FAndroidApplication::FindJavaClass(&quot;com/yourcompany/yourapp/YourActivity&quot;);
jmethodID Method = Env-&gt;GetStaticMethodID(Class, &quot;NotifyAProgram&quot;, &quot;()V&quot;);
Env-&gt;CallStaticVoidMethod(Class, Method);
}
#elif PLATFORM_IOS
UILocalNotification* Notification = [[UILocalNotification alloc] init];
Notification.alertBody = @&quot;BProgramExited&quot;;
[[UIApplication sharedApplication] presentLocalNotificationNow:Notification];
#endif
}</code></pre>
<p><strong>③:A程序监听B程序的退出消息并置顶:</strong></p>
<pre><code class="language-cpp">void StartHttpServer()
{
#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX
FHttpServerModule* HttpServerModule = &amp;FHttpServerModule::Get();
TSharedPtr&lt;IHttpRouter&gt; HttpRouter = HttpServerModule-&gt;GetHttpRouter(8080);
HttpRouter-&gt;BindRoute(
FHttpPath(TEXT(&quot;/notify&quot;)),
EHttpServerRequestVerbs::VERB_POST,
[](const FHttpServerRequest&amp; Request, const FHttpResultCallback&amp; OnComplete)
{
FString RequestBody = FString(UTF8_TO_TCHAR(Request.Body.GetData()));
TSharedPtr&lt;FJsonObject&gt; JsonObject;
TSharedRef&lt;TJsonReader&lt;&gt;&gt; Reader = TJsonReaderFactory&lt;&gt;::Create(RequestBody);
if (FJsonSerializer::Deserialize(Reader, JsonObject) &amp;&amp; JsonObject.IsValid())
{
FString Message = JsonObject-&gt;GetStringField(TEXT(&quot;message&quot;));
if (Message == TEXT(&quot;BProgramExited&quot;))
{
// 将A程序置顶(仅适用于Windows)
#if PLATFORM_WINDOWS
HWND AProgramHWND = FindWindow(nullptr, TEXT(&quot;AProgramWindowTitle&quot;));
if (AProgramHWND)
{
SetForegroundWindow(AProgramHWND);
SetWindowPos(AProgramHWND, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
}
#endif
}
}
OnComplete(FHttpServerResponse::Create(TEXT(&quot;OK&quot;), EHttpServerResponseCodes::Ok));
return true;
}
);
HttpServerModule-&gt;StartAllListeners();
#elif PLATFORM_ANDROID
// 在Android上,使用JNI监听B程序的退出消息
extern &quot;C&quot; void Java_com_yourcompany_yourapp_YourActivity_onBProgramExited(JNIEnv* Env, jobject Thiz)
{
// 将A程序置顶
// 在Android上,应用程序通常不需要显式置顶
}
#elif PLATFORM_IOS
// 在iOS上,使用本地通知监听B程序的退出消息
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
if ([notification.alertBody isEqualToString:@&quot;BProgramExited&quot;])
{
// 将A程序置顶
// 在iOS上,应用程序通常不需要显式置顶
}
}
#endif
}</code></pre>
<p><strong>Additional Tips:</strong></p>
<ul>
<li>确保B程序的窗口标题是唯一的,以便能够正确找到窗口句柄。</li>
<li>在A程序中启动HTTP服务器时,选择一个不常用的端口号以避免冲突,建议走配置。</li>
<li>在Android上,可以使用Intent机制启动另一个应用程序。</li>
<li>在iOS上,可以使用URL Scheme启动另一个应用程序。</li>
</ul>
<h3>人工修正</h3>
<p>AI提供的代码,大大提高了开发效率。
从提供的代码分析,里面包含较多的API,比起传统的搜索,便捷了很多。
当然,AI提供的代码无法保证100%正确,需要我们开发人员进行验证和调试。
> 在灵峰同学实践过程,发现PC端的方案可以,但是移动端的遇到问题。
所以下面又写了各个平台的方案,经过调研,应该都可以使用url scheme解决。</p>
<h3>PC</h3>
<p>自行实现,使用url scheme机制,自测使用了微信的例子:
<a href="https://blog.csdn.net/weixin_59685936/article/details/138917325">https://blog.csdn.net/weixin_59685936/article/details/138917325</a>
关键蓝图代码:
<img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=e7ab05208f2af68823af075b58308f44&amp;file=file.png" alt="" /></p>
<p>程序如何注册url scheme?
How do I register a custom URL protocol in Windows? - Stack Overflow
<a href="https://stackoverflow.com/questions/80650/how-do-i-register-a-custom-url-protocol-in-windows">https://stackoverflow.com/questions/80650/how-do-i-register-a-custom-url-protocol-in-windows</a>
> 上述解答详细给出了如何手动通过编辑注册表的方式给PC端程序添加url scheme配置。
还提供了reg注册脚本的实现方式。估计,Unreal没有自行支持,需要我们开发人员自行处理。
在实际正式版本中,应该是制作安装包的时候,通过安装包的脚本来接管上述的url scheme自动化配置过程。</p>
<h3>Android</h3>
<ol>
<li>第三方实现:
AI生成的调用安卓的方案有问题,下面的方案比较靠谱:
<a href="https://support.singular.net/hc/en-us/articles/4403448781723-Unreal-Engine-SDK-Implementing-Deep-Links">https://support.singular.net/hc/en-us/articles/4403448781723-Unreal-Engine-SDK-Implementing-Deep-Links</a>
> 麻烦的是需要注册会员和申请key,免费的用户似乎有限制。</li>
</ol>
<h3>iOS</h3>
<p>使用 url scheme 机制实现唤醒App
iOS Deep Linking: URL Schemes vs Universal Links (byby.dev)
<a href="https://byby.dev/ios-deep-linking">https://byby.dev/ios-deep-linking</a></p>
<h1>协议</h1>
<p>采用JSON格式通信,具体协议内容可根据后期需求进行细化和补充。
TODO:</p>