在 NativeScript 中扩展 Android 原生类

在 NativeScript 中扩展 Android 原生类 使用 NativeScript、React Native、Flutter 或 Ionic 等跨平台框架可以帮你避免编写 Java for Android 和 Objective-C for iOS 等原生代码从而省去很多麻烦。这些框架抽象了许多原生元素并为你提供了更清晰的接口和函数来使用。不过有时框架的能力还是会不够用。你的应用可能需要处理一些非常特殊的需求出于性能原因需要在原生层面处理或者单纯是因为只靠框架无法实现。在本文中我们将探讨 NativeScript 如何让你能够轻松扩展任何原生 Java 类主要介绍以下两种方法。方法一NativeScript 方式使用 TS/JS 绑定NativeScript 最独特的功能之一是原生类型编组marshalling这意味着大多数原生类和类型可以直接从你的 TypeScript/JavaScript 代码库中访问。NativeScript 自动处理 JavaScript 环境和原生环境之间的数据类型转换。这一特性在扩展原生类时起着至关重要的作用无需处理复杂的原生 Java 代码。你可以在 NativeScript 官方文档中找到关于编组和扩展 Android 的更多信息其中包含有关 JS 和原生之间如何映射原生数据的非常有用的信息https://docs.nativescript.org/guide/android-marshallinghttps://docs.nativescript.org/guide/extending-classes-and-implementing-interfaces-android教我怎么做通过.extend()函数如果你喜欢函数式风格可以调用任何原生类的extend()方法来从中创建一个子类。在 Java 中类是按照被称为包名的目录组织的。在 NativeScript 中一个原生 Java 类可以通过它的完整包名和类名来引用。以下是一个扩展代码示例(anyandroidx.appcompat.app.AppCompatActivity).extend(com.newbiescripter.MainActivity,{onCreate(savedInstanceState:android.os.Bundle):void{this.super.onCreate(savedInstanceState)},onNewIntent(intent:android.content.Intent):void{// ..},onSaveInstanceState(outState:android.os.Bundle):void{// ..},onStart():void{// ..},onStop():void{// ..},// 如果需要重写任何其他方法// ...});如果你不确定包名可以随时在 Android 开发者参考文档中查看。例如这是 Activity 类的文档查看它的类层次结构就能看到完整包名。通过NativeClass和JavaProxy装饰器如果你喜欢更干净、更自然的类语法你可能会想看看 NativeScript 提供的装饰器NativeClass和JavaProxy。下面是执行与前面示例完全相同操作的示例代码NativeClassJavaProxy(com.newbiescripter.MainActivity)classMainActivityextends(anyandroidx.appcompat.app.AppCompatActivity){onCreate(savedInstanceState:android.os.Bundle):void{this.super.onCreate(savedInstanceState)}onNewIntent(intent:android.content.Intent):void{}onSaveInstanceState(outState:android.os.Bundle):void{}onStart():void{}onStop():void{}// 如果需要重写任何其他方法// ...};JavaProxy让你有机会在 Java 世界中为你的类命名以便你可以在代码库的其他部分引用它。虽然你可以完全省略JavaProxy这样你的类就会变成匿名的但我更喜欢给自己的类命名。这两种方法的结果是相同的它们的区别只在于编写风格因此你可以根据自己的喜好进行选择。最终代码将在构建时被编译成 Java 并存储在platforms/android文件夹中。NativeScript 扩展类被转换为 Java 代码。一个有趣的地方是在生成的 Java 类中你看不到你的 TypeScript/JavaScript 代码的实现。它实际上只包含一小段处理传入参数的 Java 代码然后将它们发送回 JavaScript 世界最终调用你的 JavaScript 方法。这很酷吧有什么需要注意的吗如果你做错了什么幸运的话你会在构建时看到编译器抛出错误。但有时它会一直静默无闻直到运行时出现意外错误。以下是一些在调试时常让我抓耳挠腮的常见问题必须是 Android 的原生类例如androidx.appcompat.app.AppCompatActivity可以使用。或者是通过app.gradle安装的包里的类。确保这个类存在。避免扩展已经被扩展过的类尝试扩展一个已经使用上述方法扩展过的类将不起作用。尝试扩展编译时类如 NativeScript 生成的 Activity 类com.tns.NativeScriptActivity将不起作用。必须被包含才能生成包含扩展原生类代码的.ts/.js文件必须直接包含在 webpack 的appComponents参数中或者通过被包含在appComponents中的文件间接导入这个.ts/.js文件。请注意默认情况下app.ts/js已被包含。如果该文件未被任何其他.ts文件导入则会被视为未使用在构建时不会被生成。必须执行一次ns build android这是生成原生.java类文件及其绑定所必需的。否则不会生成.java文件并且在运行时会抛出ClassNotFoundException错误。注意单独执行ns run android并不总能触发此操作。90% 的问题在于原生.java类文件未生成如果你不确定可以导航到platforms/android/app/src/main/java文件夹并查找你的自定义类。在 Java 中类是按其包名组织的因此com.newbiescripter.CustomClass将位于app/src/main/java/com/newbiescripter/CustomClass.java… 这也是我建议给你的类命名并避免使用匿名类的原因这样在platforms文件夹中查找它会更容易。方法二原生对原生原生 Java 方式如果你是一位硬核 Java 开发者更喜欢在较低级别进行操作这对你来说可能更直接。从技术上讲放在你项目App_Resources/Android/src里面的任何东西在后面都会被复制到platforms/android/app/src并编译进最终的应用程序包中。所以我们可以利用这一点来实际编写 Java 代码并在其中扩展其他类。这是一个快速的 Java 代码示例packagecom.newbiescripter;importandroid.content.Context;importandroid.widget.Button;publicclassCustomButtonextendsButton{publicCustomButton(Contextcontext){super(context);}OverridepublicvoidsetEnabled(booleanenabled){super.setEnabled(enabled);}}我们将此文件放置在App_Resources/Android/src/main/java/com/newbiescripter/CustomButton.java虽然路径有点深……。在构建时你会看到它被复制到platforms/android文件夹如下所示这种方法的优点是你的类是 100% 原生的没有编组marshalling的过程如果代码计算密集这可能会带来更好的性能。小结总之你可以在这两种方法中选择一种找到更适合你和你的团队的方法。就我个人而言我更喜欢方法二因为它让我能舒适地待在 JavaScript 这边这也充分发挥了 NativeScript 的真正威力。https://newbiescripter.com/extending-androids-native-classes-in-nativescript-like-a-pro/