在 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 和原生之间如何映射原生数据的非常有用的信息:

教我怎么做

通过 .extend() 函数

如果你喜欢函数式风格,可以调用任何原生类的 extend() 方法来从中创建一个子类。在 Java 中,类是按照被称为包名的目录组织的。在 NativeScript 中,一个原生 Java 类可以通过它的完整包名和类名来引用。

以下是一个扩展代码示例:

typescript 复制代码
(<any>androidx.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

下面是执行与前面示例完全相同操作的示例代码:

typescript 复制代码
@NativeClass
@JavaProxy('com.newbiescripter.MainActivity')
class MainActivity extends (<any>androidx.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 代码示例:

java 复制代码
package com.newbiescripter;

import android.content.Context;
import android.widget.Button;

public class CustomButton extends Button {
    public CustomButton(Context context) {
        super(context);
    }

    @Override
    public void setEnabled(boolean enabled) {
        super.setEnabled(enabled);
    }
}

我们将此文件放置在 App_Resources/Android/src/main/java/com/newbiescripter/CustomButton.java(虽然路径有点深......)。在构建时,你会看到它被复制到 platforms/android 文件夹,如下所示:

这种方法的优点是,你的类是 100% 原生的,没有编组(marshalling)的过程,如果代码计算密集,这可能会带来更好的性能。

小结

总之,你可以在这两种方法中选择一种,找到更适合你和你的团队的方法。就我个人而言,我更喜欢方法二,因为它让我能舒适地待在 JavaScript 这边,这也充分发挥了 NativeScript 的真正威力。

newbiescripter.com/extending-a...

相关推荐
sp423 小时前
在 NativeScript-Vue 中实现流畅的共享元素转场动画
app·nativescript
sp42a8 小时前
将 NativeScript 项目升级到 Android API 35 级别
android·nativescript
Kingexpand_com2 天前
任务分发管理APP开发全解析|技术选型、架构设计与落地实践(旌展技术分享)
app·软件开发·app开发·app定制开发·app定制开发公司
Kingexpand_com4 天前
物联网APP开发实战:如何打造用户真正愿意用的智能硬件伴侣
物联网·小程序·app·智能硬件·物联网app定制开发
summerkissyou19877 天前
Android-view-绘制流程及自定义例子
android·app
aykon13 天前
SparseArray详解,SparseArray和HashMap性能、内存对比
app
Haha_bj19 天前
Flutter——状态管理 Provider 详解
flutter·app
QING61821 天前
使用ADB分析CPU性能 —— 基础指南
android·前端·app
Haha_bj21 天前
Flutter——List.map()
flutter·app