目录

Android 常见问题

优质
小牛编辑
125浏览
2023-12-01

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 怎么启动小程序?

  1. 如果启动小程序时不携带启动参数,则通过调用IAppletApiManager接口的startApplet(context: Context, appId: String)方法启动,如下:
FinAppClient.INSTANCE.getAppletApiManager().startApplet(context, "appId");
  1. 如果启动小程序时携带启动参数,则通过调用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内部的标准接口一样,可以供小程序调用。

具体实现步骤如下:

  1. 实现自定义小程序接口。
    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();
            }
        }
    }
}
  1. 注册自定义小程序接口。
    通过调用IExtensionApiManager接口的registerApi方法实现注册。如下:
FinAppClient.INSTANCE.getExtensionApiManager().registerApi(new ApiOpenPage(context));

如果需要一次注册多个接口,则可以调用registerApis,传入的参数为接口集合。

  1. 在小程序工程中增加扩展接口配置。SDK注册自定义小程序接口后,还需要在小程序工程的根目录创建FinClipConf.js文件,在FinClipConf.js中配置对应的自定义接口。配置示例如下:
module.exports = {
  	extApi:[
    	{
      		name: 'openPage', // 扩展接口名
      		params: { // 扩展接口参数,可以只列必须的参数
        		url: '',
        		title: '',
        		description: ''
      		}
    	}
  	]
}
  1. 在小程序中调用已注册接口中的各个API。

2.4 小程序加载网页时,网页可以调用原生的代码吗?

答:可以在小程序的网页中调用原生代码。JSSDK提供了一系列接口,通过这些接口可以实现在网页里面调用原生代码。

2.5 JSSDK接口是否支持扩展?即除了JSSDK内部提供的一系列标准接口之外,能否让小程序在网页中调用自己提供的接口?如果可以要怎么实现?

答:JSSDK接口支持扩展。SDK允许注册自定义的JSSDK接口,注册之后的自定义JSSDK接口和JSSDK内部提供的标准接口一样,可以供小程序在网页中调用。

具体实现步骤如下:

  1. 实现自定义JSSDK接口。
    这一步和实现自定义小程序接口是一样的,通过继承AbsApi,并重写apis()方法和invoke()方法实现自定义JSSDK接口。
  2. 注册自定义JSSDK接口。
    通过调用IExtensionWebApiManager接口的registerApi方法实现注册。如下:
FinAppClient.INSTANCE.getExtensionWebApiManager().registerApi(new ApiOpenPage(context));

如果需要一次注册多个接口,则可以调用registerApis,传入的参数为接口集合。

详细可以查看注册小程序web-view组件API (opens new window)

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

  1. 原因:

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

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

  2. 解决方案:

    方案1(推荐):
    通过ICallbackstartActivitystartActivityForResult来跳转到宿主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_TASKFLAG_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);

目前提供了五种动画可供设置:

  1. NoneAnim:无动画;
  2. SlideFromLeftToRightAnim:滑动动画-左进右出;
  3. SlideFromRightToLeftAnim:滑动动画-右进左出;
  4. SlideFromTopToBottomAnim:滑动动画-上进下出;
  5. SlideFromBottomToTopAnim:滑动动画-下进上出。

2.9 怎么分享小程序到微信等支持小程序的平台?

答:要实现小程序分享功能,总体思路是先获取到分享小程序所需要的相关信息,然后把获取到的信息转换为分享接口的参数,最后再调用分享接口把小程序分享到对应平台。具体实现方案主要有两种:

  1. 实现小程序抽象业务回调接口IAppletHandlershareAppMessage方法,并将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)
    

    通过调用IAppletApiManagersetAppletHandler(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) {
           // 实现分享小程序的逻辑
           ……………………………………………………
          ……………………………………………………
       }
    });
    
  2. 通过自定义接口来实现。在自定义接口的invoke方法中接收小程序传递过来的参数,然后调用第三方分享SDK实现小程序分享。

2.10 如何往“更多”菜单中注入自己的菜单项?

