Unity中发布Windows平台实现网页打开应用并传参功能
首先说下原因,需要网页点击一个按钮能拉起本地的一个应用程序,并且把参数传给应用程序,需要做这个功能,一般都是用于制作有分享功能的项目中。
本篇文章主要将Windows的操作,有些操作我就简略写了,只讲原理,以及一些问题的解决方案,代码可能是伪代码,需要使用的朋友再自行整理下代码,因为有些东西网上已经有很多的文章写了详细的步骤了。
首先这个功能打开应用和传参数可以分为两步个不通的功能。
第一步,网页点击按钮拉起应用。
制作这个之前,我们需要先了解一个东西注册表,我们需要在注册表中写入相应的协议,那么才能网页点击按钮拉起本地的应用,他的实现原理网页通过点击按钮打开应用,浏览器本身是处理不了的,因此会发送给系统,向系统请求寻找相同协议的信息然后系统会打开对应的应用。
所以需要先自定义协议,并且需要把自定义的信息写入到ROOT下面。如下图,但是我们不可能让用户自己手动添加的,因此就需要我们通过程序的手段自动给他添加到注册表中。需要注意的是想把信息写入到ROOT中必须有管理员权限,这就很大的限制了我们的操作。(后续会说名协议内容)
第一种,在安装应用的时候可以申请管理员权限,同步把信息写入到注册表。例如制作安装包的Advanced Installer软件,可以直接设置写入。
第二种,在Unity应用中直接编写代码写入,通过下面方式(伪代码)写入,设置的Value是exe运行文件的完整路径,加上%1,需要注意的是一样的需要管理员权限,因此如果通过这种方式写入信息,那么需要手动打开一次exe,并且用管理员身份打开才会有效。其中metaversebeta就是协议名称,后续的shell\\open\\command是固定的分层照抄就行。
Microsoft.Win32.RegistryKey sofeware = Microsoft.Win32.Registry.ClassesRoot.CreateSubKey(@\"metaversebeta\\shell\\open\\command\");sofeware.SetValue(\"\", \"拉起的应用路径 %1\");
第三种,通过注册表reg文件写入,其中metaversebeta就是协议名称,后续的shell\\open\\command是固定的分层照抄就行。最后就是设置的参数,一个是运行的exe文件的绝对路径,加上%1即可。生成reg文件也很简单,创建一个txt文件,然后把下面复制进去,修改协议以及路径后,另存为后缀是reg的文件就可以了,然后双击运行就会申请权限,写入内容。
Windows Registry Editor Version 5.00[HKEY_CLASSES_ROOT\\metaversebeta]\"URL Protocol\"=\"\"@=\"URL:metaversebeta\"[HKEY_CLASSES_ROOT\\metaversebeta\\shell][HKEY_CLASSES_ROOT\\metaversebeta\\shell\\open][HKEY_CLASSES_ROOT\\metaversebeta\\shell\\open\\command]@=\"E:\\\\OwnerFiles\\\\Swift\\\\Unity Projects\\\\Swi_Metawork_Test\\\\Build\\\\NewMobileWindows\\\\Meta会议.exe \\\"%1\\\"\"
但是通过这种方式写入的,你会发现两个问题,第一个他会跳出一堆确认框,第二个怎么动态修改文件路径,因为每个人安装的路径是不一样的。
先说第一个问题,在Mono编译的情况下,可以通过Process打开不用Application.OpenURL打开就可以解决,只会有一个申请权限的弹窗。如下面代码,需要传入运行程序,加上/s以及完整的reg文件路径
但是IL2CPP编译情况不支持Process了,调用这个有问题,需要另外自行找解决方案。
Application.OpenURL方法不管哪种编译都能使用,但是都会出现一堆弹窗。
System.Diagnostics.Process.Start(\"regedit.exe\", \"/s \" + FilePath);
接着说第二个问题,手动生成reg文件没办法动态配置路径,而且也不能让用户手动生成,那么解决方案很简单也是唯一的解决方案,就是通过代码生成,直接编写在程序中,并且还能判断是否需要更新路径,用户每次打开应用,判断路径不一致时重新生成reg文件打开运行刷新注册表信息,如果信息一致的时候就生成reg刷新信息。。
下面代码只做参考,因为是我直接复制我项目中的代码,有一些参数需要根据对应项目调整,以及协议也需要调整。
string FilePath = Path.Combine(Config.LocalFilesBasePath, \"Protocol.reg\"); string ExePath = Path.Combine(System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase, UnityEngine.Application.productName+\".exe\"); bool IsReWrite = true; using (Microsoft.Win32.RegistryKey handlerKey = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(@\"metaversebeta\\shell\\open\\command\")) { Debug.Log(\"开始检测唤起\"); if (handlerKey != null) { string value = (string)handlerKey.GetValue(null); var strarray = value.Split(\'\"\'); if (value != string.Empty && strarray.Length > 0) { var registValue = strarray[0].Remove(strarray[0].Length-1); Debug.Log(registValue); IsReWrite = !registValue.Equals(ExePath); } } } if (IsReWrite) { Debug.Log(\"重新生成唤起文件\"); ExePath = ExePath.Replace(\"\\\\\", \"\\\\\\\\\"); string content = $\"Windows Registry Editor Version 5.00\\r\\n\" + \"[HKEY_CLASSES_ROOT\\\\\" + ProductName + \"]\\r\\n\" + \"\\\"URL Protocol\\\"=\\\"\\\"\\r\\n\" + \"@=\\\"URL:\" + ProductName + \"\\\"\\r\\n\" + \"[HKEY_CLASSES_ROOT\\\\\" + ProductName + \"\\\\shell]\\r\\n\\r\\n\" + \"[HKEY_CLASSES_ROOT\\\\\" + ProductName + \"\\\\shell\\\\open]\\r\\n\\r\\n\" + \"[HKEY_CLASSES_ROOT\\\\\" + ProductName + \"\\\\shell\\\\open\\\\command]\\r\\n\" + \"@=\\\"\" + ExePath + \" \\\\\\\"%1\\\\\\\"\\\"\\r\\n\"; FilesTool.ExportFile(content, Encoding.Unicode,Config.LocalFilesBasePath, \"Protocol.reg\"); //System.Diagnostics.Process.Start(\"regedit.exe\", \"/s \" + FilePath); Application.OpenURL(FilePath); }
说完协议,接着说浏览器部分,如果只是拉起应用就很简单,通过href = “metaversebeta://”,😕/前面是对应注册表中的协议
第二部传参,通过href = “metaversebeta://key1=value1&key2=value2”,😕/后面的就是具体参数,自行定义解析协议拼接就行。在Unity中获取参数也很简单直接获取控制台信息即可。
代码示例如下,根据自己项目自行修改,Environment.GetCommandLineArgs()这个方法就是获取控制台信息的,Search_string方法是做参数拆分获取具体数据的。
public string GetWindowsWebData() {#if UNITY_STANDALONE_WIN var args = Environment.GetCommandLineArgs(); if (args.Length > 1) { var arg=Uri.UnescapeDataString(args[1]); Debug.Log(arg); var code = Search_string(arg,\"roomid=\",\"&\"); Debug.Log(code); return code; } return string.Empty;#else return string.Empty;#endif } public static string Search_string(string s, string s1, string s2) { int n1, n2; n1 = s.IndexOf(s1, 0) + s1.Length; n2 = s.IndexOf(s2, n1); return s.Substring(n1, n2 - n1); }
理论上到此就已经完成了网页拉起的所有功能,但是当你实际编写完测试的时候就会发现一些问题,我这边只记录我遇到的问题,以及解决方案。如果以后碰到新的问题,再更新。
第一个问题,网页拉起的永远都是打开一个新的应用。
解决方案,有多种:
1、编写一个bat,通过bat去控制,拉起的也是bat文件,判断如果应用已经打开,就直接拉起打开的应用,如果没打开就打开一个新的应用,并传参到控制台。
2、编写一个服务service,那种开机就打开的服务,通过服务去筛选。原理也是一样的。
3、最简单的一种,直接使用Unity中提供的限制多开的设置就可以,使用这个后,当网页去拉起应用的时候,如果没打开应用,就正常的打开应用,如果已经打开了应用的情况下,因为被限制多开,会去拉起已经打开的应用。
这是位置在 ProjectSettings=>Player=>Resolution and Presentation下有个Force Single Instance选项,这个勾选上就行
第二个问题,传参如果是没打开应用的情况下,传参会正常拉起一个新的应用,并且把参数传到控制台,我们从控制台获取参数就行,但是如果拉起了一个已经打开着的应用,参数就不会传到控制台。那么怎么把数据传给应用呢。
我想到的有两种方案,自行选择
1、编写一个服务service,那种开机就打开的服务,通过服务去中转,把参数发送到unity层。(一些大厂的选择,例如百度网盘等)
2、在我们的应用项目中内嵌一个服务,写一个WebApi或者WebSocket,如果是Socket必须是WebSocket,不能是TCP和UDP,因为网页只能链接WebSocket,这部分服务就自行找服务器的库写个很简单的接口或者WebSocket就行,当网页拉起应用的时候也同步调一下127.0.0.1本机接口,或者连接下WebSocket进行传参。
例如下图,(因为是项目中的截图,用了Photon的服务器库,自己项目中可以另外找个库使用编写就行,也可以使用我写C#服务器的库,TouchSocket,自行搜索,作者在B站还有教学课程,里面包含了Http的接口编写,TCP、UDP、WebSocket、WebApi的各种服务器编写)
第三个问题:微信浏览器,浏览器限制了拉起应用。两种解决方案:
1、微信浏览器没用直接提供拉起应用,但是提供了拉起应用宝下载功能,如果你的项目已经上架应用宝,那么可以通过应用宝变相的拉起应用。
2、在网页点击按钮的时候做个提升,让用户不要使用微信浏览器打开,通过其他的浏览器打开。