手把手第十二篇:在 FinClip 小程序中引入自有 API

在上期文章中,我们主要将 FinClip SDK 引入了自有 App。本期文章中,我们将一起聊聊如何在 FinClip 小程序中引入自有 API。

本期文章属于《手把手系列教学》的第十二篇,如果你还不太了解这一系列,可以点击 这里 查看详情。

什么是自定义 API 及其有何作用

自定义 API,顾名思义为开发者为满足自身需求而自己创建的一个 API。那么自己创建的这个 API 能起到什么效果和作用呢。

如果小程序里需要调用一些宿主 App 提供的能力,而 FinClip SDK 未实现或无法实现时,就可以注册一些自定义 API。然后小程序里就可以像调用其他 API 一样调用注册的 API 了。

简单来说这个自定义 API 能起到小程序或者 H5 与原生 App 的交互作用。

注册自定义 API 分两个场景:

  1. 注册给原生小程序使用的自定义 API;
  2. 注册给小程序中 Web-view 组件加载的 H5 使用的自定义 API。

如何注册及使用 FinClip 小程序自定义 API

1. iOS 端注册小程序自定义 API

注册自定义的小程序 API 的函数如下所示:

/**
 注册扩展Api
 
 @param extApiName 扩展的api名称
 @param handler 回调
 @return 返回注册结果
 */
- (BOOL)registerExtensionApi:(NSString *)extApiName handle:(void (^)(id param, FATExtensionApiCallback callback))handler;

比如,我这里注册一个小程序 APIcustomEvent:

[[FATClient sharedClient] registerExtensionApi:@"customEvent" handle:^(id param, FATExtensionApiCallback callback) {
    // xxxx
    callback(FATExtensionCodeSuccess, nil);
}];

然后,在小程序的根目录创建 FinClipConf.js 文件,配置实例如下:

module.exports = {
  extApi:[
    { //普通交互API
      name: 'customEvent', //扩展api名 该api必须Native方实现了
      params: { //扩展api 的参数格式,可以只列必须的属性
        url: ''
      }
    }
  ]
}
extApi 是个数组,所以,您可以注册多个自定义API。

最后,在小程序里调用自定义的 API,示例代码:

ft.customEvent({
    url:'https://www.baidu.com',
    success: function (res) {
        console.log("调用customEvent success");
        console.log(res);
    },
    fail: function (res) {
        console.log("调用customEvent fail");
        console.log(res);
    }
});

2. iOS 端注册小程序 web-view 组件 API

小程序里加载的 H5,如果也想调用宿主 API 的某个能力,就可以利用该方法注册一个 API。

/// 为HTML 注册要调用的原生 api
/// @param webApiName 原生api名字
/// @param handler 回调
- (BOOL)fat_registerWebApi:(NSString *)webApiName handle:(void (^)(id param, FATExtensionApiCallback callback))handler;

我这里为小程序里的 H5 注册了一个叫 js2AppFunction 的方法:

 [[FATClient sharedClient] fat_registerWebApi:@"js2AppFunction" handle:^(id param, FATExtensionApiCallback callback) {
        NSString *name = param[@"name"];
//        id params = param[@"data"];
        if ([name isEqualToString:@"getLocation"]) {
            // 执行定位逻辑
            
            // 返回结果给HTML
            NSDictionary *dict = @{@"errno":@"403", @"errmsg":@"无权限", @"result": @{@"address":@"广东省深圳市南山区航天科技广场"}};
            callback(FATExtensionCodeSuccess, dict);
        } else if ([name isEqualToString:@"getColor"]) {
            // 执行其他逻辑
            
            // 返回结果给HTML
            NSDictionary *dict = @{@"r":@"110",@"g":@"150",@"b":@"150"};
            callback(FATExtensionCodeSuccess, dict);
        }
    }];

在 H5 内引用我们的桥接 JSSDK 文件,即可调用上面的注册的方法了。

HTML 内调用注册的方法示例:

window.ft.miniProgram.callNativeAPI('js2AppFunction', {name:'getLocation'}, (result) => {
    console.log(result)
});
3. Android 端注册小程序自定义API

自定义 API 示例:

public class CustomApi extends BaseApi {

