众所不周知,在大概 3 4 年前曾经有这么一个项目叫 sona,这是一个音乐播放器,react native,连着网抑云。大概长这样

sona

过了这么久,我早就不网抑云,也以现在的眼光再看这个 UI 未免有些稚气,还有点费电资源 —— 整的什么垃圾

正好,关注了一年多的 MAUI,纯纯在 Build 看着宣布,然后 GitHub 开库,进 preview,到现在 stable。.NET 都从 6 preview 一直到 7…

所以先来搂一眼,正好也想复活这个产品,并且不优先解决网抑云,而是解决 Apple Music 和 Spotify,还有 Last.fm。再决定是改用 maui 重构还是继续用 RN

TL;DR

  • C# 很香,但遗传了 Xamarin 的臭
  • 安卓 SDK 两个包,噶了一个
  • 平台能力最好还是用平台原生语言
  • 目前的 MAUI,狗都不用

MAUI

一句话:MAUI 是另一个 React Native 但 C#,不是 Flutter

MAUI 前面有 Xamarin(分平台的不是 Xamarin.Forms),服就服在微软能把各个平台的 API 都转一份,这样所有代码都使用 C# 编写,以便享受这个极其变态的编译器。当你创建一个 mac 的 Xamarin 应用,你会发现除了语法是 C#,无论结构还是文件组织都与直接用 XCode 创建出来的项目不能说有点相似,只能说完全一致

高情商:令人钦佩的软件工程能力;低情商:吃饱了撑着

所以作为后继者,MAUI 在平台调用依然会是全 C#,不像 flutter 或者 RN 在分别平台使用 swift/kotlin。这样有好坏

好是因为全 C#,在编写平台逻辑时可以直接使用通用的 interface,或者是 common 写一个 partial,再分平台补充实现。即使在通用代码也可以用 macro 来区分平台生成代码,毕竟不用担心,编译会根据编译目标选择源代码

坏那也坏,RN 和 flutter 的方式是让平台代码完全使用平台程序,相当于写了一个原生程序,通过桥通信交换信息,这样可以保证平台逻辑是绝对的平台逻辑,《非常原生》。而反过来就是 MAUI 的缺点,谁也不知道到底这个翻译官水平如何

MusicKit

WWDC21 宣布了 MusicKit,WWDC22 宣布了其他平台的 MusicKit SDK 和 Apple Music API,至此 Apple Music 可以在其他平台集成,包括 Web

只不过,据我所知,Apple Music API 和 MusicKit Web 只有很普通的音质,没有 ALAC,没有 Dolby Atmos,其实后者没有应该不奇怪

Combine

按照平台分,方案如下:

  • iOS/iPadOS:系统带着了,直接写,可以参考 Xamarin.iOS 的示例
  • Android: 使用官方 SDK,一共两个 aar
  • 其他:只能封装 Apple Music API

这个项目在 RN 的时候也仅考虑 iOS 和 Android。这次会考虑新增 iPad 和桌面端,但无所谓

Create Library(s)

如果按照 React Native 的习惯,可能会创建一个插件项目,接着分别在对应位置做平台实现。但,Xamarin 似乎不是这么做的

首先需要创建两个 Binding 项目:Android Bindings LibraryiOS Bindings Library

毕竟只是概念验证,所以这次不实现 iOS,先试试 aar 绑定。这里可以参考微软文档 Binding an .AAR

在安卓,Apple Music 提供了两个 arr

  • mediaplayback:Apple Music 部分,用于获取专辑,音乐信息等
  • musickitauth: MusicKit 的验证部分(例如直接使用本地的 Apple Music 进行授权,类似通过 QQ 登录)

一个一个来

Droid.MusicKit - mediaplayback

(似乎 Xamarin 的插件大火都是这么命名的)

先处理 mediaplayback,按照文档,创建项目之后,把 aar 直接拖进项目,并设置「生成操作设置」

  1. 将 textanalyzer.aar 的生成操作设置为 LibraryProjectZip

Xamarin 虽然是这么设置的,但是在 Visual Studio 2022 已经没有这个选项了,取而代之的是 AndroidLibrary

然后就可以开始生成了,要不怎么说微软 nb 呢 —— .NET 会解析 arr 或者 jar ,将所有签名或者代码结构生成出来,并以类为单位生成一个个 C# 文件,作为 header file 使用

但,刚说什么来着?

谁也不知道到底这个翻译官水平如何

不出意外的话马上又要出意外了

寄!

error

mediaplayback 关于音乐部分是原生库(.so),应该是 JNI 部分依赖了 javacpp,而 javacpp 在翻译成 C# 的时候出问题了。尝试解决一下但无法解决,想抄作业全网搜索没有一个相似案例可以抄作业。如果直接用 kotlin 我会受这苦?

没办法只能退而求其次 —— 本地授权完获取 token 之后,剩下的走 Apple Music API。但即使可以的话,音质可能是收音机水平

Droid.MusicKit - musickitauth

跟 mediaplayback 一样,把 arr 拖进来,改成 AndroidLibrary 之后编译,很意外的非常顺利,只有几百个警告。在老程序员的眼里,1000 个警告都是无异常

接着,在 MAUI 的项目中打开「引用」目录,右键这个目录添加引用,把 Droid.MusicKit 添加进来,再编译一遍。没有报错的话就是没有报错

要不怎么说微软 nb 呢x2,在使用时,可以直接 using 包名

using Com.Apple.Android.Sdk.Authentication;

这里采用的方式是:先在公用逻辑写一半类,接着在对应的平台实现里完成各个平台调用

namespace MauiDemo.Services
{

    public partial class MusicKitService
    {
        public partial void Auth();
    }
}

接着到 android 目录,创建一个 MusicKitService.cs

namespace MauiDemo.Services
{
    public partial class MusicKitService
    {
        public partial void Auth()
        {
            // 当前 activity(如果有更好的方式的话欢迎告诉我)
            var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
            // 应用上下文(React Native 直接在 props 进来的那个)
            var context = Android.App.Application.Context;
            var authenticationManager = AuthenticationFactory.CreateAuthenticationManager(context);

            var developerToken = "";

            var intent = authenticationManager
                .CreateIntentBuilder(developerToken)
                .SetHideStartScreen(false)
                // 授权页面显示的告示信息
                .SetStartScreenMessage("要想听歌就赶紧授权!")
                .Build();

            // 跳转 activity(android 原生应该很熟悉)
            activity.StartActivityForResult(intent, 100);
        }
    }
}

最后随便绑定到项目创建时的 Demo 的按钮上,不出意外的话点击按钮就能显示 Apple Music 的授权页面(跳转到 Apple Music 或者让你安装一个)

总结

零零散散写了很久,文章周期很长。可能有上文不接下气的感觉

本来对 MAUI 寄予厚望,现在理性考虑还是不会使用

一方面是需要踩坑的地方目测得到的就有点多(如果之前没踩过 Xamarin 的话还得算上);一方面目前社区不活跃,有点冷门的实现自己搞不定

体感上会比印象中的 React Native 好很多(新解释器和 Hermes 还没用过),但开发成本和理解成本不在一个层面

总之,看还是会看,暗中观察。用的话现在不会用(但是体感上 Xamarin 真的要比 React Native 好,很纠结…