答:和通过抽象业务回调接口IAppletHandler实现小程序分享一样, “更多”菜单中菜单项的注入也是通过IAppletHandler来实现的,IAppletHandler会把获取注入菜单项的接口方法getRegisteredMoreMenuItems和点击注入菜单项的接口方法onRegisteredMoreMenuItemClicked回调给宿主应用,由宿主应用实现具体的业务逻辑。

getRegisteredMoreMenuItemsonRegisteredMoreMenuItemClicked如下:

/**
 * 获取注册的"更多"菜单项
 *
 * @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实例需要通过调用IAppletApiManagersetAppletHandler(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实例需要通过调用IAppletApiManagersetAppletHandler(appletHandler: IAppletHandler)方法传入。

2.18 怎样实现自定义导航栏?

答:目前提供了三种导航栏样式,分别是:

  1. 默认样式(default);
  2. 只保留“更多关闭”按钮的自定义样式(custom);
  3. 不保留“更多关闭”按钮,原生导航栏全部隐藏的自定义样式(hide)。

默认情况下,小程序中pagenavigationStylewindownavigationStyle都为default,如果开发者需要自定义导航栏样式,可以通过配置page或者windownavigationStyle来实现。

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文件中单独配置。

  1. app.json中全局配置:
{
	"window": {
		"pageOrientation": "auto" // auto:横竖屏自由切换,portrait:竖屏,landscape:横屏
	}
}
  1. 在页面的.json文件中单独配置:
{
	"pageOrientation": "auto" // auto:横竖屏自由切换,portrait:竖屏,landscape:横屏
}
  1. 页面配置在当前页面会覆盖 app.jsonwindow 中相同的配置。

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文件中单独配置。

  1. app.json中全局配置:
{
	"window": {
		"navigationBarHideMoreButton": true // true:隐藏右上角更多按钮,false:显示右上角更多按钮,默认为false。
	}
}
  1. 在页面的.json文件中单独配置:
{
	"navigationBarHideMoreButton": true // true:隐藏右上角更多按钮,false:显示右上角更多按钮,默认为false。
}

2.28 小程序可以控制右上角关闭按钮的显示和隐藏吗?

可以。配置navigationBarHideCloseButton属性即可实现,navigationBarHideCloseButton可以在小程序工程的app.json文件的window中全局配置,也可以在每个页面的.json文件中单独配置。

  1. app.json中全局配置:
{
	"window": {
		"navigationBarHideCloseButton": true // true:隐藏右上角关闭按钮,false:显示右上角关闭按钮,默认为false。
	}
}
  1. 在页面的.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 是否支持打开体验版小程序?

答:支持。

平台支持为小程序配置体验版本和体验成员,拥有体验权限的成员能够打开体验版小程序。

使用体验版小程序的步骤:

  1. 在平台中上传小程序,并为小程序配置体验版,配置方式请参考企业端操作指引 - 4.4 调试
  2. 在初始化SDK的时候传入用户ID:
val config = FinAppConfig.Builder()
    .setUserId("用户ID")
    .build()

只有当传入的用户ID在小程序配置的成员列表中时,才能打开体验版小程序,否则在打开小程序的时候,前置页面中会提示“无体验权限”。

  1. 通过调用SDK提供的接口打开小程序,接口如下:
/**
 * 启动小程序
 *
 * @param context 上下文
 * @param startAppletDecryptRequest 请求体
 */
fun startApplet(context: Context, startAppletDecryptRequest: StartAppletDecryptRequest)`

StartAppletDecryptRequest的结构如下:

/**
 * 启动小程序请求实体类
 *
 * @param info 小程序加密信息
 */
data class StartAppletDecryptRequest(val info: String)

StartAppletDecryptRequestinfo字段表示小程序加密信息,和小程序体验版二维码中的info字段对应。因此打开体验版小程序时,StartAppletDecryptRequestinfo字段传小程序体验版二维码中的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代码,从而发生错误。

对于这类问题,一般建议:

  1. 在编码的时候,尽可能使用兼容性好的语法;
  2. 如果不可避免地需要使用一些新语法特性,可以尝试引入一些语法转换工具,如babel-polyfill等,将新的语法自动转换为低版本的语法,这样你就可以在使用新语法特性的时候不用考虑环境兼容的问题。