    public CustomApi(Context context) {
        super(context);
    }

    @Override
    public String[] apis() {
        return new String[]{"customEvent"}; //api名称
    }

    @Override
    public void invoke(String event, JSONObject param, ICallback callback) {
        // 调用方法时原生对应的操作
    }
}

然后将其注册到 extensionApiManager 中,支持单个注册和批量注册。

Kotlin

单个注册

FinAppClient.extensionApiManager.registerApi(CustomApi(this)) 

批量注册

val apis = listOf<IApi>(CustomApi1(), CustomApi2(), CustomApi3())
FinAppClient.extensionApiManager.registerApis(apis) 

Java

单个注册

FinAppClient.INSTANCE.getExtensionApiManager().registerApi(new CustomApi(this)); 

批量注册

List<IApi> apis = new ArrayList<>();

IApi customApi1 = new CustomApi1();
apis.add(customApi1);

IApi customApi2 = new CustomApi2();
apis.add(customApi2);

IApi customApi3 = new CustomApi3();
apis.add(customApi3);

FinAppClient.INSTANCE.getExtensionApiManager().registerApis(apis); 

然后,在小程序的根目录创建 FinClipConf.js 文件,配置实例如下:

module.exports = {
  extApi:[
    { //普通交互API
      name: 'customEvent', //扩展api名 该api必须Native方实现了
      params: { //扩展api 的参数格式,可以只列必须的属性
        url: ''
      }
    },
    {
        name: 'customEvent1',
        params: {
            foo: ''
        }
    },
    {
        // foo
    }
  ]
}

最后,在小程序里调用自定义的 API,示例代码:

ft.customEvent({
    url:'https://www.xxx.com',
    success: function (res) {
        console.log("customEvent call succeeded");
        console.log(res)
    },
    fail: function (res) {
        console.log("customEvent call failed");
        console.log(res)
    }
})
4. Android 端注册小程序 web-view 组件 API

小程序里加载的 H5,如果也想调用宿主 API 的某个能力,就可以利用该方法注册一个 API。

public class WebApi extends BaseApi {

    public WebApi(Context context) {
        super(context);
    }

    @Override
    public String[] apis() {
        return new String[]{"webApiName"}; //api名称
    }

    @Override
    public void invoke(String event, JSONObject param, ICallback callback) {
        // 调用方法时原生对应的操作
    }
}

然后将其注册到 extensionWebApiManager 中,同样也支持单个注册和批量注册。

Kotlin

单个注册

FinAppClient.extensionWebApiManager.registerApi(WebApi(this)) 

批量注册

val apis = listOf<IApi>(WebApi1(), WebApi2(), WebApi3())
FinAppClient.extensionWebApiManager.registerApis(apis)

Java

单个注册

FinAppClient.INSTANCE.getExtensionWebApiManager().registerApi(new WebApi(this)); 

批量注册

List<IApi> apis = new ArrayList<>();

IApi webApi1 = new WebApi1();
apis.add(webApi1);

IApi webApi2 = new WebApi2();
apis.add(webApi2);

IApi webApi3 = new WebApi3();
apis.add(webApi3);

FinAppClient.INSTANCE.getExtensionWebApiManager().registerApis(apis); 

在 H5 内引用我们的桥接 JSSDK 文件,即可调用上面的注册的方法了。

HTML 内调用注册的方法示例:

window.ft.miniProgram.callNativeAPI('js2AppFunction', {name:'getLocation'}, (result) => {
    console.log(result)
});

FinClip 小程序自定义 API 常见问题

1. 为什么注册的自定义小程序 API 不起作用

在注册自定义 API 时,会判断当前的小程序 SDK 是否初始化成功了。如果没有初始化成功,那么注册自定义 API 就不会成功。

所以,注册自定义 API 前,一定要保证小程序已经初始化成功了。

2. 在 FinClip FIDE 中如何 mock 使用自定义 API

在 FIDE 中,有 mock 功能可以方便开发者在开发的途中 mock 模拟自定义 API 的返回结果。如下图:

在 mock 中定义 API 接口字段及返回结果(需要注意的是,这里的 JSON 数据包的返回结果需要的是双引号"")然后在小程序根目录下。

