Android 常见问题
1. 集成时常见问题
1.1 集成SDK要求的最低环境配置是什么?
答:集成SDK要求Android SDK版本为19(Android 4.4)及以上。
1.2 具体的集成步骤是怎样的?
答:请参考 SDK集成文档
1.3 集成SDK是否需要配置混淆规则?
答:需要配置,混淆规则如下:
-keep class com.finogeeks.** {*;}
1.4 初始化SDK的时候,有哪些参数是必须配置的?
答:初始化SDK时至少需要传入SDK Key、SDK Secret、服务器地址、服务器接口请求路由前缀、加密方式,通过FinAppConfig
实例传入配置参数。
2.13.102
版本之前,SDK仅支持配置一个服务器信息,只能打开单个环境中的小程序,如下:
FinAppConfig config = new FinAppConfig.Builder()
.setSdkKey(BuildConfig.SDK_KEY) // SDK Key,增加合作应用成功后由平台签发
.setSdkSecret(BuildConfig.APP_SECRET) // SDK Secret,增加合作应用成功后由平台签发
.setApiUrl(BuildConfig.API_URL) // 服务器地址
.setApiPrefix(BuildConfig.API_PREFIX) // 服务器接口请求路由前缀
.setEncryptionType(ENCRYPTION_TYPE_SM) // 加密方式,国密:SM,md5: MD5
.build();
从2.13.102
版本开始支持配置多个服务器信息,可以同时打开不同环境中的小程序,如下:
// 服务器信息集合
List<FinStoreConfig> storeConfigs = new ArrayList<>();
// 服务器1的信息
FinStoreConfig storeConfig1 = new FinStoreConfig(
"SDK Key信息", // SDK Key
"SDK Secret信息", // SDK Secret
"服务器1的地址", // 服务器地址
"服务器1的数据上报服务器地址", // 数据上报服务器地址
"/api/v1/mop/", // 服务器接口请求路由前缀
"",
"加密方式" // 加密方式,国密:SM,md5: MD5
);
storeConfigs.add(storeConfig1);
// 服务器2的信息
FinStoreConfig storeConfig2 = new FinStoreConfig(
"SDK Key信息", // SDK Key
"SDK Secret信息", // SDK Secret
"服务器2的地址", // 服务器地址
"服务器2的数据上报服务器地址", // 数据上报服务器地址
"/api/v1/mop/", // 服务器接口请求路由前缀
"",
"加密方式" // 加密方式,国密:SM,md5: MD5
);
storeConfigs.add(storeConfig2);
FinAppConfig config = new FinAppConfig.Builder()
.setFinStoreConfigs(storeConfigs) // 服务器信息集合
.build();
1.5 初始化SDK的时候,有哪些参数是选择性配置的?
答:除了上面提到的必须配置的参数,其它参数都是根据需要自行选择配置的,主要包括UI配置、灰度发布规则配置。
UI配置如下:
// UI配置
FinAppConfig.UIConfig uiConfig = new FinAppConfig.UIConfig();
// 是否隐藏右上角关闭按钮
uiConfig.setHideNavigationBarCloseButton(false);
// 是否隐藏"更多"菜单中的"转发"按钮
uiConfig.setHideForwardMenu(false);
// 是否隐藏更多菜单中的"反馈与投诉"
uiConfig.setHideFeedbackAndComplaints(false);
// 是否隐藏更多菜单中的"返回首页"
uiConfig.setHideBackHome(false);
// 当导航栏为默认导航栏时,是否始终显示返回按钮
uiConfig.setAlwaysShowBackInDefaultNavigationBar(false);
// 导航栏标题文字样式
uiConfig.setNavigationBarTitleTextAppearance(R.style.TextAppearance_AppCompat);
// 导航栏标题相对父控件的Gravity
uiConfig.setNavigationBarTitleTextLayoutGravity(Gravity.CENTER);
// 是否清除导航栏导航按钮的背景
uiConfig.setClearNavigationBarNavButtonBackground(true);
// "更多"菜单样式
uiConfig.setMoreMenuStyle(UIConfig.MORE_MENU_DEFAULT);
// 胶囊按钮配置
uiConfig.setCapsuleConfig(new CapsuleConfig());
APM数据上报扩展信息如下:
// APM数据上报扩展信息
Map<String, Object> apmExtendInfo = new HashMap<>();
apmExtendInfo.put("key1", "value1");
apmExtendInfo.put("key2", "value2");
可选参数通过FinAppConfig
实例和必配参数一起传给SDK,如下:
// UI配置
FinAppConfig.UIConfig uiConfig = new FinAppConfig.UIConfig();
// 是否隐藏右上角关闭按钮
uiConfig.setHideNavigationBarCloseButton(false);
// 是否隐藏"更多"菜单中的"转发"按钮
uiConfig.setHideForwardMenu(false);
// 是否隐藏更多菜单中的"反馈与投诉"
uiConfig.setHideFeedbackAndComplaints(false);
// 是否隐藏更多菜单中的"返回首页"
uiConfig.setHideBackHome(false);
// 当导航栏为默认导航栏时,是否始终显示返回按钮
uiConfig.setAlwaysShowBackInDefaultNavigationBar(false);
// 导航栏标题文字样式
uiConfig.setNavigationBarTitleTextAppearance(R.style.TextAppearance_AppCompat);
// 导航栏标题相对父控件的Gravity
uiConfig.setNavigationBarTitleTextLayoutGravity(Gravity.CENTER);
// 是否清除导航栏导航按钮的背景
uiConfig.setClearNavigationBarNavButtonBackground(true);
// "更多"菜单样式
uiConfig.setMoreMenuStyle(UIConfig.MORE_MENU_DEFAULT);
// 胶囊按钮配置
uiConfig.setCapsuleConfig(new CapsuleConfig());
// APM数据上报扩展信息
Map<String, Object> apmExtendInfo = new HashMap<>();
apmExtendInfo.put("key1", "value1");
apmExtendInfo.put("key2", "value2");
// 需要移除Cookies的域名
List<String> needToRemoveCookiesDomains = new ArrayList<>();
needToRemoveCookiesDomains.add("https://aaa.bbb.ccc");
FinAppConfig config = new FinAppConfig.Builder()
.setSdkKey(BuildConfig.SDK_KEY) // SDK Key,增加合作应用成功后由平台签发
.setSdkSecret(BuildConfig.APP_SECRET) // SDK Secret,增加合作应用成功后由平台签发
.setApiUrl(BuildConfig.API_URL) // 服务器地址
.setApiPrefix(BuildConfig.API_PREFIX) // 服务器接口请求路由前缀
.setDebugMode(BuildConfig.DEBUG) // 应用当前是否是Debug模式
.setUiConfig(uiConfig) // UI配置
.setApmExtendInfo(apmExtendInfo) // APM数据上报扩展信息
.setEncryptionType(ENCRYPTION_TYPE_SM) // 加密方式,国密:SM,md5: MD5
.setDisableRequestPermissions(true) // 否禁止发起运行时权限申请,默认不禁止
.setNeedToRemoveCookiesDomains(needToRemoveCookiesDomains) // 需要移除Cookies的域名
.setDisableTbs(true) // 是否禁止禁止启用Tbs SDK,默认不禁止
.setCustomWebViewUserAgent("aaa/111; bbb") // 自定义WebView UserAgent
.setAppletIntervalUpdateLimit(6) // 定时批量更新小程序的数量
.setForegroundServiceConfig(new FinAppConfig.ForegroundServiceConfig(true, R.drawable.ic_launcher, "小程序正在运行", "")) // 是否在小程序前台运行时启动前台服务
.setBindAppletWithMainProcess(true) // 小程序与app进程绑定,App被杀死,小程序是否同步关闭,默认为false
.setWebViewMixedContentMode(MIXED_CONTENT_ALWAYS_ALLOW) // 设置WebView mixed content mode
.setAppletText("X应用") // 替换“小程序”文案,把SDK中的“小程序”文案替换为“X应用”
.setEnableApmDataCompression(true) // 上报数据时是否对数据进行压缩,默认不压缩
.setDisableGetSuperviseInfo(true) // 是否禁止调用获取监管信息的小程序API,默认不禁止
.setUserId("用户ID") // 设置用户ID
.build();
1.6 初始化SDK的时候,有没有需要特别注意的地方?
答:SDK采用多进程机制实现,每个小程序运行在独立的进程中,即一个小程序对应一个进程,在初始化SDK时,要特别注意的一点是:小程序进程在创建的时候不需要执行任何初始化操作,即使是小程序SDK的初始化,也不需要在小程序进程中执行。
例如:应用使用了一些第三方库,这些库需要在应用启动时先初始化,那么,在Application
中执行初始化时,只有当前进程为宿主进程时才需要初始化这些第三方库,小程序进程是不需要初始化这些库的。
因此,在初始化SDK之前,一定要判断当前进程是哪一个进程,如果是小程序进程,并且不需要处理跨进程调用接口 (opens new window)或是在小程序进程中注册api (opens new window),就不进行任何操作了:
/**
* 应用的{@link android.app.Application}
*/
public class AppletApplication extends MultiDexApplication {
@Override
public void onCreate() {
super.onCreate();
// 判断是否为小程序进程的代码放在最前面
if (FinAppClient.INSTANCE.isFinAppProcess(this)) {
return;
}
// 其它代码放在后面
…………………………………………
…………………………………………
}
}
1.7 集成时遇到依赖冲突问题如何解决?
答:Android运行时SDK 依赖的第三方库
// appcompat-v7
implementation "com.android.support:appcompat-v7:23.0.0"
// support-v4
implementation "com.android.support:support-v4:23.0.0"
// Recyclerview
implementation "com.android.support:recyclerview-v7:23.2.0"
// Kotlin
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.3.61"
// Gson
implementation "com.google.code.gson:gson:2.2.2"
// zxing
implementation "com.google.zxing:core:3.3.0"
implementation "com.google.zxing:android-core:3.3.0"
// SdkCore
implementation "com.finogeeks.finochat.sdk:sdkcore:2.15.1"
sdk的依赖使用gradle管理,当app与sdk依赖了相同的库时,gradle会自动处理冲突,使用版本号较高的库。
如果app里通过jar包或源码依赖的库与sdk发生冲突,这时可以通过exclude命令剔除某个sdk的依赖。
例如:
implementation('com.finogeeks.lib:finapplet:2.21.1') {
exclude group: "com.tecent.smtt"
}
另外,还是建议使用gradle管理依赖比较好
2. 使用时常见问题
2.1 怎么启动小程序?
- 如果启动小程序时不携带启动参数,则通过调用
IAppletApiManager
接口的startApplet(context: Context, appId: String)
方法启动,如下:
FinAppClient.INSTANCE.getAppletApiManager().startApplet(context, "appId");
- 如果启动小程序时携带启动参数,则通过调用
IAppletApiManager
接口的startApplet(context: Context, appId: String, startParams: Map<String, String>)
方法启动,如下:
Map<String, String> params = new HashMap<>();
// path为小程序页面路径
params.put("path", "/pages/index/index");
// query为启动参数,内容为"key1=value1&key2=value2 ..."的形式
params.put("query", "aaa=\"test\"&bbb=\"123\"");
FinAppClient.INSTANCE.getAppletApiManager().startApplet(this, "appId", params);
2.2 在宿主应用中启动多个小程序之后,为什么在Android系统的最近任务栏列表里面除了宿主应用外,还会看到多个小程序任务?
答:因为Android小程序SDK采用多进程机制实现,宿主应用是一个进程,而每个小程序也分属一个独立的进程,在Android系统的最近任务栏中,不同的进程会分别展示出来,因此会看到多个被打开的小程序。
当下主流的小程序,例如Android版的微信小程序、Android版的百度智能小程序亦如此。
2.3 小程序接口是否支持扩展?即除了SDK内部提供的一系列标准接口之外,能否让小程序调用自己提供的接口?如果可以要怎么实现?
答:小程序接口支持扩展。SDK允许注册自定义的小程序接口,注册之后的自定义接口和SDK内部的标准接口一样,可以供小程序调用。
具体实现步骤如下:
- 实现自定义小程序接口。
IApi
声明了小程序接口需要实现的两个最核心的方法,即apis();
和invoke(String event, JSONObject param, ICallback callback);
,具体代码如下:
/**
* 小程序API接口,实现相应功能的API需实现此接口
*/
public interface IApi extends ILifecycle {
/**
* @return 可调用API的名称的数组
*/
String[] apis();
/**
* 接收到API调用时,会触发此方法,在此方法中实现具体的业务逻辑
*
* @param event 事件名称,即API名称
* @param param 参数
* @param callback 回调接口
*/
void invoke(String event, JSONObject param, ICallback callback);
}
实现自定义接口需要创建一个类,并让这个类继承IApi
的子类AbsApi
,然后重写apis()
方法和invoke()
方法。apis()
返回所有可调用API的名称的数组,通过实现apis()
声明所有需要实现的API。
invoke()
则会在小程序调用API时被触发,其中的参数event是API的名称,param是和API对应的参数,callback则用于执行完调用逻辑后给小程序回调数据。
例如,我们自定义一个小程序接口,用于实现简单的页面跳转功能,代码如下:
/**
* 自定义小程序接口,实现一个简单的页面跳转功能
*/
public class ApiOpenPage extends AbsApi {
private static final String API_NAME_OPEN_PAGE = "openPage";
private Context mContext;
public ApiOpenPage(Context context) {
mContext = context;
}
/**
* @return 可调用API的名称的数组
*/
@Override
public String[] apis() {
return new String[]{API_NAME_OPEN_PAGE};
}
/**
* 接收到API调用时,会触发此方法,在此方法中实现具体的业务逻辑
*
* @param event 事件名称,即API的名称
* @param param 参数
* @param callback 回调接口
*/
@Override
public void invoke(String event, JSONObject param, ICallback callback) {
if (API_NAME_OPEN_PAGE.equals(event)) {
String url = param.optString("url");
if (!TextUtils.isEmpty(url)) {
Intent intent = new Intent();
intent.setClass(mContext, SecondActivity.class);
if (!(mContext instanceof Activity)) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
mContext.startActivity(intent);
callback.onSuccess(null);
} else {
callback.onFail();
}
}
}
}
- 注册自定义小程序接口。
通过调用IExtensionApiManager
接口的registerApi
方法实现注册。如下:
FinAppClient.INSTANCE.getExtensionApiManager().registerApi(new ApiOpenPage(context));
如果需要一次注册多个接口,则可以调用registerApis
,传入的参数为接口集合。
- 在小程序工程中增加扩展接口配置。SDK注册自定义小程序接口后,还需要在小程序工程的根目录创建
FinClipConf.js
文件,在FinClipConf.js
中配置对应的自定义接口。配置示例如下:
module.exports = {
extApi:[
{
name: 'openPage', // 扩展接口名
params: { // 扩展接口参数,可以只列必须的参数
url: '',
title: '',
description: ''
}
}
]
}
- 在小程序中调用已注册接口中的各个API。
2.4 小程序加载网页时,网页可以调用原生的代码吗?
答:可以在小程序的网页中调用原生代码。JSSDK提供了一系列接口,通过这些接口可以实现在网页里面调用原生代码。
2.5 JSSDK接口是否支持扩展?即除了JSSDK内部提供的一系列标准接口之外,能否让小程序在网页中调用自己提供的接口?如果可以要怎么实现?
答:JSSDK接口支持扩展。SDK允许注册自定义的JSSDK接口,注册之后的自定义JSSDK接口和JSSDK内部提供的标准接口一样,可以供小程序在网页中调用。
具体实现步骤如下:
- 实现自定义JSSDK接口。
这一步和实现自定义小程序接口是一样的,通过继承AbsApi
,并重写apis()
方法和invoke()
方法实现自定义JSSDK接口。 - 注册自定义JSSDK接口。
通过调用IExtensionWebApiManager
接口的registerApi
方法实现注册。如下:
FinAppClient.INSTANCE.getExtensionWebApiManager().registerApi(new ApiOpenPage(context));
如果需要一次注册多个接口,则可以调用registerApis
,传入的参数为接口集合。
详细可以查看注册小程序web-view组件API (opens new window)。
2.6 在自定义接口的invoke()
方法中跳转到宿主App的其它页面,做完一系列操作之后,按系统返回键想返回小程序,结果却返回到了宿主App中启动小程序的页面,为什么?
原因:
跳转到宿主App其它页面这一步,是通过宿主App中的Context实例来启动Activity的,并且没有把Activity压入新的任务栈中。
Android小程序SDK是多进程架构的,小程序和宿主App处于不同进程中,所处的任务栈自然也是不同的。小程序跳转到宿主App的页面,新打开的页面是添加到宿主App原有的任务栈中的,当从页面返回时,执行的逻辑是在原生App中原有的任务栈中弹出页面,因此会看到原生App的页面被逐个关闭,最后返回到原生应用启动小程序的页面,并没有返回小程序。
解决方案:
方案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); }
方案二(不推荐):
如果一定要使用宿主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
不会被调用,因此不推荐。
2.7 在原生代码中可以调用网页中的JS函数吗?
答:SDK支持原生代码调用js函数。通过调用IAppletApiManager
接口的callJS
方法即可实现调用,如下:
JSONObject funcParams = new JSONObject();
try {
funcParams.put("param1", "value1");
funcParams.put("param2", "value2");
FinAppClient.INSTANCE.getAppletApiManager().callJS(
"appId",
"funcName",
funcParams.toString(),
-1,
new FinCallback<String>() {
@Override
public void onSuccess(String result) {
Log.d(TAG, "callJS onSuccess : " + result);
}
@Override
public void onError(int code, String error) {
Log.d(TAG, "callJS onError : " + code + ", " + error);
}
@Override
public void onProgress(int status, String info) {
}
});
} catch (JSONException e) {
e.printStackTrace();
}
2.8 怎么设置小程序中Activity的转场动画?
答:通过调用IAppletApiManager
接口的setActivityTransitionAnim
方法设置小程序中Activity的转场动画,如下:
FinAppClient.INSTANCE.getAppletApiManager().setActivityTransitionAnim(SlideFromRightToLeftAnim.INSTANCE);
目前提供了五种动画可供设置:
- NoneAnim:无动画;
- SlideFromLeftToRightAnim:滑动动画-左进右出;
- SlideFromRightToLeftAnim:滑动动画-右进左出;
- SlideFromTopToBottomAnim:滑动动画-上进下出;
- SlideFromBottomToTopAnim:滑动动画-下进上出。
2.9 怎么分享小程序到微信等支持小程序的平台?
答:要实现小程序分享功能,总体思路是先获取到分享小程序所需要的相关信息,然后把获取到的信息转换为分享接口的参数,最后再调用分享接口把小程序分享到对应平台。具体实现方案主要有两种:
实现小程序抽象业务回调接口
IAppletHandler
的shareAppMessage方
法,并将IAppletHandler
实例传入SDK。当点击小程序更多菜单中的“转发”时,会调用
IAppletHandler
实例的shareAppMessage
方法,shareAppMessage
方法中有小程序信息、小程序页面截图等参数,获取到小程序相关参数之后,便可调用第三方分享SDK实现分享。shareAppMessage
方法如下:/** * 转发小程序 * * @param appInfo 小程序信息,是一串json,包含了小程序id、小程序名称、小程序图标、用户id、转发的数据内容等信息。 * [appInfo]的内容格式如下: * { * "appTitle": "凡泰小程序", * "appAvatar": "https:\/\/www.finogeeks.club\/statics\/images\/swan_mini\/swan_logo.png", * "appId": "5df36b3f687c5c00013e9fd1", * "appType": "trial", * "userId": "finogeeks", * "cryptInfo": "SFODj9IW1ENO8OA0El8P79aMuxB1DJvfKenZd7hrnemVCNcJ+Uj9PzkRkf/Pu5nMz0cGjj0Ne4fcchBRCmJO+As0XFqMrOclsqrXaogsaUPq2jJKCCao03vI8rkHilrWxSDdzopz1ifJCgFC9d6v29m9jU29wTxlHsQUtKsk/wz0BROa+aDGWh0rKvUEPgo8mB+40/zZFNsRZ0PjsQsi7GdLg8p4igKyRYtRgOxUq37wgDU4Ymn/yeXvOv7KrzUT", * "params": { * "title": "apt-test-tweet-接口测试发布的动态!@#¥%……&*(", * "desc": "您身边的服务专家", * "imageUrl": "finfile:\/\/tmp_fc15edd8-2ff6-4c54-9ee9-fe5ee034033d1576550313667.png", * "path": "pages\/tweet\/tweet-detail.html?fcid=%40staff_staff1%3A000000.finogeeks.com&timelineId=db0c2098-031e-41c4-b9c6-87a5bbcf681d&shareId=3dfa2f78-19fc-42fc-b3a9-4779a6dac654", * "appInfo": { * "weixin": { * "path": "\/studio\/pages\/tweet\/tweet-detail", * "query": { * "fcid": "@staff_staff1:000000.finogeeks.com", * "timelineId": "db0c2098-031e-41c4-b9c6-87a5bbcf681d" * } * } * } * } * } * [appInfo]中各字段的说明: * appId 小程序ID * appTitle 小程序名称 * appAvatar 小程序头像 * appType 小程序类型,其中trial表示体验版,temporary表示临时版,review表示审核版,release表示线上版,development表示开发版 * userId 用户ID * cryptInfo 小程序加密信息 * params 附带的其它参数,由小程序自己透传 * * @param bitmap 小程序封面图片。如果[appInfo].params.imageUrl字段为http、https的链接地址,那么小程序封面图片 * 就取[appInfo].params.imageUrl对应的图片,否则小程序的封面图片取[bitmap]。 * @param callback 转发小程序结果回调。 */ fun shareAppMessage(appInfo: String, bitmap: Bitmap?, callback: IAppletCallback)
通过调用
IAppletApiManager
的setAppletHandler(appletHandler: IAppletHandler)
方法传入IAppletHandler
实例,如下:FinAppClient.INSTANCE.getAppletApiManager().setAppletHandler(new IAppletHandler() { @Override public void shareAppMessage(@NotNull String appInfo, @org.jetbrains.annotations.Nullable Bitmap bitmap, @NotNull IAppletCallback callback) { // 实现分享小程序的逻辑 …………………………………………………… …………………………………………………… } });
通过自定义接口来实现。在自定义接口的
invoke
方法中接收小程序传递过来的参数,然后调用第三方分享SDK实现小程序分享。
2.10 如何往“更多”菜单中注入自己的菜单项?
答:和通过抽象业务回调接口IAppletHandler
实现小程序分享一样, “更多”菜单中菜单项的注入也是通过IAppletHandler
来实现的,IAppletHandler
会把获取注入菜单项的接口方法getRegisteredMoreMenuItems
和点击注入菜单项的接口方法onRegisteredMoreMenuItemClicked
回调给宿主应用,由宿主应用实现具体的业务逻辑。
getRegisteredMoreMenuItems
和onRegisteredMoreMenuItemClicked
如下:
/**
* 获取注册的"更多"菜单项
*
* @param appId 小程序ID
* @return 注册的"更多"菜单项
*/
fun getRegisteredMoreMenuItems(appId: String): List<MoreMenuItem>?
/**
* 注册的"更多"菜单项被点击
*
* @param appId 小程序ID
* @param path 小程序页面路径
* @param menuItemId 被点击的菜单条目的ID
* @param appInfo 小程序信息,是一串json,包含了小程序id、小程序名称、小程序图标、用户id、转发的数据内容等信息。
* [appInfo]的内容格式如下:
* {
* "appTitle": "凡泰小程序",
* "appAvatar": "https:\/\/www.finogeeks.club\/statics\/images\/swan_mini\/swan_logo.png",
* "appId": "5df36b3f687c5c00013e9fd1",
* "appType": "trial",
* "userId": "finogeeks",
* "cryptInfo": "SFODj9IW1ENO8OA0El8P79aMuxB1DJvfKenZd7hrnemVCNcJ+Uj9PzkRkf/Pu5nMz0cGjj0Ne4fcchBRCmJO+As0XFqMrOclsqrXaogsaUPq2jJKCCao03vI8rkHilrWxSDdzopz1ifJCgFC9d6v29m9jU29wTxlHsQUtKsk/wz0BROa+aDGWh0rKvUEPgo8mB+40/zZFNsRZ0PjsQsi7GdLg8p4igKyRYtRgOxUq37wgDU4Ymn/yeXvOv7KrzUT",
* "params": {
* "title": "apt-test-tweet-接口测试发布的动态!@#¥%……&*(",
* "desc": "您身边的服务专家",
* "imageUrl": "finfile:\/\/tmp_fc15edd8-2ff6-4c54-9ee9-fe5ee034033d1576550313667.png",
* "path": "pages\/tweet\/tweet-detail.html?fcid=%40staff_staff1%3A000000.finogeeks.com&timelineId=db0c2098-031e-41c4-b9c6-87a5bbcf681d&shareId=3dfa2f78-19fc-42fc-b3a9-4779a6dac654",
* "appInfo": {
* "weixin": {
* "path": "\/studio\/pages\/tweet\/tweet-detail",
* "query": {
* "fcid": "@staff_staff1:000000.finogeeks.com",
* "timelineId": "db0c2098-031e-41c4-b9c6-87a5bbcf681d"
* }
* }
* }
* }
* }
* [appInfo]中各字段的说明:
* appId 小程序ID
* appTitle 小程序名称
* appAvatar 小程序头像
* appType 小程序类型,其中trial表示体验版,temporary表示临时版,review表示审核版,release表示线上版,development表示开发版
* userId 用户ID
* cryptInfo 小程序加密信息
* params 附带的其它参数,由小程序自己透传
*
* @param bitmap 小程序封面图片。如果[appInfo].params.imageUrl字段为http、https的链接地址,那么小程序封面图片
* 就取[appInfo].params.imageUrl对应的图片,否则小程序的封面图片取[bitmap]。
* @param callback 结果回调。
*/
fun onRegisteredMoreMenuItemClicked(appId: String, path: String, menuItemId: String, appInfo: String?, bitmap: Bitmap?, callback: IAppletCallback)
同样,IAppletHandler
实例需要通过调用IAppletApiManager
的setAppletHandler(appletHandler: IAppletHandler)
方法传入。
getRegisteredMoreMenuItems
方法和onRegisteredMoreMenuItemClicked
方法实现示例如下:
/**
* {@link IAppletHandler}实现类,用于实现一些业务场景,例如注册"更多"菜单项,转发小程序等。
*/
public class AppletHandler implements IAppletHandler {
@NonNull
private Context mContext;
private AppletHandler() {
}
public AppletHandler(@NonNull Context context) {
this.mContext = context;
}
@Nullable
@Override
public List<MoreMenuItem> getRegisteredMoreMenuItems(@NotNull String appId) {
List<MoreMenuItem> items = new ArrayList<>();
MoreMenuItem item0 = new MoreMenuItem("WXShareAPPFriends", "微信好朋友", MoreMenuType.ON_MINI_PROGRAM);
items.add(item0);
MoreMenuItem item1 = new MoreMenuItem("WXShareAPPMoments", "微信朋友圈", MoreMenuType.ON_MINI_PROGRAM, true);
items.add(item1);
MoreMenuItem item2 = new MoreMenuItem("ShareSinaWeibo", "新浪微博", MoreMenuType.ON_MINI_PROGRAM);
items.add(item2);
MoreMenuItem item3 = new MoreMenuItem("ShareQQFirends", "QQ", MoreMenuType.ON_MINI_PROGRAM);
items.add(item3);
MoreMenuItem item4 = new MoreMenuItem("ShareDingDing", "Dingding", MoreMenuType.ON_MINI_PROGRAM);
items.add(item4);
MoreMenuItem item5 = new MoreMenuItem("ShareLinks", "标题以后端配置为准", MoreMenuType.ON_MINI_PROGRAM);
items.add(item5);
MoreMenuItem item6 = new MoreMenuItem("SharePicture", "SharePicture", MoreMenuType.ON_MINI_PROGRAM);
items.add(item6);
MoreMenuItem item7 = new MoreMenuItem("Restart", "Restart", MoreMenuType.COMMON);
items.add(item7);
MoreMenuItem item8 = new MoreMenuItem("Desktop", "Desktop", MoreMenuType.COMMON);
items.add(item8);
return items;
}
@Override
public void onRegisteredMoreMenuItemClicked(@NotNull String appId, @NotNull String path, @NotNull String menuItemId, @Nullable String appInfo, @Nullable Bitmap bitmap, @NotNull IAppletCallback callback) {
Toast.makeText(mContext, "小程序" + appId + "的" + path + "页面的菜单" + menuItemId + "被点击了,appInfo : " + appInfo + " bitmap : " + bitmap, Toast.LENGTH_SHORT).show();
callback.onSuccess(null);
}
}
MoreMenuItem
为菜单条目数据类,如下:
/**
* 更多菜单条目
*
* @param id 菜单条目ID
* @param title 菜单菜单条目标题
* @param image 菜单条目图标地址
* @param icon 菜单条目图标对应的资源ID
* @param type 菜单条目类型
* @param isEnable 菜单条目是否可用
*/
data class MoreMenuItem(val id: String,
val title: String,
val image: String,
@DrawableRes val icon: Int,
val type: MoreMenuType = MoreMenuType.COMMON,
val isEnable: Boolean = true) {
/**
* 构造方法
* @param id 菜单条目ID
* @param title 菜单菜单条目标题
* @param type 菜单条目类型[MoreMenuType.COMMON]或[MoreMenuType.ON_MINI_PROGRAM]
*/
constructor(id: String, title: String, type: MoreMenuType) : this(id, title, "", -1, type, true)
}
MoreMenuType
是一个枚举类,如下:
/**
* 更多菜单类型
* [COMMON]为普通菜单类型,不需要和小程序有交互
* [ON_MINI_PROGRAM]为需要和小程序有交互的菜单类型,例如分享小程序按钮,点击按钮分享小程序时,可能需要获取到小程序的一些数据
*/
enum class MoreMenuType {
COMMON, ON_MINI_PROGRAM
}
2.11 如何获取小程序当前页面截图?
答:通过调用IAppletApiManager
接口的captureAppletPicture
方法获取,如下:
FinAppClient.appletApiManager.captureAppletPicture("appId", object :FinCallback<Bitmap?>{
override fun onSuccess(result: Bitmap?) {
Log.d(TAG, "获取小程序页面截图成功 :$result")
}
override fun onError(code: Int, error: String?) {
Log.e(TAG, "获取小程序页面截图失败 :$code, $error")
}
override fun onProgress(status: Int, info: String?) {
}
})
2.12 如何屏蔽更多菜单中的“转发”按钮?
答:初始化SDK时,通过UI配置项进行配置,如下:
// UI配置
FinAppConfig.UIConfig uiConfig = new FinAppConfig.UIConfig();
// 是否隐藏"更多"菜单中的"转发"按钮
uiConfig.setHideForwardMenu(true);
2.13 如何屏蔽更多菜单中的“返回首页”按钮?
答:初始化SDK时,通过UI配置项进行配置,如下:
// UI配置
FinAppConfig.UIConfig uiConfig = new FinAppConfig.UIConfig();
// 是否隐藏"更多"菜单中的"返回首页"菜单入口
uiConfig.setHideBackHome(true);
2.14 如何屏蔽更多菜单中的“反馈与投诉”入口?
答:初始化SDK时,通过UI配置项进行配置,如下:
// UI配置
FinAppConfig.UIConfig uiConfig = new FinAppConfig.UIConfig();
// 是否隐藏更多菜单中的"反馈与投诉"
uiConfig.setHideFeedbackAndComplaints(true);
2.15 如何隐藏导航栏中的关闭按钮?
答:初始化SDK时,通过UI配置项进行配置,如下:
// UI配置
FinAppConfig.UIConfig uiConfig = new FinAppConfig.UIConfig();
// 是否隐藏右上角关闭按钮
uiConfig.setHideNavigationBarCloseButton(true);
2.16 当导航栏为默认样式时,怎样实现在首页也显示返回按钮?
答:初始化SDK时,通过UI配置项进行配置,如下:
// UI配置
FinAppConfig.UIConfig uiConfig = new FinAppConfig.UIConfig();
// 当导航栏为默认导航栏时,是否始终显示返回按钮
uiConfig.setAlwaysShowBackInDefaultNavigationBar(false);
2.17 怎样配置灰度发布规则?
答:和通过抽象业务回调接口IAppletHandler
实现小程序分享一样, 灰度发布配置参数的注入也是通过IAppletHandler
来实现的,IAppletHandler
会把获取灰度发布配置参数的接口方法getGrayAppletVersionConfigs
回调给宿主应用,由宿主应用实现具体的业务逻辑。
getGrayAppletVersionConfigs
如下:
/**
* 获取灰度发布配置参数
*
* @param appId 小程序ID
* @return 灰度发布配置参数
*/
fun getGrayAppletVersionConfigs(appId: String): List<GrayAppletVersionConfig>?
同样,IAppletHandler
实例需要通过调用IAppletApiManager
的setAppletHandler(appletHandler: IAppletHandler)
方法传入。
2.18 怎样实现自定义导航栏?
答:目前提供了三种导航栏样式,分别是:
- 默认样式(default);
- 只保留“更多关闭”按钮的自定义样式(custom);
- 不保留“更多关闭”按钮,原生导航栏全部隐藏的自定义样式(hide)。
默认情况下,小程序中page
的navigationStyle
和window
的navigationStyle
都为default,如果开发者需要自定义导航栏样式,可以通过配置page
或者window
的navigationStyle
来实现。
2.19 是否支持禁止SDK主动发起运行时权限申请?
答:支持。
如果宿主应用希望所有运行时权限的申请都交由自己管理,不想SDK在用到权限时主动发起申请,那么可以通过在初始化SDK时配置参数来实现,如下:
FinAppConfig finAppConfig = new FinAppConfig.Builder()
.setDisableRequestPermissions(true) // 禁止SDK发起运行时权限申请
.build();
2.20 怎样调整导航栏标题文字样式?
答:初始化SDK时,通过UI配置项进行配置,如下:
// UI配置
FinAppConfig.UIConfig uiConfig = new FinAppConfig.UIConfig();
// 导航栏标题文字样式
uiConfig.setNavigationBarTitleTextAppearance(R.style.TextAppearance_AppCompat);
2.21 怎样让导航栏标题居中显示?
答:初始化SDK时,通过UI配置项进行配置,如下:
// UI配置
FinAppConfig.UIConfig uiConfig = new FinAppConfig.UIConfig();
// 导航栏标题相对父控件的Gravity
uiConfig.setNavigationBarTitleTextLayoutGravity(Gravity.CENTER);
2.22 怎样去除导航栏返回按钮按下时的背景动画?
答:初始化SDK时,通过UI配置项进行配置,如下:
// UI配置
FinAppConfig.UIConfig uiConfig = new FinAppConfig.UIConfig();
// 是否清除导航栏导航按钮的背景
uiConfig.setClearNavigationBarNavButtonBackground(true);
2.23 小程序是否支持横竖屏切换?
答:支持。
小程序支持横屏、竖屏、横竖屏自由切换。小程序的屏幕旋转设置可以在小程序工程的app.json
文件的window
中全局配置,也可以在每个页面的.json
文件中单独配置。
- 在
app.json
中全局配置:
{
"window": {
"pageOrientation": "auto" // auto:横竖屏自由切换,portrait:竖屏,landscape:横屏
}
}
- 在页面的
.json
文件中单独配置:
{
"pageOrientation": "auto" // auto:横竖屏自由切换,portrait:竖屏,landscape:横屏
}
- 页面配置在当前页面会覆盖
app.json
的window
中相同的配置。
2.24 怎样禁止使用TBS SDK?
答:初始化SDK时通过配置参数来实现,如下:
FinAppConfig finAppConfig = new FinAppConfig.Builder()
.setDisableTbs(true) // 设置是否禁止启用Tbs SDK
.build();
2.25 怎样设置定时批量更新小程序的数量?
答:初始化SDK时通过配置参数来实现,如下:
FinAppConfig finAppConfig = new FinAppConfig.Builder()
.setAppletIntervalUpdateLimit(3) // 设置定时批量更新小程序的数量
.build();
2.26 怎样设置可同时打开的小程序的个数?
答:初始化SDK时通过配置参数来实现,如下:
FinAppConfig finAppConfig = new FinAppConfig.Builder()
.setMaxRunningApplet(3) // 可同时可打开的小程序的个数
.build();
2.27 小程序支持控制右上角更多
按钮的显示和隐藏吗?
答:支持。
配置navigationBarHideMoreButton
属性即可实现,navigationBarHideMoreButton
可以在小程序工程的app.json
文件的window
中全局配置,也可以在每个页面的.json
文件中单独配置。
- 在
app.json
中全局配置:
{
"window": {
"navigationBarHideMoreButton": true // true:隐藏右上角更多按钮,false:显示右上角更多按钮,默认为false。
}
}
- 在页面的
.json
文件中单独配置:
{
"navigationBarHideMoreButton": true // true:隐藏右上角更多按钮,false:显示右上角更多按钮,默认为false。
}
2.28 小程序可以控制右上角关闭按钮的显示和隐藏吗?
可以。配置navigationBarHideCloseButton
属性即可实现,navigationBarHideCloseButton
可以在小程序工程的app.json文件的window中全局配置,也可以在每个页面的.json
文件中单独配置。
- 在
app.json
中全局配置:
{
"window": {
"navigationBarHideCloseButton": true // true:隐藏右上角关闭按钮,false:显示右上角关闭按钮,默认为false。
}
}
- 在页面的
.json
文件中单独配置:
{
"navigationBarHideCloseButton": true // true:隐藏右上角关闭按钮,false:显示右上角关闭按钮,默认为false。
}
2.29 是否支持把SDK中“小程序”文案替换为其它名称?
答:支持。
通过SDK初始化配置参数来实现,如下:
FinAppConfig finAppConfig = new FinAppConfig.Builder()
.setAppletText("X应用") // 把SDK中的“小程序”文案替换为“X应用”
.build();
2.30 数据上报时,是否会对上报的数据进行压缩?
答:数据上报时,SDK默认不对上报的数据进行压缩,如果要开启压缩,可以通过SDK初始化配置参数来实现,如下:
FinAppConfig finAppConfig = new FinAppConfig.Builder()
.setEnableApmDataCompression(true) // 设置数据上报时,对上报的数据进行压缩
.build();
2.31 是否支持禁用获取监管信息的小程序API?
答:支持。
SDK默认允许小程序调用获取监管信息的小程序API(getSuperviseInfo),如果要禁用,则可以通过SDK初始化配置参数来实现,如下:
FinAppConfig finAppConfig = new FinAppConfig.Builder()
.setDisableGetSuperviseInfo(true) // 设置是否禁止小程序调用获取监管信息的小程序API
.build();
禁止后,小程序调用getSuperviseInfo
时将会收到getSuperviseInfo:fail disabled
回调
2.32 是否支持打开体验版小程序?
答:支持。
平台支持为小程序配置体验版本和体验成员,拥有体验权限的成员能够打开体验版小程序。
使用体验版小程序的步骤:
- 在平台中上传小程序,并为小程序配置体验版,配置方式请参考企业端操作指引 - 4.4 调试
- 在初始化SDK的时候传入用户ID:
val config = FinAppConfig.Builder()
.setUserId("用户ID")
.build()
只有当传入的用户ID在小程序配置的成员列表中时,才能打开体验版小程序,否则在打开小程序的时候,前置页面中会提示“无体验权限”。
- 通过调用SDK提供的接口打开小程序,接口如下:
/**
* 启动小程序
*
* @param context 上下文
* @param startAppletDecryptRequest 请求体
*/
fun startApplet(context: Context, startAppletDecryptRequest: StartAppletDecryptRequest)`
StartAppletDecryptRequest
的结构如下:
/**
* 启动小程序请求实体类
*
* @param info 小程序加密信息
*/
data class StartAppletDecryptRequest(val info: String)
StartAppletDecryptRequest
的info
字段表示小程序加密信息,和小程序体验版二维码中的info
字段对应。因此打开体验版小程序时,StartAppletDecryptRequest
的info
字段传小程序体验版二维码中的info字段对应的value即可。
以下面这段体验版小程序二维码内容为例:
https://finchat-mop.finogeeks.club/mop/scattered-page/#/sdktip?type=scanOpen&info=SFODj9IW1ENO8OA0El8P79aMuxB1DJvfKenZd7hrnemVCNcJ+Uj9PzkRkf/Pu5nMz0cGjj0Ne4fcchBRCmJO+As0XFqMrOclsqrXaogsaUPq2jJKCCao03vI8rkHilrWxSDdzopz1ifJCgFC9d6v29m9jU29wTxlHsQUtKsk/wz0BROa+aDGWh0rKvUEPgo8mB+40/zZFNsRZ0PjsQsi7GdLg8p4igKyRYtRgOxUq37wgDU4Ymn/yeXvOv7KrzUT&codeType=trial
则info
字段值应为:
SFODj9IW1ENO8OA0El8P79aMuxB1DJvfKenZd7hrnemVCNcJ+Uj9PzkRkf/Pu5nMz0cGjj0Ne4fcchBRCmJO+As0XFqMrOclsqrXaogsaUPq2jJKCCao03vI8rkHilrWxSDdzopz1ifJCgFC9d6v29m9jU29wTxlHsQUtKsk/wz0BROa+aDGWh0rKvUEPgo8mB+40/zZFNsRZ0PjsQsi7GdLg8p4igKyRYtRgOxUq37wgDU4Ymn/yeXvOv7KrzUT
2.33 怎么在小程序中实现第三方登录?
2.33.1集成 FinClip SDK(凡泰小程序SDK)
开发者首先需要集成 FinClip SDK,集成指南请参照 FinClip 小程序开放平台 Android 集成文档,开放平台已有详尽的 Android 集成文档,此处不再赘述。
2.33.2 自定义小程序接口以实现授权登录
为了让小程序能过获取到小程序以外的 APP 数据,需要注册小程序自定义接口,自定义接口具体说明请参照 FinClip 小程序开放平台-自定义小程序接口。
注意
本例中的参数在实际开发中由开发者自行制定,本例仅为示范作用。
- 自定义授权登录 login 接口
因本示例在授权登录时需要展示授权 Dialog,即需要获取 Activity 实例,因此我们需要该 Api 注册在小程序进程,可以方便的获取到展示小程序的 Activity 实例。
public class LoginApi extends AbsApi {
// 定义代码省略
private void showAuthDialog(ICallback iCallback) {
new AlertDialog.Builder(activity)
.setTitle("授权登录")
.setMessage("是否授权该小程序获取用户信息?")
.setCancelable(false)
.setPositiveButton("确定", (dialog, which) -> authLoginOnMainProcess(iCallback))
.setNegativeButton("取消", (dialog, which) -> iCallback.onFail())
.show();
}
/**
* 由于用户信息一般只会存储在主进程中,在小程序进程中直接调用取不到数据
* 因此要使用 callInMainProcess 方法跨进程调用,在主进程中获取到信息后,再回传给小程序进程
*/
private void authLoginOnMainProcess(ICallback iCallback) {
// 跨进程调用代码省略
}
}
跨进程调用 api 的相关说明可以查看文档:小程序进程调用主进程
在小程序进程中注册自定义 api
if (FinAppClient.INSTANCE.isFinAppProcess(this)) {
// 小程序进程
initFinClipOnAppletProcess();
} else {
// 主进程初始化代码省略
}
/**
* 将小程序注册到小程序进程中
*/
private void initFinClipOnAppletProcess() {
FinAppProcessClient.INSTANCE.setCallback(new FinAppProcessClient.Callback() {
@Override
public List<IApi> getRegisterExtensionApis(@NotNull Activity activity) {
List<IApi> extensionApis = new ArrayList<>();
extensionApis.add(new LoginApi(activity));
return extensionApis;
}
@Override
public List<IApi> getRegisterExtensionWebApis(@NotNull Activity activity) {
return null;
}
});
}
至此,小程序通过自定义 Api 从 APP 获取用户token的整个流程就已经完成了。
注意
如果产品需求不需要展示用户授权提示 Dialog,建议在主进程注册自定义 Api,从而省掉上述跨进程调用的过程。
2.33.3 自定义小程序接口以实现获取用户信息
本例中的参数在实际开发中由开发者自行制定,本例仅为示范作用。
自定义获取用户信息 getUserProfile 接口
public class ProfileApi extends AbsApi {
// 定义代码省略
/**
* 此示例中 ProfileApi 直接注册在了主进程的扩展 api 中
* 因此该 api 是在主进程中执行,可以直接获取数据
*/
private void getUserProfile(JSONObject jsonObject, ICallback iCallback) {
// 获取用户信息过程省略
}
}
在主进程中注册 api
FinCallback<Object> initCallback = new FinCallback<Object>() {
@Override
public void onSuccess(Object result) {
// 注册扩展Api,此处注册的Api将会在主进程中执行
FinAppClient.INSTANCE
.getExtensionApiManager()
.registerApi(new ProfileApi());
}
@Override
public void onError(int code, String error) {
}
@Override
public void onProgress(int status, String error) {
}
};
FinAppClient.INSTANCE.init(this, finAppConfig, initCallback);
至此,小程序通过自定义 Api 从 APP 获取用户信息的整个流程就已经完成了。
2.33.4自定义小程序接口以检查用户token是否已失效
本例中的参数在实际开发中由开发者自行制定,本例仅为示范作用。
自定义检查用户token的checkSession 接口
public class AppletSessionApi extends AbsApi {
// 定义代码省略
private void checkSession(JSONObject jsonObject, ICallback iCallback) {
// 检查过程代码省略
}
}
在主进程中注册 api
FinCallback<Object> initCallback = new FinCallback<Object>() {
@Override
public void onSuccess(Object result) {
// 注册扩展Api,此处注册的Api将会在主进程中执行
FinAppClient.INSTANCE
.getExtensionApiManager()
.registerApi(new ProfileApi());
FinAppClient.INSTANCE
.getExtensionApiManager()
.registerApi(new AppletSessionApi());
}
@Override
public void onError(int code, String error) {
}
@Override
public void onProgress(int status, String error) {
}
};
FinAppClient.INSTANCE.init(this, finAppConfig, initCallback);
至此,小程序通过自定义 Api 从 APP 检查用户token的整个流程就已经完成了。
3. 调试方面
3.1 开发小程序的时候,用什么工具调试小程序?
答:Android小程序SDK使用了腾讯TBS浏览服务(X5内核),调试工具为TBS Studio。TBS Studio能够像在Chrome浏览器中调试网页那样对小程序进行调试。
注意
从 2.33.3
版本开始,SDK已使用系统webview替换X5内核。
3.2 为什么TBS Studio无法开启调试,一直提示当前目标App不能进行TBS调试,请进行如下检查和操作 请确保当前目标App的Webview基于TBS开发 请确保前序步骤执行成功?
答:出现这种情况,一般是因为当前设备为64位处理器的设备,且没有让应用以32位模式运行。43903版本之前的TBS SDK不提供64位的so动态库,如果在64位处理器设备上仍以64位模式运行,那么TBS SDK在初始化的时候便会失败,X5内核将不能成功启用。
要让64位处理器的设备能够正常启用X5内核,需要在当前Android工程的build.gradle
文件的defaultConfig
中进行如下设置,让应用以32位模式运行。
ndk {
// 设置支持的SO库架构
abiFilters "armeabi", 'armeabi-v7a'
}
如果配置后编译报错,则可以通过在工程的gradle.properties
文件中增加如下配置来解决。
Android.useDeprecatedNdk=true;
3.3 为什么在一些低版本系统中,小程序加载网页会一直白屏,而在高版本系统中则不会?
答:出现这种情况,一般是因为低版本系统中的浏览器版本也比较低,浏览器无法正常识别新语法特性导致的。例如:有些浏览器版本的发布早于ES6的定稿和发布,如果在编码的时候使用了ES6的新特性,而浏览器并没有更新版本,那么当在浏览器中打开网页时,浏览器就会无法识别ES6代码,从而发生错误。
对于这类问题,一般建议:
- 在编码的时候,尽可能使用兼容性好的语法;
- 如果不可避免地需要使用一些新语法特性,可以尝试引入一些语法转换工具,如babel-polyfill等,将新的语法自动转换为低版本的语法,这样你就可以在使用新语法特性的时候不用考虑环境兼容的问题。