diff --git a/example/unity/DemoApp/Assets/Editor/Build.cs b/example/unity/DemoApp/Assets/Editor/Build.cs index 7ad79d8..281d5c8 100644 --- a/example/unity/DemoApp/Assets/Editor/Build.cs +++ b/example/unity/DemoApp/Assets/Editor/Build.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using System.IO; using System.Linq; using System.Text.RegularExpressions; @@ -14,17 +13,31 @@ public class Build : MonoBehaviour static readonly string apkPath = Path.Combine(ProjectPath, "Builds/" + Application.productName + ".apk"); - [MenuItem("Flutter/Export Android %&a", false, 1)] - public static void DoBuildAndroid() - { - string buildPath = Path.Combine(apkPath, Application.productName); - string exportPath = Path.GetFullPath(Path.Combine(ProjectPath, "../../android/UnityExport")); + static readonly string androidExportPath = Path.GetFullPath(Path.Combine(ProjectPath, "../../android/UnityExport")); + static readonly string iosExportPath = Path.GetFullPath(Path.Combine(ProjectPath, "../../ios/UnityExport")); + [MenuItem("Flutter/Export Android (Unity 2019.3.*) %&n", false, 1)] + public static void DoBuildAndroidLibrary() + { + DoBuildAndroid(Path.Combine(apkPath, "unityLibrary")); + + // Copy over resources from the launcher module that are used by the library + Copy(Path.Combine(apkPath + "/launcher/src/main/res"), Path.Combine(androidExportPath, "src/main/res")); + } + + [MenuItem("Flutter/Export Android %&a", false, 2)] + public static void DoBuildAndroidLegacy() + { + DoBuildAndroid(Path.Combine(apkPath, Application.productName)); + } + + public static void DoBuildAndroid(String buildPath) + { if (Directory.Exists(apkPath)) Directory.Delete(apkPath, true); - if (Directory.Exists(exportPath)) - Directory.Delete(exportPath, true); + if (Directory.Exists(androidExportPath)) + Directory.Delete(androidExportPath, true); EditorUserBuildSettings.androidBuildSystem = AndroidBuildSystem.Gradle; @@ -39,17 +52,18 @@ public class Build : MonoBehaviour if (report.summary.result != BuildResult.Succeeded) throw new Exception("Build failed"); - Copy(buildPath, exportPath); + Copy(buildPath, androidExportPath); // Modify build.gradle - var build_file = Path.Combine(exportPath, "build.gradle"); + var build_file = Path.Combine(androidExportPath, "build.gradle"); var build_text = File.ReadAllText(build_file); build_text = build_text.Replace("com.android.application", "com.android.library"); + build_text = build_text.Replace("implementation fileTree(dir: 'libs', include: ['*.jar'])", "implementation project(':unity-classes')"); build_text = Regex.Replace(build_text, @"\n.*applicationId '.+'.*\n", "\n"); File.WriteAllText(build_file, build_text); // Modify AndroidManifest.xml - var manifest_file = Path.Combine(exportPath, "src/main/AndroidManifest.xml"); + var manifest_file = Path.Combine(androidExportPath, "src/main/AndroidManifest.xml"); var manifest_text = File.ReadAllText(manifest_file); manifest_text = Regex.Replace(manifest_text, @"", ""); Regex regex = new Regex(@"(\s|\S)+?", RegexOptions.Multiline); @@ -57,20 +71,18 @@ public class Build : MonoBehaviour File.WriteAllText(manifest_file, manifest_text); } - [MenuItem("Flutter/Export IOS %&i", false, 2)] + [MenuItem("Flutter/Export IOS (Unity 2019.3.*) %&i", false, 3)] public static void DoBuildIOS() { - string exportPath = Path.GetFullPath(Path.Combine(ProjectPath, "../../ios/UnityExport")); - - if (Directory.Exists(exportPath)) - Directory.Delete(exportPath, true); + if (Directory.Exists(iosExportPath)) + Directory.Delete(iosExportPath, true); EditorUserBuildSettings.iOSBuildConfigType = iOSBuildType.Release; var options = BuildOptions.AcceptExternalModificationsToPlayer; var report = BuildPipeline.BuildPlayer( GetEnabledScenes(), - exportPath, + iosExportPath, BuildTarget.iOS, options ); diff --git a/example/unity/DemoApp/Assets/Editor/XCodePostBuild.cs b/example/unity/DemoApp/Assets/Editor/XCodePostBuild.cs index d8b236d..d9176a2 100644 --- a/example/unity/DemoApp/Assets/Editor/XCodePostBuild.cs +++ b/example/unity/DemoApp/Assets/Editor/XCodePostBuild.cs @@ -21,68 +21,25 @@ SOFTWARE. #if UNITY_IOS using System; -using System.Linq; + using System.Collections.Generic; using System.IO; -using System.Text; -using UnityEngine; using UnityEditor; using UnityEditor.Callbacks; using UnityEditor.iOS.Xcode; -using Application = UnityEngine.Application; /// -/// Adding this post build script to Unity project enables Unity iOS build output to be embedded -/// into existing Xcode Swift project. -/// -/// However, since this script touches Unity iOS build output, you will not be able to use Unity -/// iOS build directly in Xcode. As a result, it is recommended to put Unity iOS build output into -/// a temporary directory that you generally do not touch, such as '/tmp'. -/// -/// In order for this to work, necessary changes to the target Xcode Swift project are needed. -/// Especially the 'AppDelegate.swift' should be modified to properly initialize Unity. -/// See https://github.com/jiulongw/swift-unity for details. +/// Adding this post build script to Unity project enables the flutter-unity-widget to access it /// public static class XcodePostBuild { - /// - /// Path to the root directory of Xcode project. - /// This should point to the directory of '${XcodeProjectName}.xcodeproj'. - /// It is recommended to use relative path here. - /// Current directory is the root directory of this Unity project, i.e. the directory of 'Assets' folder. - /// Sample value: "../xcode" - /// - private const string XcodeProjectRoot = "../../ios"; - - /// - /// Name of the Xcode project. - /// This script looks for '${XcodeProjectName} + ".xcodeproj"' under '${XcodeProjectRoot}'. - /// Sample value: "DemoApp" - /// - private static string XcodeProjectName = Application.productName; - - /// - /// Directories, relative to the root directory of the Xcode project, to put generated Unity iOS build output. - /// - private static string ClassesProjectPath = "UnityExport/Classes"; - private static string LibrariesProjectPath = "UnityExport/Libraries"; - private static string DataProjectPath = "UnityExport/Data"; - - /// - /// Path, relative to the root directory of the Xcode project, to put information about generated Unity output. - /// - private static string ExportsConfigProjectPath = "UnityExport/Exports.xcconfig"; - - private static string PbxFilePath = XcodeProjectName + ".xcodeproj/project.pbxproj"; - - private const string BackupExtension = ".bak"; /// /// The identifier added to touched file to avoid double edits when building to existing directory without /// replace existing content. /// - private const string TouchedMarker = "https://github.com/jiulongw/swift-unity#v1"; + private const string TouchedMarker = "https://github.com/snowballdigital/flutter-unity-view-widget"; [PostProcessBuild] public static void OnPostBuild(BuildTarget target, string pathToBuiltProject) @@ -94,217 +51,49 @@ public static class XcodePostBuild PatchUnityNativeCode(pathToBuiltProject); - UpdateUnityIOSExports(pathToBuiltProject); - UpdateUnityProjectFiles(pathToBuiltProject); } /// - /// Writes current Unity version and output path to 'Exports.xcconfig' file. - /// - private static void UpdateUnityIOSExports(string pathToBuiltProject) - { - var config = new StringBuilder(); - config.AppendFormat("UNITY_RUNTIME_VERSION = {0};", Application.unityVersion); - config.AppendLine(); - config.AppendFormat("UNITY_IOS_EXPORT_PATH = {0};", pathToBuiltProject); - config.AppendLine(); - - var configPath = Path.Combine(XcodeProjectRoot, ExportsConfigProjectPath); - var configDir = Path.GetDirectoryName(configPath); - if (!Directory.Exists(configDir)) - { - Directory.CreateDirectory(configDir); - } - - File.WriteAllText(configPath, config.ToString()); - } - - /// - /// Enumerates Unity output files and add necessary files into Xcode project file. - /// It only add a reference entry into project.pbx file, without actually copy it. - /// Xcode pre-build script will copy files into correct location. + /// We need to add the Data folder to the UnityFramework framework /// private static void UpdateUnityProjectFiles(string pathToBuiltProject) { var pbx = new PBXProject(); - var pbxPath = Path.Combine(XcodeProjectRoot, PbxFilePath); + var pbxPath = Path.Combine(pathToBuiltProject, "Unity-iPhone.xcodeproj/project.pbxproj"); pbx.ReadFromFile(pbxPath); - // Add UnityExport/Classes - ProcessUnityDirectory( - pbx, - Path.Combine(pathToBuiltProject, "Classes"), - Path.Combine(XcodeProjectRoot, ClassesProjectPath), - ClassesProjectPath); - - // Add UnityExport/Libraries - ProcessUnityDirectory( - pbx, - Path.Combine(pathToBuiltProject, "Libraries"), - Path.Combine(XcodeProjectRoot, LibrariesProjectPath), - LibrariesProjectPath); - // Add UnityExport/Data - var targetGuid = pbx.TargetGuidByName(XcodeProjectName); - var fileGuid = pbx.AddFolderReference(Path.Combine(pathToBuiltProject, "Data"), DataProjectPath); + var targetGuid = pbx.TargetGuidByName("UnityFramework"); + var fileGuid = pbx.AddFolderReference(Path.Combine(pathToBuiltProject, "Data"), "Data"); pbx.AddFileToBuild(targetGuid, fileGuid); pbx.WriteToFile(pbxPath); } - /// - /// Update pbx project file by adding src files and removing extra files that - /// exists in dest but not in src any more. - /// - /// This method only updates the pbx project file. It does not copy or delete - /// files in Swift Xcode project. The Swift Xcode project will do copy and delete - /// during build, and it should copy files if contents are different, regardless - /// of the file time. - /// - /// The pbx project. - /// The directory where Unity project is built. - /// The directory of the Swift Xcode project where the - /// Unity project is embedded into. - /// The prefix of project path in Swift Xcode - /// project for Unity code files. E.g. "DempApp/Unity/Classes" for all files - /// under Classes folder from Unity iOS build output. - private static void ProcessUnityDirectory(PBXProject pbx, string src, string dest, string projectPathPrefix) - { - var targetGuid = pbx.TargetGuidByName(XcodeProjectName); - if (string.IsNullOrEmpty(targetGuid)) - { - throw new Exception(string.Format("TargetGuid could not be found for '{0}'", XcodeProjectName)); - } - - // newFiles: array of file names in build output that do not exist in project.pbx manifest. - // extraFiles: array of file names in project.pbx manifest that do not exist in build output. - // Build output files that already exist in project.pbx manifest will be skipped to minimize - // changes to project.pbx file. - string[] newFiles, extraFiles; - CompareDirectories(src, dest, out newFiles, out extraFiles); - - foreach (var f in newFiles) - { - if (ShouldExcludeFile(f)) - { - continue; - } - - var projPath = Path.Combine(projectPathPrefix, f); - if (!pbx.ContainsFileByProjectPath(projPath)) - { - var guid = pbx.AddFile(projPath, projPath); - pbx.AddFileToBuild(targetGuid, guid); - - Debug.LogFormat("Added file to pbx: '{0}'", projPath); - } - } - - foreach (var f in extraFiles) - { - var projPath = Path.Combine(projectPathPrefix, f); - if (pbx.ContainsFileByProjectPath(projPath)) - { - var guid = pbx.FindFileGuidByProjectPath(projPath); - pbx.RemoveFile(guid); - - Debug.LogFormat("Removed file from pbx: '{0}'", projPath); - } - } - } - - /// - /// Compares the directories. Returns files that exists in src and - /// extra files that exists in dest but not in src any more. - /// - private static void CompareDirectories(string src, string dest, out string[] srcFiles, out string[] extraFiles) - { - srcFiles = GetFilesRelativePath(src); - - var destFiles = GetFilesRelativePath(dest); - var extraFilesSet = new HashSet(destFiles); - - extraFilesSet.ExceptWith(srcFiles); - extraFiles = extraFilesSet.ToArray(); - } - - private static string[] GetFilesRelativePath(string directory) - { - var results = new List(); - - if (Directory.Exists(directory)) - { - foreach (var path in Directory.GetFiles(directory, "*", SearchOption.AllDirectories)) - { - var relative = path.Substring(directory.Length).TrimStart('/'); - results.Add(relative); - } - } - - return results.ToArray(); - } - - private static bool ShouldExcludeFile(string fileName) - { - if (fileName.EndsWith(".bak", StringComparison.OrdinalIgnoreCase)) - { - return true; - } - - return false; - } - /// /// Make necessary changes to Unity build output that enables it to be embedded into existing Xcode project. /// private static void PatchUnityNativeCode(string pathToBuiltProject) { - EditMainMM(Path.Combine(pathToBuiltProject, "Classes/main.mm")); + EditUnityFrameworkH(Path.Combine(pathToBuiltProject, "UnityFramework/UnityFramework.h")); EditUnityAppControllerH(Path.Combine(pathToBuiltProject, "Classes/UnityAppController.h")); EditUnityAppControllerMM(Path.Combine(pathToBuiltProject, "Classes/UnityAppController.mm")); - - if (Application.unityVersion == "2017.1.1f1") - { - EditMetalHelperMM(Path.Combine(pathToBuiltProject, "Classes/Unity/MetalHelper.mm")); - } - - // TODO: Parse unity version number and do range comparison. - if (Application.unityVersion.StartsWith("2017.3.0f") || Application.unityVersion.StartsWith("2017.3.1f")) - { - EditSplashScreenMM(Path.Combine(pathToBuiltProject, "Classes/UI/SplashScreen.mm")); - } + EditUnityViewMM(Path.Combine(pathToBuiltProject, "Classes/UI/UnityView.mm")); } - /// - /// Edit 'main.mm': removes 'main' entry that would conflict with the Xcode project it embeds into. - /// - private static void EditMainMM(string path) - { - EditCodeFile(path, line => - { - if (line.TrimStart().StartsWith("int main", StringComparison.Ordinal)) - { - return line.Replace("int main", "int old_main"); - } - - return line; - }); - } /// - /// Edit 'UnityAppController.h': returns 'UnityAppController' from 'AppDelegate' class. + /// Edit 'UnityFramework.h': add 'frameworkWarmup' /// - private static void EditUnityAppControllerH(string path) + private static void EditUnityFrameworkH(string path) { var inScope = false; - var markerDetected = false; - var markerAdded = false; - // Add static GetAppController + // Add frameworkWarmup method EditCodeFile(path, line => { - inScope |= line.Contains("- (void)startUnity:"); + inScope |= line.Contains("- (void)runUIApplicationMainWithArgc:"); if (inScope) { @@ -316,7 +105,7 @@ public static class XcodePostBuild { "", "// Added by " + TouchedMarker, - "+ (UnityAppController*)GetAppController;", + "- (void)frameworkWarmup:(int)argc argv:(char*[])argv;", "" }; } @@ -324,6 +113,130 @@ public static class XcodePostBuild return new string[] { line }; }); + } + + /// + /// Edit 'UnityAppController.h': returns 'UnityAppController' from 'AppDelegate' class. + /// + private static void EditUnityAppControllerH(string path) + { + var inScope = false; + var markerDetected = false; + + // Add static GetAppController + EditCodeFile(path, line => + { + inScope |= line.Contains("- (void)startUnity:"); + + if (inScope) + { + if (line.Trim() == "") + { + inScope = false; + + return new string[] + { + "", + "// Added by " + TouchedMarker, + "+ (UnityAppController*)GetAppController;", + "" + }; + } + } + + return new string[] { line }; + }); + + inScope = false; + markerDetected = false; + + // Modify inline GetAppController + EditCodeFile(path, line => + { + inScope |= line.Contains("extern UnityAppController* GetAppController"); + + if (inScope && !markerDetected) + { + if (line.Trim() == "") + { + inScope = false; + markerDetected = true; + + return new string[] + { + "// }", + "", + "static inline UnityAppController* GetAppController()", + "{", + " return [UnityAppController GetAppController];", + "}", + }; + } + + return new string[] { "// " + line }; + } + + return new string[] { line }; + }); + + + } + + /// + /// Edit 'UnityAppController.mm': triggers 'UnityReady' notification after Unity is actually started. + /// + private static void EditUnityAppControllerMM(string path) + { + var inScope = false; + var markerDetected = false; + + EditCodeFile(path, line => + { + if (line.Trim() == "@end") + { + return new string[] + { + "", + "// Added by " + TouchedMarker, + "static UnityAppController *unityAppController = nil;", + "", + @"+ (UnityAppController*)GetAppController", + "{", + " static dispatch_once_t onceToken;", + " dispatch_once(&onceToken, ^{", + " unityAppController = [[self alloc] init];", + " });", + " return unityAppController;", + "}", + "", + line, + }; + } + + inScope |= line.Contains("- (void)startUnity:"); + markerDetected |= inScope && line.Contains(TouchedMarker); + + if (inScope && line.Trim() == "}") + { + inScope = false; + + if (markerDetected) + { + return new string[] { line }; + } + else + { + return new string[] + { + " // Modified by " + TouchedMarker, + @" [[NSNotificationCenter defaultCenter] postNotificationName: @""UnityReady"" object:self];", + "}", + }; + } + } + + return new string[] { line }; + }); inScope = false; markerDetected = false; @@ -331,7 +244,7 @@ public static class XcodePostBuild // Modify inline GetAppController EditCodeFile(path, line => { - inScope |= line.Contains("inline UnityAppController"); + inScope |= line.Contains("UnityAppController* GetAppController()"); if (inScope && !markerDetected) { @@ -342,22 +255,7 @@ public static class XcodePostBuild return new string[] { - "// }", "", - "static inline UnityAppController* GetAppController()", - "{", - " return [UnityAppController GetAppController];", - "}", - }; - } - - if (!markerAdded) - { - markerAdded = true; - return new string[] - { - "// Modified by " + TouchedMarker, - "// " + line, }; } @@ -369,114 +267,34 @@ public static class XcodePostBuild } /// - /// Edit 'UnityAppController.mm': triggers 'UnityReady' notification after Unity is actually started. + /// Edit 'UnityView.mm': fix the width and height needed for the Metal renderer /// - private static void EditUnityAppControllerMM(string path) + private static void EditUnityViewMM(string path) { - EditCodeFile(path, line => - { - if (line.Trim() == "@end") - { - return new string[] - { - "", - "// Added by " + TouchedMarker, - "static UnityAppController *unityAppController = nil;", - "", - @"+ (UnityAppController*)GetAppController", - "{", - " static dispatch_once_t onceToken;", - " dispatch_once(&onceToken, ^{", - " unityAppController = [[self alloc] init];", - " });", - " return unityAppController;", - "}", - "", - line, - }; - } - - return new string[] { line }; - }); - } - - /// - /// Edit 'MetalHelper.mm': fixes a bug (only in 2017.1.1f1) that causes crash. - /// - private static void EditMetalHelperMM(string path) - { - var markerDetected = false; - - EditCodeFile(path, line => - { - markerDetected |= line.Contains(TouchedMarker); - - if (!markerDetected && line.Trim() == "surface->stencilRB = [surface->device newTextureWithDescriptor: stencilTexDesc];") - { - return new string[] - { - "", - " // Modified by " + TouchedMarker, - " // Default stencilTexDesc.usage has flag 1. In runtime it will cause assertion failure:", - " // validateRenderPassDescriptor:589: failed assertion `Texture at stencilAttachment has usage (0x01) which doesn't specify MTLTextureUsageRenderTarget (0x04)'", - " // Adding MTLTextureUsageRenderTarget seems to fix this issue.", - " stencilTexDesc.usage |= MTLTextureUsageRenderTarget;", - line, - }; - } - - return new string[] { line }; - }); - } - - /// - /// Edit 'SplashScreen.mm': Unity introduces its own 'LaunchScreen.storyboard' since 2017.3.0f3. - /// Disable it here and use Swift project's launch screen instead. - /// - private static void EditSplashScreenMM(string path) - { - var markerDetected = false; - var markerAdded = false; var inScope = false; - var level = 0; + // Add frameworkWarmup method EditCodeFile(path, line => { - inScope |= line.Trim() == "void ShowSplashScreen(UIWindow* window)"; - markerDetected |= line.Contains(TouchedMarker); + inScope |= line.Contains("UnityGetRenderingResolution(&requestedW, &requestedH)"); - if (inScope && !markerDetected) + if (inScope) { - if (line.Trim() == "{") - { - level++; - } - else if (line.Trim() == "}") - { - level--; - } - - if (line.Trim() == "}" && level == 0) + if (line.Trim() == "") { inScope = false; - } - if (level > 0 && line.Trim().StartsWith("bool hasStoryboard")) - { return new string[] { - " // " + line, - " bool hasStoryboard = false;", - }; - } - - if (!markerAdded) - { - markerAdded = true; - return new string[] - { - "// Modified by " + TouchedMarker, - line, + "", + "// Added by " + TouchedMarker, + " if (requestedW == 0) {", + " requestedW = _surfaceSize.width;", + " }", + " if (requestedH == 0) {", + " requestedH = _surfaceSize.height;", + " }", + "" }; } } @@ -485,14 +303,6 @@ public static class XcodePostBuild }); } - private static void EditCodeFile(string path, Func lineHandler) - { - EditCodeFile(path, line => - { - return new string[] { lineHandler(line) }; - }); - } - private static void EditCodeFile(string path, Func> lineHandler) { var bakPath = path + ".bak"; diff --git a/example/unity/DemoApp/Assets/UnityMessageManager.cs b/example/unity/DemoApp/Assets/UnityMessageManager.cs index 23d1100..0dd6cf9 100644 --- a/example/unity/DemoApp/Assets/UnityMessageManager.cs +++ b/example/unity/DemoApp/Assets/UnityMessageManager.cs @@ -1,175 +1,175 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using UnityEngine; - -public class MessageHandler -{ - public int id; - public string seq; - - public String name; - private JToken data; - - public static MessageHandler Deserialize(string message) - { - JObject m = JObject.Parse(message); - MessageHandler handler = new MessageHandler( - m.GetValue("id").Value(), - m.GetValue("seq").Value(), - m.GetValue("name").Value(), - m.GetValue("data") - ); - return handler; - } - - public T getData() - { - return data.Value(); - } - - public MessageHandler(int id, string seq, string name, JToken data) - { - this.id = id; - this.seq = seq; - this.name = name; - this.data = data; - } - - public void send(object data) - { - JObject o = JObject.FromObject(new - { - id = id, - seq = "end", - name = name, - data = data - }); - UnityMessageManager.Instance.SendMessageToFlutter(UnityMessageManager.MessagePrefix + o.ToString()); - } -} - -public class UnityMessage -{ - public String name; - public JObject data; - public Action callBack; -} - -public class UnityMessageManager : MonoBehaviour -{ -#if UNITY_IOS && !UNITY_EDITOR - [DllImport("__Internal")] - private static extern void onUnityMessage(string message); -#endif - - public const string MessagePrefix = "@UnityMessage@"; - - private static int ID = 0; - - private static int generateId() - { - ID = ID + 1; - return ID; - } - - public static UnityMessageManager Instance { get; private set; } - - public delegate void MessageDelegate(string message); - public event MessageDelegate OnMessage; - - public delegate void MessageHandlerDelegate(MessageHandler handler); - public event MessageHandlerDelegate OnFlutterMessage; - - private Dictionary waitCallbackMessageMap = new Dictionary(); - - static UnityMessageManager() - { - GameObject go = new GameObject("UnityMessageManager"); - DontDestroyOnLoad(go); - Instance = go.AddComponent(); - } - - void Awake() - { - } - - public void SendMessageToFlutter(string message) - { - if (Application.platform == RuntimePlatform.Android) - { - using (AndroidJavaClass jc = new AndroidJavaClass("com.reactnative.unity.view.UnityUtils")) - { - jc.CallStatic("onUnityMessage", message); - } - } - else if (Application.platform == RuntimePlatform.IPhonePlayer) - { -#if UNITY_IOS && !UNITY_EDITOR - onUnityMessage(message); -#endif - } - } - - public void SendMessageToFlutter(UnityMessage message) - { - int id = generateId(); - if (message.callBack != null) - { - waitCallbackMessageMap.Add(id, message); - } - - JObject o = JObject.FromObject(new - { - id = id, - seq = message.callBack != null ? "start" : "", - name = message.name, - data = message.data - }); - UnityMessageManager.Instance.SendMessageToFlutter(MessagePrefix + o.ToString()); - } - - void onMessage(string message) - { - if (OnMessage != null) - { - OnMessage(message); - } - } - - void onFlutterMessage(string message) - { - if (message.StartsWith(MessagePrefix)) - { - message = message.Replace(MessagePrefix, ""); - } - else - { - return; - } - - MessageHandler handler = MessageHandler.Deserialize(message); - if ("end".Equals(handler.seq)) - { - // handle callback message - UnityMessage m; - if (waitCallbackMessageMap.TryGetValue(handler.id, out m)) - { - waitCallbackMessageMap.Remove(handler.id); - if (m.callBack != null) - { - m.callBack(handler.getData()); // todo - } - } - return; - } - - if (OnFlutterMessage != null) - { - OnFlutterMessage(handler); - } - } +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using UnityEngine; + +public class MessageHandler +{ + public int id; + public string seq; + + public String name; + private JToken data; + + public static MessageHandler Deserialize(string message) + { + JObject m = JObject.Parse(message); + MessageHandler handler = new MessageHandler( + m.GetValue("id").Value(), + m.GetValue("seq").Value(), + m.GetValue("name").Value(), + m.GetValue("data") + ); + return handler; + } + + public T getData() + { + return data.Value(); + } + + public MessageHandler(int id, string seq, string name, JToken data) + { + this.id = id; + this.seq = seq; + this.name = name; + this.data = data; + } + + public void send(object data) + { + JObject o = JObject.FromObject(new + { + id = id, + seq = "end", + name = name, + data = data + }); + UnityMessageManager.Instance.SendMessageToFlutter(UnityMessageManager.MessagePrefix + o.ToString()); + } +} + +public class UnityMessage +{ + public String name; + public JObject data; + public Action callBack; +} + +public class UnityMessageManager : MonoBehaviour +{ +#if UNITY_IOS && !UNITY_EDITOR + [DllImport("__Internal")] + private static extern void onUnityMessage(string message); +#endif + + public const string MessagePrefix = "@UnityMessage@"; + + private static int ID = 0; + + private static int generateId() + { + ID = ID + 1; + return ID; + } + + public static UnityMessageManager Instance { get; private set; } + + public delegate void MessageDelegate(string message); + public event MessageDelegate OnMessage; + + public delegate void MessageHandlerDelegate(MessageHandler handler); + public event MessageHandlerDelegate OnFlutterMessage; + + private Dictionary waitCallbackMessageMap = new Dictionary(); + + static UnityMessageManager() + { + GameObject go = new GameObject("UnityMessageManager"); + DontDestroyOnLoad(go); + Instance = go.AddComponent(); + } + + void Awake() + { + } + + public void SendMessageToFlutter(string message) + { + if (Application.platform == RuntimePlatform.Android) + { + using (AndroidJavaClass jc = new AndroidJavaClass("com.rexraphael.flutterunitywidget.UnityUtils")) + { + jc.CallStatic("onUnityMessage", message); + } + } + else if (Application.platform == RuntimePlatform.IPhonePlayer) + { +#if UNITY_IOS && !UNITY_EDITOR + onUnityMessage(message); +#endif + } + } + + public void SendMessageToFlutter(UnityMessage message) + { + int id = generateId(); + if (message.callBack != null) + { + waitCallbackMessageMap.Add(id, message); + } + + JObject o = JObject.FromObject(new + { + id = id, + seq = message.callBack != null ? "start" : "", + name = message.name, + data = message.data + }); + UnityMessageManager.Instance.SendMessageToFlutter(MessagePrefix + o.ToString()); + } + + void onMessage(string message) + { + if (OnMessage != null) + { + OnMessage(message); + } + } + + void onFlutterMessage(string message) + { + if (message.StartsWith(MessagePrefix)) + { + message = message.Replace(MessagePrefix, ""); + } + else + { + return; + } + + MessageHandler handler = MessageHandler.Deserialize(message); + if ("end".Equals(handler.seq)) + { + // handle callback message + UnityMessage m; + if (waitCallbackMessageMap.TryGetValue(handler.id, out m)) + { + waitCallbackMessageMap.Remove(handler.id); + if (m.callBack != null) + { + m.callBack(handler.getData()); // todo + } + } + return; + } + + if (OnFlutterMessage != null) + { + OnFlutterMessage(handler); + } + } } \ No newline at end of file