然后,在小程序的根目录创建 FinClipConf.js 文件,配置实例如下:

module.exports = {
  extApi: [{
    name: 'kkshy',
  }]
}

最后就是小程序中的调用

ft.kkshy({
      success: function(res) {
        console.log("success");
        console.log(res);
      },
      fail: function(res) {
        console.log("fail");
        console.log(res);
      }
    });

3. 使用 Flutter 接入的话,自定义 API 是否支持通过 Success 方法接收 Flutter 的回调数据

答案是支持的。

typedef ExtensionApiHandler = Future Function(dynamic params)

自定义的方法返回的结果会返回给小程序

4. 在自定义接口的 invoke() 方法中跳转到宿主 App 的其它页面,做完一系列操作之后,按系统返回键想返回小程序,结果却返回到了宿主 App 中启动小程序的页面,为什么?

原因:

跳转到宿主App其它页面这一步,是通过宿主App中的Context实例来启动Activity的,并且没有把Activity压入新的任务栈中。

Android小程序SDK是多进程架构的,小程序和宿主App处于不同进程中,所处的任务栈自然也是不同的。小程序跳转到宿主App的页面,新打开的页面是添加到宿主App原有的任务栈中的,当从页面返回时,执行的逻辑是在原生App中原有的任务栈中弹出页面,因此会看到原生App的页面被逐个关闭,最后返回到原生应用启动小程序的页面,并没有返回小程序。

解决方案共有 2 种:

方案 1(推荐):

通过 ICallback 的 startActivity 或 startActivityForResult 来跳转到宿主 App 的其它页面。

这是推荐的方案,因为这样做是在小程序所在的任务栈打开新宿主 App 的 Activity 的,Activity 的入栈出栈都是在同一个任务栈中完成的,没有任务栈切换的过程。

更重要的一个原因是:如果需要通过 startActivityForResult 来启动 Activity 并在页面返回时获取到回传的数据,只有使用这种方案,自定义接口的 onActivityResult 才会执行,才能拿到返回的数据。

此方案使用示例:

@Override
public void invoke(String event, JSONObject param, ICallback callback) {
    Intent intent = new Intent();
    intent.setClass(mContext, SecondActivity.class);
    callback.startActivityForResult(intent, 100);
}

方案 2(不推荐):

如果一定要使用宿主 App 中的 Context实例来启动 Activity,就需要对启动原生页面的 Intent 设置"支持多任务栈"和“开启新任务栈”的 Flag,这样可以在原生 App 的进程中新开一个任务栈,开启新任务栈之后,新打开的页面将被逐个压入这个新任务栈中,当结束完原生页面的所有操作之后逐个页面返回时,便会从这个新任务栈中将页面逐个弹出,当这个新任务栈中的所有页面都被弹出后,便会回到小程序进程的任务栈。

因此,在自定义接口的 invoke() 方法中,如果需要跳转到原生应用的其它页面执行某些操作,并期望当关闭这些原生页面后能够返回小程序,那么建议在执行跳转的时候为 Intent 对象同时增加 Intent.FLAG_ACTIVITY_MULTIPLE_TASK 和 FLAG_ACTIVITY_NEW_TASK,如下:

Intent intent = new Intent();
intent.setClass(context, SecondActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent); // context是宿主App中的Context实例

使用此方案,如果通过 startActivityForResult 来启动 Activity,当页面返回时,自定义接口的 onActivityResult 不会被调用,因此不推荐。

5. Taro 中如何给打包后文件添加 FinChatConf.js

taro中可以使用 copy配置项,将 FinChatConf.js 复制到打包后的文件之中,具体写法可参考如下:

module.exports = {
  // ...
  copy: {
    patterns: [
      { from: 'FinChatConf.js', to: 'dist/FinChatConf.js' } // 指定需要 copy 的文件
    ]
  }
}
具体可参考 taro 文档http://taro-docs.jd.com/taro/docs/config-detail#copy

FinClip 小程序自定义 API 示例

自定义 API 相关示例代码可见:https://github.com/finogeeks/auth_demo_android

本期教程我们讨论了如何在自有小程序中引入自定义 API,后续我们也会不定时更新 FinClip 相关的内容,敬请期待。