支持区分体验版/非体验版
开启功能。
Mriver.setConfig("mr_experience_required", "YES");
打开小程序体验/测试版本。
MriverResource.deleteApp(appId); // 删除本地版本 Bundle bundle = new Bundle(); bundle.putString(RVStartParams.LONG_NB_UPDATE, "synctry"); // 强制更新版本,可以组合使用 bundle.putInt(RVStartParams.LONG_NB_EXPERIENCE_REQUIRED, 1); // 1表示体验版本, 不传参数表示正式版本 Mriver.startApp(this, appId, bundle);
支持小程序页面返回询问对话框
开启返回拦截开关。
Mriver.setConfig("enable_back_perform", "YES");
升级 Appx 版本。
// build.gradle中添加 api ('com.mpaas.mriver:mriverappxplus-build:2.7.18.20230130001@aar') { force=true }
小程序开发时调用相关 API。
// 开启 my.enableAlertBeforeUnload({ message: '确认离开此页面?', }); // 关闭 my.disableAlertBeforeUnload()
真机调试/预览
MriverDebug.setWssHost("真实wss地址");
MriverDebug.debugAppByScan(activity);
自定义标题栏
Mriver.setProxy(TitleViewFactoryProxy.class, new TitleViewFactoryProxy() {
@Override
public ITitleView createTitle(Context context, App app) {
return new CustomTitleView(context);
}
});
public class CustomTitleView implements ITitleView, View.OnClickListener {
public static final String TAG = MRConstants.INTEGRATION_TAG + ":MRTitleView";
protected TextView tvTitle;
protected ImageView ivImageTitle;
protected ImageView btBack;
protected TextView btBackToHome;
protected RelativeLayout rlTitle;
protected View statusBarAdjustView;
protected List<ImageButton> btIconList = new ArrayList<>();
// 整个TitleBar的container view
protected TitleBarFrameLayout contentView;
// 右上角OptionMenu的个数(默认1个)
protected int visibleOptionNum;
protected Page mPage;
// 底部分割线
protected View mDivider;
protected Context mContext;
protected TitleViewIconSpec mTitleViewIconSpec;
protected TitleViewStyleSpec mDarkStyleSpec;
protected TitleViewStyleSpec mLightStyleSpec;
// protected ProgressBar mNavLoadingBar;
protected ITitleEventDispatcher mTitleEventDispatcher;
public CustomTitleView(Context context) {
mContext = context;
ViewGroup parent = null;
if (context instanceof Activity && ((Activity) context).getWindow() != null) {
parent = ((Activity) mContext).findViewById(android.R.id.content);
}
mTitleViewIconSpec = TitleViewSpecProvider.g().getIconSpec();
mDarkStyleSpec = TitleViewSpecProvider.g().getDarkSpec();
mLightStyleSpec = TitleViewSpecProvider.g().getLightSpec();
contentView = (TitleBarFrameLayout) LayoutInflater.from(context).inflate(R.layout.mriver_title_bar_demo, parent, false);
tvTitle = contentView.findViewById(R.id.h5_tv_title);
ivImageTitle = contentView.findViewById(R.id.h5_tv_title_img);
statusBarAdjustView = contentView.findViewById(R.id.h5_status_bar_adjust_view);
ivImageTitle.setVisibility(View.GONE);
tvTitle.setOnClickListener(this);
ivImageTitle.setOnClickListener(this);
btBack = contentView.findViewById(R.id.h5_tv_nav_back);
btBackToHome = contentView.findViewById(R.id.h5_tv_nav_back_to_home);
mDivider = contentView.findViewById(R.id.h5_h_divider_intitle);
rlTitle = contentView.findViewById(R.id.h5_rl_title);
visibleOptionNum = 1;
// ad view
// adViewLayout.setTag(H5Utils.TRANSPARENT_AD_VIEW_TAG);
btBack.setOnClickListener(this);
btBackToHome.setOnClickListener(this);
applyViewStyleAndIcon();
}
protected void applyViewStyleAndIcon() {
boolean useBackSpec = false;
boolean useHomeSpec = false;
if (mTitleViewIconSpec != null) {
TitleViewIconSpec.IconSpecEntry btHomeSpec = mTitleViewIconSpec.getHomeButton();
if (btHomeSpec != null) {
btBackToHome.setTypeface(btHomeSpec.getKey());
btBackToHome.setText(btHomeSpec.getValue());
useHomeSpec = true;
}
}
if (!useHomeSpec) {
Typeface iconFont = Typeface.createFromAsset(mContext.getAssets(), "mrv_iconfont.ttf");
btBackToHome.setTypeface(iconFont);
}
btBackToHome.setTextColor(StateListUtils.getStateColor(mLightStyleSpec.getHomeButtonColor()));
}
protected void setButtonIcon(Bitmap btIcon, int index) {
if (isOutOfBound(index, btIconList.size())) {
return;
}
btIconList.get(index).setImageBitmap(btIcon);
}
@Override
public void setTitle(String title) {
if (title != null && enableSetTitle(title)) {
tvTitle.setText(title);
tvTitle.setVisibility(View.VISIBLE);
ivImageTitle.setVisibility(View.GONE);
}
}
protected boolean enableSetTitle(String title) {
return !title.startsWith("http://") && !title.startsWith("https://");
}
// view visible control
protected boolean isOutOfBound(int num, int length) {
return length == 0 || length < num;
}
@Override
public void showBackButton(boolean show) {
btBack.setVisibility(show ? View.VISIBLE : View.GONE);
if (show && btBackToHome != null) {
btBackToHome.setVisibility(View.GONE);
}
addLeftMarginOnTitle();
}
@Override
public void showOptionMenu(boolean b) {
}
public void showHomeButton(boolean show) {
btBackToHome.setVisibility(show ? View.VISIBLE : View.GONE);
if (show) {
btBack.setVisibility(View.GONE);
}
addLeftMarginOnTitle();
}
@Override
public void setTitleEventDispatcher(ITitleEventDispatcher dispatcher) {
mTitleEventDispatcher = dispatcher;
}
@Override
public void addCapsuleButtonGroup(View view) {
if (view == null) {
return;
}
}
protected void addLeftMarginOnTitle() {
boolean needAdd = btBack.getVisibility() != View.VISIBLE &&
btBackToHome.getVisibility() != View.VISIBLE;
RelativeLayout.LayoutParams rlTitleLayoutParams =
(RelativeLayout.LayoutParams) rlTitle.getLayoutParams();
rlTitleLayoutParams.setMargins(!needAdd ? 0 : DimensionUtil.dip2px(mContext, 16), 0, 0, 0);
}
@Override
public void showTitleLoading(boolean show) {
}
@Override
public View getContentView() {
return contentView;
}
@Override
public void onClick(View view) {
RVLogger.d(TAG, "onClick " + view);
if (mPage == null) {
return;
}
if (view.equals(btBack)) {
if (mTitleEventDispatcher != null) {
mTitleEventDispatcher.onBackPressed();
}
} else if (view.equals(tvTitle) || view.equals(ivImageTitle)) {
if (mTitleEventDispatcher != null) {
mTitleEventDispatcher.onTitleClick();
}
} else if (view.equals(btBackToHome)) {
if (mTitleEventDispatcher != null) {
mTitleEventDispatcher.onHomeClick();
}
}
}
@Override
public void setPage(Page page) {
mPage = page;
tvTitle.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
mPage.getApp().restartFromServer(null);
return false;
}
});
}
public View getDivider() {
return mDivider;
}
protected void switchToLightTheme() {
tvTitle.setTextColor(mLightStyleSpec.getTitleTextColor());
btBackToHome.setTextColor(StateListUtils.getStateColor(mLightStyleSpec.getHomeButtonColor()));
}
protected void switchToDarkTheme() {
tvTitle.setTextColor(mDarkStyleSpec.getTitleTextColor());
btBackToHome.setTextColor(StateListUtils.getStateColor(mDarkStyleSpec.getHomeButtonColor()));
}
public void onRelease() {
btIconList.clear();
}
/***
* 打开沉浸式状态栏支持
*/
@Override
public void setStatusBarColor(int color) {
if (StatusBarUtils.isSupport()) {
int statusBarHeight = StatusBarUtils.getStatusBarHeight(mContext);
if (statusBarHeight == 0) { //保护,万一有rom没办法拿到状态栏高度的话,则在这里不生效。
return;
}
LinearLayout.LayoutParams layoutParams =
(LinearLayout.LayoutParams) statusBarAdjustView.getLayoutParams();
layoutParams.height = statusBarHeight;
statusBarAdjustView.setLayoutParams(layoutParams);
statusBarAdjustView.setVisibility(View.VISIBLE);
try {
StatusBarUtils.setTransparentColor((Activity) mContext, color);
} catch (Exception e) {
RVLogger.e(TAG, e);
}
}
}
@Override
public void setBackgroundColor(int color) {
contentView.getContentBgView().setColor(color);
}
@Override
public void setAlpha(int alpha, boolean titleTextAlphaEnabled) {
contentView.getContentBgView().setAlpha(alpha);
if (titleTextAlphaEnabled) {
tvTitle.setAlpha(alpha);
}
}
@Override
public void setOptionMenu(Bitmap bitmap) {
visibleOptionNum = 2;
setButtonIcon(bitmap, 1);
}
@Override
public void setTitleImage(Bitmap image, String contentDesc) {
if (!TextUtils.isEmpty(contentDesc)) {
ivImageTitle.setContentDescription(contentDesc);
}
if (image != null) {
RVLogger.d(TAG, "imgTitle width " + image.getWidth() + ", imgTitle height " + image
.getHeight());
ivImageTitle.setImageBitmap(image);
ivImageTitle.setVisibility(View.VISIBLE);
tvTitle.setVisibility(View.GONE);
RVLogger.d(TAG, "ivImageTitle width " + ivImageTitle
.getWidth() + ", ivImageTitle height " + ivImageTitle.getHeight());
}
}
@Override
public void setTitlePenetrate(boolean enable) {
contentView.setPreventTouchEvent(!enable);
}
@Override
public void applyTheme(TitleBarTheme theme) {
if (theme == TitleBarTheme.DARK) {
switchToDarkTheme();
} else if (theme == TitleBarTheme.LIGHT) {
switchToLightTheme();
}
}
}
自定义小程序加载动画
Mriver.setProxy(SplashViewFactoryProxy.class, new SplashViewFactoryProxy() {
@Override
public ISplashView createSplashView(Context context) {
return new CustomLoadingView(context);
}
});
public class CustomLoadingView extends FrameLayout implements ISplashView {
private static final String TAG = "CustomLoadingView";
private static final int defaultAlphaColor = 855638016;//Color.argb(51, 0, 0, 0);//默认透明色
private static final long TIME_DELAY_FOR_SHOW_PERCENTAGE = 2000;//展示百分的延迟时间2s
public final static String MSG_UPDATE_APPEARANCE = "UPDATE_APPEARANCE";
public final static String DATA_UPDATE_APPEARANCE_BG_COLOR = "UPDATE_APPEARANCE_BG_COLOR"; //页面背景色 #RGB
public final static String DATA_UPDATE_APPEARANCE_LOADING_ICON = "UPDATE_APPEARANCE_LOADING_ICON"; //loading图标 Drawable
public final static String DATA_UPDATE_APPEARANCE_LOADING_TEXT = "UPDATE_APPEARANCE_LOADING_TEXT"; //loading文案
public final static String DATA_UPDATE_APPEARANCE_LOADING_TEXT_COLOR = "UPDATE_APPEARANCE_LOADING_TEXT_COLOR"; //loading文案颜色 #RGB
public final static String DATA_UPDATE_APPEARANCE_LOADING_BOTTOM_TIP = "UPDATE_APPEARANCE_LOADING_BOTTOM_TIP"; //底部提示文案
public final static String ANIMATION_STOP_LOADING_PREPARE = "ANIMATION_STOP_LOADING_PREPARE";
private Context mContext;
protected ImageView mLoadingIcon;
protected TextView mLoadingTitle;
protected TextView mLoadingPercentTip;
protected TextView mBottomTip;
protected TextView mBackButton;
private Paint mDotPaint;
private Timer mTimer;
private TimerTask mTimerTask;
private boolean mPlayingStartAnim;
private int mDarkDotX;
private int mDarkDotY;
private int mDarkGap;
private int mDotSize;
private int mLightDotIndex = 0;
private int mPercentValue;
private long mStartLoadingTime = 0;
private OnCancelListener onCancelListener;
private Activity hostActivity;
public interface OnCancelListener {
void onCancel();
}
public CustomLoadingView(Context context) {
this(context, null);
}
public CustomLoadingView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomLoadingView(final Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
hostActivity = (Activity) context;
initView();
mBackButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
cancel();
if (context instanceof Activity) {
RVLogger.d(TAG, "user want close app when splash loading");
((Activity) context).finish();
}
}
});
}
public final void cancel() {
if (this.onCancelListener != null) {
this.onCancelListener.onCancel();
}
}
public void initView() {
mLoadingIcon = new ImageView(mContext);
mLoadingIcon.setScaleType(ImageView.ScaleType.FIT_XY);
mLoadingIcon.setImageResource(R.drawable.ic_launcher_foreground);
mLoadingTitle = new TextView(mContext);
mLoadingTitle.setGravity(Gravity.CENTER);
mLoadingTitle.setTextColor(Color.BLACK);
mLoadingTitle.setSingleLine();
mLoadingTitle.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
mLoadingTitle.setEllipsize(TextUtils.TruncateAt.END);
ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
mLoadingTitle.setLayoutParams(lp);
addView(mLoadingIcon);
addView(mLoadingTitle);
mBackButton = new TextView(mContext);
mBackButton.setGravity(Gravity.CENTER);
addView(mBackButton);
// 加载百分比
mPercentValue = 0;
mLoadingPercentTip = new TextView(mContext);
mLoadingPercentTip.setGravity(Gravity.CENTER);
mLoadingPercentTip.setSingleLine();
mLoadingPercentTip.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12);
mLoadingPercentTip.setEllipsize(TextUtils.TruncateAt.END);
lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
mLoadingPercentTip.setLayoutParams(lp);
mLoadingPercentTip.setText("");
addView(mLoadingPercentTip);
mBottomTip = new TextView(mContext);
mBottomTip.setTextSize(12);
mBottomTip.setGravity(Gravity.CENTER);
lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
mBottomTip.setLayoutParams(lp);
addView(mBottomTip);
mDotSize = 30;
mDotPaint = new Paint();
mDotPaint.setStyle(Paint.Style.FILL);
mDarkGap = 10;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int size = 150;
mLoadingIcon.measure(makeMeasureSpec(size), makeMeasureSpec(size));
int height = 200;
int width = 500;
mLoadingTitle.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), makeMeasureSpec(height));
height = 200;
width = 500;
mLoadingPercentTip.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), makeMeasureSpec(height));
width = 200;
height = 100;
mBottomTip.measure(makeMeasureSpec(width), MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
width = 200;
height = 200;
mBackButton.measure(makeMeasureSpec(width), makeMeasureSpec(height));
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int offsetX = 0;
int offsetY = 0;
mBackButton.layout(offsetX, offsetY, mBackButton.getMeasuredWidth(), mBackButton.getMeasuredHeight() + offsetY);
offsetX = (getMeasuredWidth() - mLoadingIcon.getMeasuredWidth()) / 2;
mLoadingIcon.layout(offsetX, offsetY, offsetX + mLoadingIcon.getMeasuredWidth(),
offsetY + mLoadingIcon.getMeasuredHeight());
offsetX = (getMeasuredWidth() - mLoadingTitle.getMeasuredWidth()) / 2;
offsetY = offsetY + mLoadingIcon.getMeasuredHeight();
mLoadingTitle.layout(offsetX, offsetY, offsetX + mLoadingTitle.getMeasuredWidth(),
offsetY + mLoadingTitle.getMeasuredHeight());
mDarkDotX = getMeasuredWidth() / 2 - mDotSize - mDarkGap;
mDarkDotY = offsetY + mLoadingTitle.getMeasuredHeight();
offsetX = (getMeasuredWidth() - mLoadingPercentTip.getMeasuredWidth()) / 2;
offsetY = offsetY + mLoadingPercentTip.getMeasuredHeight();
mLoadingPercentTip.layout(offsetX, offsetY, offsetX + mLoadingPercentTip.getMeasuredWidth(),
offsetY + mLoadingPercentTip.getMeasuredHeight());
offsetX = (getMeasuredWidth() - mBottomTip.getMeasuredWidth()) / 2;
offsetY = getMeasuredHeight() - mBottomTip.getMeasuredHeight();
mBottomTip.layout(offsetX, offsetY, offsetX + mBottomTip.getMeasuredWidth(), offsetY + mBottomTip.getMeasuredHeight());
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (mPlayingStartAnim) {
mDotPaint.setColor(Color.BLACK);
mDarkDotX = getMeasuredWidth() / 2 - mDotSize - mDarkGap;
for (int i = 0; i < 3; i++) {
mDotPaint.setColor(mLightDotIndex == i ? Color.WHITE : Color.BLACK);
canvas.drawCircle(mDarkDotX, mDarkDotY, mDotSize / 2, mDotPaint);
mDarkDotX = mDarkDotX + mDarkGap + mDotSize;
}
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
super.onTouchEvent(ev);
return true;
}
public void startLoadingAnimation() {
if (mPlayingStartAnim) return;
mPlayingStartAnim = true;
if (mTimerTask == null) {
mTimerTask = new TimerTask() {
@Override
public void run() {
mLightDotIndex++;
if (mLightDotIndex > 2) {
mLightDotIndex = 0;
}
ExecutorUtils.runOnMain(new Runnable() {
@Override
public void run() {
invalidate();
// 更新百分比的值
if (isCanShowPercentage()) {
if (mPercentValue == 0) {
mPercentValue = 52;
} else if (mPercentValue < 99) {
mPercentValue++;
}
mLoadingPercentTip.setText(String.format("%d%%", mPercentValue));
}
}
});
}
};
}
if (mTimer == null) {
try {
mTimer = new Timer();
mTimer.schedule(mTimerTask, 0, 200);
} catch (Throwable throwable) {
RVLogger.e(TAG, "printMonitor error", throwable);
}
}
RVLogger.d(TAG, "SplashLoadingView... startLoading Animation");
}
public void stopLoadingAnimation() {
mPlayingStartAnim = false;
if (mTimer != null) {
mTimer.cancel();
}
if (mTimerTask != null) {
mTimerTask.cancel();
}
invalidate();
RVLogger.d(TAG, "SplashLoadingView... stopLoading Animation");
}
private int getDimen(int id) {
return mContext.getResources().getDimensionPixelSize(id);
}
private int makeMeasureSpec(int size) {
return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
}
public void onStart() {
updateStatusBar();
startLoadingAnimation();
}
public void onStop() {
stopLoadingAnimation();
mLoadingPercentTip.setVisibility(GONE);
RVLogger.d(TAG, "SplashLoadingView... stop");
}
@Override
public void onFail() {
onStop();
Map<String, Object> msgData = new HashMap<>();
msgData.put(CustomLoadingView.DATA_UPDATE_APPEARANCE_LOADING_BOTTOM_TIP, "");
sendMessage(CustomLoadingView.MSG_UPDATE_APPEARANCE, msgData);
}
public void onHandleMessage(String msg, Map<String, Object> data) {
if (MSG_UPDATE_APPEARANCE.equals(msg)) {
String bgColor = (String) data.get(DATA_UPDATE_APPEARANCE_BG_COLOR);
if (!TextUtils.isEmpty(bgColor)) {
setBackgroundColor(Color.parseColor(bgColor));
}
Drawable loadingIcon = (Drawable) data.get(DATA_UPDATE_APPEARANCE_LOADING_ICON);
if (loadingIcon != null) {
mLoadingIcon.setImageDrawable(loadingIcon);
}
String text = (String) data.get(DATA_UPDATE_APPEARANCE_LOADING_TEXT);
if (text != null) {
mLoadingTitle.setText(text);
}
String textColor = (String) data.get(DATA_UPDATE_APPEARANCE_LOADING_TEXT_COLOR);
if (!TextUtils.isEmpty(textColor)) {
mLoadingTitle.setTextColor(Color.parseColor(textColor));
}
String bottomTip = (String) data.get(DATA_UPDATE_APPEARANCE_LOADING_BOTTOM_TIP);
if (bottomTip != null) {
mBottomTip.setText(bottomTip);
}
}
}
public void performAnimation(final String animationType, final Animator.AnimatorListener animationListener) {
if (Looper.myLooper() == Looper.getMainLooper()) {
doPerformAnimation(animationType, animationListener);
} else {
post(new Runnable() {
@Override
public void run() {
doPerformAnimation(animationType, animationListener);
}
});
}
}
private void doPerformAnimation(final String animationType, final Animator.AnimatorListener animationListener) {
if (getParent() == null) {
RVLogger.e(TAG, "loading view has not added to parent container");
return;
}
if (ANIMATION_STOP_LOADING_PREPARE.equals(animationType)) {
mPlayingStartAnim = false;
int offsetTargetY = 0;
float titleTargetX = 0f;
if (isBackButtonVisible()) {
titleTargetX = mBackButton.getX() + mBackButton.getMeasuredWidth();
} else {
titleTargetX = getTitleLeftMargin();
}
float titleTargetY = (200 - mLoadingTitle.getMeasuredHeight()) / 2;
AnimatorSet prepareStopLoadingAnimator = new AnimatorSet();
prepareStopLoadingAnimator.setDuration(400);
if (animationListener != null) {
prepareStopLoadingAnimator.addListener(animationListener);
}
prepareStopLoadingAnimator.play(ObjectAnimator.ofFloat(mLoadingIcon, "y", mLoadingIcon.getY(), offsetTargetY))
.with(ObjectAnimator.ofFloat(mLoadingIcon, "scaleX", mLoadingIcon.getScaleX(), 0))
.with(ObjectAnimator.ofFloat(mLoadingIcon, "scaleY", mLoadingIcon.getScaleY(), 0))
.with(ObjectAnimator.ofFloat(mLoadingTitle, "x", mLoadingTitle.getX(), titleTargetX))
.with(ObjectAnimator.ofFloat(mLoadingTitle, "y", mLoadingTitle.getY(), titleTargetY));
prepareStopLoadingAnimator.start();
} else {
performAnimation(animationType, animationListener);
}
}
@Override
public void updateLoadingInfo(EntryInfo entryInfo) {
Map<String, Object> msgData = new HashMap<>();
msgData.put(CustomLoadingView.DATA_UPDATE_APPEARANCE_LOADING_TEXT, entryInfo.title);
sendMessage(CustomLoadingView.MSG_UPDATE_APPEARANCE, msgData);
H5ImageUtil.loadImage(entryInfo.iconUrl, null, new H5ImageListener() {
@Override
public void onImage(Bitmap bitmap) {
RVLogger.d(TAG, "onBitmapLoaded!");
Map<String, Object> msgData = new HashMap<>();
int dimen = 100;
Bitmap displayBitmap = ImageUtil.scaleBitmap(bitmap, dimen, dimen);
msgData.put(CustomLoadingView.DATA_UPDATE_APPEARANCE_LOADING_ICON, new BitmapDrawable(displayBitmap));
sendMessage(CustomLoadingView.MSG_UPDATE_APPEARANCE, msgData);
}
});
}
@Override
public View getView() {
return this;
}
@Override
public void onExit() {
performAnimation(CustomLoadingView.ANIMATION_STOP_LOADING_PREPARE, new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
RVLogger.d(TAG, "onAnimationStart");
}
@Override
public void onAnimationEnd(Animator animation) {
RVLogger.d(TAG, "onAnimationEnd");
}
@Override
public void onAnimationCancel(Animator animation) {
RVLogger.d(TAG, "onAnimationCancel");
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
}
private void updateStatusBar() {
if (hostActivity != null && hostActivity.getClass().getName().equals("com.alipay.mobile.core.loading.impl.LoadingPage")) {
StatusBarUtils.setTransparentColor(hostActivity, defaultAlphaColor);
}
}
protected boolean isBackButtonVisible() {
return true;
}
protected float getTitleLeftMargin() {
return 0f;
}
private boolean isCanShowPercentage() {
if (mStartLoadingTime == 0) {
mStartLoadingTime = System.currentTimeMillis();
}
long time = System.currentTimeMillis();
return ((time - mStartLoadingTime) > TIME_DELAY_FOR_SHOW_PERCENTAGE);
}
public final void sendMessage(final String msg, final Map<String, Object> data) {
this.post(new Runnable() {
public void run() {
try {
CustomLoadingView.this.onHandleMessage(msg, data);
} catch (Throwable e) {
RVLogger.e(TAG, e);
}
}
});
}
}
支持调试面板功能
// Mriver 初始化完成时调用,默认只有预览和真机调试小程序才显示
MriverEngine.enableDebugConsole();
// 强制所有小程序显示调试面板
Mriver.setConfig("mriver_show_debug_menu_all", "YES");
自定义更多菜单栏
Mriver.setProxy(MRTinyMenuProxy.class, new MRTinyMenuProxy() {
@Override
public ITinyMenuPopupWindow createTinyMenuPopupWindow(Context context, TinyMenuViewModel tinyMenuViewModel) {
return new DemoTinyMenuPopupWindow(context, tinyMenuViewModel);
}
});
// DemoTinyMenuPopupWindow 实现参考内部的 TinyMenuModalWindow
监听拦截返回
Mriver.setConfig("enable_back_perform", "YES");
List<String> tt = new ArrayList<String>();
tt.add(BackInterceptPoint.class.getName());// 接口 类名
Mriver.registerPoint(DemoBackInterceptPointProviderImp.class.getName(), tt);
public class DemoBackInterceptPointProviderImp implements BackInterceptPoint {
@Override
public boolean intercepted(final Render render, int i, CommonBackPerform.BackHandler backHandler, GoBackCallback goBackCallback) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
Toast.makeText(render.getActivity(), "返回键" ,Toast.LENGTH_LONG).show();
}
});
return false; // true表示拦截
}
@Override
public void onInitialized() {
Log.i("BackPoint", "BackInterceptPoint--onInitialized--:");
}
@Override
public void onFinalized() {
Log.i("BackPoint", "BackInterceptPoint--onFinalized--:");
}
}
监听拦截关闭
Mriver.setProxy(AppCloseInterceptProxy.class, new AppCloseInterceptProxy() {
@Override
public boolean intercept(Context context, Page page) {
showToast("关闭键");
return false; // true表示拦截
}
});
自定义 appx loading 动画(仅支持 GIF)
在小程序 mini.project.json
文件中添加 "nonLoadingIndicator": false
。
代码示例如下:
Mriver.registerPoint(MriverResourceInterceptor.class.getName(),
Arrays.asList("com.alibaba.ariver.resource.api.extension.ResourceInterceptPoint"));
Mriver.setConfig("mriver_custom_appxloading", CUSTOM_LOADING_RESOURCE);
// loading的大小:占屏幕宽度的比例,20表示loading控件大小为屏幕宽度的20%
Mriver.setConfig("mriver_custom_appxloading_size", "20");
ResourcePackage resourcePackage = new GlobalResourcePackage("00000001") {
@Override
protected boolean needWaitSetupWhenGet() {
return false;
}
@Override
public boolean needWaitForSetup() {
return false;
}
@Override
protected boolean canHotUpdate(String hotVersion) {
return false;
}
@Override
public Resource get(ResourceQuery query) {
// 可以拦截所有资源
if (TextUtils.equals(CUSTOM_LOADING_RESOURCE, query.pureUrl)) {
return getPresetImageResource(UIStyleActivity.this, query);
}
return null;
}
};
GlobalPackagePool.getInstance().add(resourcePackage);
private Resource getPresetImageResource(Context application, ResourceQuery query) {
Resource sResource = null;
if (sResource == null) {
InputStream inputStream = null;
AssetManager am = application.getAssets();
try {
inputStream = am.open("preset/custom_loading.gif");
int length = inputStream.available();
byte[] buffer = new byte[length];
inputStream.read(buffer);
sResource = new OfflineResource(query.pureUrl, buffer);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (Throwable t) {
t.printStackTrace();
}
}
}
}
return sResource;
}
资源管理
// 主动删除本地小程序
MriverResource.deleteApp("xxxx");
// 获取所有小程序信息列表
Map<String, List<AppModel>> allApp = MriverResource.getAllApp();
// 主动更新所有
MriverResource.updateAll(new UpdateAppCallback() {
@Override
public void onSuccess(List<AppModel> list) {
showToast("userid能拉到的所有信息小程序更新成功");
}
@Override
public void onError(UpdateAppException e) {
showToast(e.getMessage());
}
});
// 主动更新特定小程序
Map<String, String> updateApp = new HashMap<>();
updateApp.put("xxx", "");
MriverResource.updateApp(updateApp, new UpdateAppCallback() {
@Override
public void onSuccess(List<AppModel> list) {
showToast("appid=2021042520210425的小程序更新成功");
}
@Override
public void onError(UpdateAppException e) {
showToast(e.getMessage());
}
});
// 主动下载小程序
MriverResource.downloadAppPackage("xxx", new PackageDownloadCallback() {
@Override
public void onPrepare(String s) {
//做一些辅助的工作如可以打个日志
}
@Override
public void onProgress(String s, int i) {
//进度
showToast("i=" + i);
}
@Override
public void onCancel(String s) {
//用户不用关心。取消是内部网络库的取消api
}
@Override
public void onFinish(String s) {
showToast(s);
}
@Override
public void onFailed(String s, int i, String s1) {
showToast("onFailed--" + s);
}
});
预置小程序
将小程序
.amr
包和小程序信息放到assets/mriver/legacy
目录即可。重要文件名规则为:appId.amr,不要带版本号。
小程序信息放到
nebula_preset.json
里,参考如下:{ "config":{ "updateReqRate":16400, "limitReqRate":13600, "appPoolLimit":3, "versionRefreshRate":86400 }, "data":[ { "app_desc":"预置小程序", "app_id":"2022080915350001", "auto_install":1, "extend_info":{ "launchParams":{ "enableTabBar":"YES", "enableKeepAlive":"NO", "enableDSL":"YES", "nboffline":"sync", "enableWK":"YES", "page":"page/tabBar/component/index", "tinyPubRes":"YES", "enableJSC":"YES" }, "usePresetPopmenu":"YES" }, "fallback_base_url":"https://xxx/2022080915350001/1.0.1.0_all/nebula/fallback/", "global_pack_url":"", "installType":1, "main_url":"/index.html#page/tabBar/component/index", "name":"预置小程序", "online":1, "package_url":"https://xxx/2022080915350001/1.0.1.0_all/nebula/2022080915350001_1.0.1.0.amr", "patch":"", "sub_url":"", "version":"1.0.1.0", "vhost":"https://2022080915350001.h5app.com" } ], "resultCode":100, "resultMsg":"操作成功", "state":"success" }
如需提前安装,参考如下代码。
private void checkPresetInstalled() { Map<String, AppModel> appModelMap = RVProxy.get(RVResourcePresetProxy.class).getPresetAppInfos(); Map<String, RVResourcePresetProxy.PresetPackage> packageMap = RVProxy.get(RVResourcePresetProxy.class).getPresetPackage(); Set<String> stringSet = packageMap.keySet(); for (String key: stringSet) { RVResourcePresetProxy.PresetPackage presetPackage = packageMap.get(key); AppModel presetModel = appModelMap.get(key); if (presetModel != null && presetPackage != null && presetPackage.getInputStream() != null) { AppModel appModel = MriverResource.getAppModel(presetModel.getAppId()); boolean available = ((RVResourceManager)RVProxy.get(RVResourceManager.class)).isAvailable(appModel); if (!available) { if (TextUtils.equals(appModel.getAppVersion(), presetModel.getAppVersion())) { InternalUtils.installApp(presetModel, presetPackage.getInputStream()); } else { // 决定是否提前安装线上版本 } } } } }
资源加载拦截
ResourcePackage resourcePackage = new GlobalResourcePackage("00000001") {
@Override
protected boolean needWaitSetupWhenGet() {
return false;
}
@Override
public boolean needWaitForSetup() {
return false;
}
@Override
protected boolean canHotUpdate(String hotVersion) {
return false;
}
@Override
public Resource get(ResourceQuery query) {
// 可以拦截所有资源
if (TextUtils.equals("指定资源路径", query.pureUrl)) {
return getLocalResource(UIStyleActivity.this, query);
}
return null;
}
};
GlobalPackagePool.getInstance().add(resourcePackage);
签名校验
// 开启签名
MriverResource.enableVerify(MriverResource.VERIFY_TYPE_YES,"公钥");
// 关闭签名
MriverResource.disableVerify( );
开启保活
Mriver.setConfig("enable_keep_alive", "YES");
Mriver.setConfig("mriver_keep_alive_time", "120000"); // 保活时间2分钟
Mriver.setConfig("mriver_keepalive_max", "3"); // 最大保活个数3
Android 小程序保活基于 Activity Task Stack 实现,如果小程序存在跳转原生页面(例如登录)或者其他 App 页面(例如三方支付、分享等)时,需要注意:
如果和小程序页面在同个一个栈里不会存在风险。
如果这些页面是单独的 activity stack,可能会影响返回栈顺序,需要回归相关逻辑。
默认情况下,保活唤醒时如果指定了页面,并且指定的页面并不是小程序当前页,唤醒时会刷新成指定页面。
可以通过设置 refererBiz 参数忽略刷新,直接唤起当前页:
Bundle intent = new Bundle();
intent.putString("page", "page/component/view/view");
intent.putString("refererBiz", "home");
Mriver.startApp(appId, intent);
如果 refererBiz 值固定为 home,保活唤醒时会忽略指定的页面。如果没有已保活的小程序,则会打开指定页面。
如果 refererBiz 值为其他(业务自定义即可),保活唤醒时会和上一次打开的 referer 值对比,如果一致会忽略指定的页面。 如果没有已保活的小程序,则会打开指定页面。
启动指定版本小程序
MriverResource.deleteApp("2022080918000001"); // 删除本地小程序
Bundle bundle = new Bundle();
bundle.putString(RVStartParams.LONG_NB_TARGET_VERSION, "指定版本号");
Mriver.startApp(TargetVersionActivity.this, "2022080918000001", bundle);
自定义 JSAPI
// 自定义tinyToNative的jsapi
MriverEngine.registerBridge(CustomApiBridgeExtension.class);
public class CustomApiBridgeExtension extends SimpleBridgeExtension {
private static final String TAG = "CustomApiBridgeExtension";
@ActionFilter
public void tinyToNative(@BindingId String id,
@BindingNode(App.class) App app,
@BindingNode(Page.class) Page page,
@BindingApiContext ApiContext apiContext,
@BindingExecutor(ExecutorType.UI) Executor executor,
@BindingRequest JSONObject params,
@BindingParam("param1") String param1,
@BindingParam("param2") String param2,
@BindingCallback BridgeCallback callback) {
RVLogger.d(TAG, "id: "+id+
"\napp: "+app.toString()+
"\npage: "+page.toString()+
"\napiContext: "+apiContext.toString()+
"\nexecutor: "+executor.toString());
RVLogger.d(TAG, JSONUtils.toString(params));
JSONObject result = BridgeResponse.SUCCESS.get();
//result.put("message", "客户端接收到参数:" + param1 + ", " + param2 + "\n返回 Demo 当前包名:" + apiContext.getActivity().getPackageName());
// 将结果返回给小程序
Stack stack = MriverApp.getAppStack();
Enumeration enumerationLists = stack.elements();
JSONArray jsonArray = new JSONArray();
while (enumerationLists.hasMoreElements()) {
JSONObject jsonObject = new JSONObject();
MRApp o = (MRApp) enumerationLists.nextElement();
jsonObject.put("AppId", o.getAppId());
jsonObject.put("AppVersion", o.getAppVersion());
jsonArray.add(jsonObject);
}
String tinyappStr = jsonArray.toJSONString();
// result.put("message", "客户端接收到参数:" + param1 + ", " + param2 + "\n返回 Demo 当前包名:" + apiContext.getActivity().getPackageName());
result.put("message", tinyappStr);
callback.sendJSONResponse(result);
}
}
开启分享功能
Mriver.setConfig("mr_showShareMenuItem", "YES");
// 实现ShareApiBridgeExtension
public class ShareApiBridgeExtension extends SimpleBridgeExtension {
private static final String TAG = "CustomApiBridgeExtension";
@ActionFilter
public void shareTinyAppMsg(@BindingId String id,
@BindingNode(App.class) App app,
@BindingNode(Page.class) Page page,
@BindingApiContext ApiContext apiContext,
@BindingExecutor(ExecutorType.UI) Executor executor,
@BindingRequest JSONObject params,
final @BindingCallback BridgeCallback callback) {
Log.i("ShareApiBridge", "share: " + (params == null ? "null" : params.toJSONString()));
String title = params.getString("title");
String desc = params.getString("desc");
String myprop = params.getString("myprop");
String path = params.getString("page");
String appId = app.getAppId();
// 此处可调用分享组件,实现后续功能
String message = "应用ID: " + appId + "\n"
+ "title: " + title + "\n"
+ "desc: " + desc + "\n"
+ "myprop: " + myprop + "\n"
+ "path: " + path + "\n";
AUNoticeDialog dialog = new AUNoticeDialog(apiContext.getActivity(),
"分享结果", message, "分享成功", "分享失败");
dialog.setPositiveListener(new AUNoticeDialog.OnClickPositiveListener() {
@Override
public void onClick() {
JSONObject result = BridgeResponse.SUCCESS.get();
result.put("success", true);
callback.sendJSONResponse(result);
}
});
dialog.setNegativeListener(new AUNoticeDialog.OnClickNegativeListener() {
@Override
public void onClick() {
callback.sendBridgeResponse(BridgeResponse.newError(11, "分享失败"));
}
});
dialog.show();
}
}
权限弹窗
// 自定义权限管控提醒的弹窗
Mriver.setProxy(LocalPermissionDialogProxy.class, new LocalPermissionDialogProxy() {
@Override
public LocalPermissionDialog create(Context context) {
return new DemoLocalPermissionDialog(context);
}
@Override
public boolean interceptPermission(String appId, String page, String action, String scope, List<String> permissions) {
showToast("jsapi: " + action + " 小程序:"+appId+" 页面:"+page);
return false;
}
});
// 弹窗示例
public class DemoLocalPermissionDialog implements LocalPermissionMultiDialog {
private Dialog mDialog;
private final Context mContext;
private PermissionPermitListener mPermissionPermitListener;
public DemoLocalPermissionDialog(Context context) {
this.mContext = context;
}
public void setExtData(String[] permissions, AppModel appModel, Page page, String action, String scope) {
// 支持根据这些参数自定义能力,包括多级弹窗、自定义文案样式等
// permissions: 当前action需要的所有权限列表
// appModel: 当前action的appModel,能取到小程序定义的相关文案
// action: 对应jsapi的action
// scope: 对应jsapi的scope
}
public void setDialogContent(String content, String title, String icon) {
AlertDialog.Builder builder = new AlertDialog.Builder(this.mContext);
builder.setTitle("权限弹窗");
builder.setMessage(content);
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface pDialogInterface, int pI) {
if (DemoLocalPermissionDialog.this.mPermissionPermitListener != null) {
DemoLocalPermissionDialog.this.mPermissionPermitListener.onSuccess();
}
}
});
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface pDialogInterface, int pI) {
if (DemoLocalPermissionDialog.this.mPermissionPermitListener != null) {
DemoLocalPermissionDialog.this.mPermissionPermitListener.onFailed(-1, "", true);
}
}
});
this.mDialog = builder.create();
this.mDialog.show();
}
public void setPermissionPermitListener(PermissionPermitListener permissionPermitListener) {
this.mPermissionPermitListener = permissionPermitListener;
}
public void show() {
if (this.mDialog != null && this.mContext instanceof Activity && !((Activity)this.mContext).isFinishing()) {
this.mDialog.show();
}
}
增加 setExtData 支持权限弹窗更多能力。
自定义 View
升级 appx 到
2.7.18
版本。api ('com.mpaas.mriver:mriverappxplus-build:2.7.18.20220825001@aar') { force=true }
调用相关 API。
RVProxy.set(RVEmbedProxy.class, new RVEmbedProxy() { @Override public Class<?> getEmbedViewClass(String type) { if ("custom_barrage".equalsIgnoreCase(type)) { // type需要和小程序端的type一一对应,根据type返回对应的自定义View return EmbedCustomView.class; } return null; } });
实现自定义 View。
package com.mpaas.demo.tinyapp.engine; import android.content.Context; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.widget.FrameLayout; import com.alibaba.ariver.app.api.Page; import com.alibaba.ariver.engine.api.bridge.extension.BridgeCallback; import com.alibaba.ariver.engine.api.bridge.extension.BridgeResponse; import com.alibaba.ariver.engine.api.embedview.IEmbedView; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.alipay.mobile.beehive.video.h5.live.MRLivePlayerHelper; import com.mpaas.mriver.integration.embed.IMREmbedView; import java.util.Map; public class EmbedCustomView implements IMREmbedView { private Context mContext; private Page mPage; private CustomBarrageView mCustomBarrageView; // 弹幕view示例 @Override public void onCreate(Context context, Page page, IEmbedView iEmbedView) { mContext = context; mPage = page; } @Override public View getView(int width, int height, final String viewId, String type, Map<String, String> params) { Log.i("EmneCustomV", "getView: " + mCustomBarrageView + " " + viewId + " " + type + " " + params); if (mCustomBarrageView == null) { mCustomBarrageView = new CustomBarrageView(mContext); } FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(width, height); mCustomBarrageView.setLayoutParams(layoutParams); return mCustomBarrageView; } // 收到小程序端发送的数据 @Override public void onReceivedMessage(String actionType, JSONObject data, BridgeCallback bridgeCallback) { Log.i("EmneCustomV", "onReceivedMessage: " + actionType); if ("mpaasCustomEvent".equalsIgnoreCase(actionType)) { String innerAction = data.getString("actionType"); if (TextUtils.equals(innerAction, "bindLivePlayer")) { JSONObject dataJSON = data.getJSONObject("data"); // 设置弹幕数据 JSONArray barrages = dataJSON.getJSONArray("barrages"); mCustomBarrageView.setData(barrages); // 绑定liveplayer String bindId = dataJSON.getString("id"); if (!TextUtils.isEmpty(bindId)) { MRLivePlayerHelper.bind(bindId, mCustomBarrageView); } } } } protected void notifySuccess(final BridgeCallback bridgeContext) { if (bridgeContext != null) { bridgeContext.sendBridgeResponse(BridgeResponse.SUCCESS); } } @Override public void onReceivedRender(JSONObject params, BridgeCallback bridgeCallback) { Log.i("EmneCustomV", "onReceivedRender: " + params); notifySuccess(bridgeCallback); } @Override public void onWebViewResume() { } @Override public void onWebViewPause() { } @Override public void onAttachedToWebView() { } @Override public void onDetachedToWebView() { } @Override public void onDestroy() { } @Override public void onRequestPermissionResult(int i, String[] strings, int[] ints) { } @Override public void onEmbedViewVisibilityChanged(int i) { } @Override public void initElementId(String s) { } }
小程序端实现
//page.axml
<mpaas-component
id="mpaas-barrage"
type="custom_barrage" // type类型,需要和native对应起来
style="{{ width: 400, height: 200 }}" // 只能配置宽高
onMpaasCustomEvent="onMpaasCustomEvent" // 收到native事件
/>
//page.js
barrageContext = my.createMpaasComponentContext('mpaas-barrage');
// 发送数据给native
barrageContext.mpaasCustomEvent({
actionType: 'bindLivePlayer',
data: {
"id": "liveplayer",
"barrages": ["有意思", "沙发", "弹幕1", "弹幕2", "弹幕3"]
}
});
}, 100)
页面生命周期监听
初始化时调用如下代码。
List<String> miniAppPoint = new ArrayList<>(); miniAppPoint.add(PageResumePoint.class.getName()); miniAppPoint.add(PagePausePoint.class.getName()); miniAppPoint.add(PageEnterPoint.class.getName()); miniAppPoint.add(AppExitPoint.class.getName()); Mriver.registerPoint(PageLifeCycleExtension.class.getName(), miniAppPoint);
实现
PageLifeCycleExtension.java
。public class PageLifeCycleExtension implements PageResumePoint, PageEnterPoint, PagePausePoint, AppExitPoint { private static final String TAG = "PageLifeCycleExtension"; @Override public void onPageResume(Page page) { } @Override public void onInitialized() { } @Override public void onFinalized() { } @Override public void onPageEnter(Page page) { } @Override public void onPagePause(final Page page) { } @Override public void onAppExit(App app) { } }
APM 监听
如果页面空白区域较大,不会回调 MiniPage_Load_T2
。
初始化时设置如下内容。
RVProxy.set(PrepareNotifyProxy.class, new PrepareNotifyProxy() { @Override public void notify(String s, PrepareStatus prepareStatus) { } @Override public void apmEvent(final String s, final String s1, final String s2, final String s3, final String s4) { // 非UI线程 Log.i("MiniStartTime", "apmE: " + s + " " + s4); if ("MiniAppStart".equalsIgnoreCase(s) || "MiniPage_Load_T2".equalsIgnoreCase(s)) { boolean isT2 = "MiniPage_Load_T2".equalsIgnoreCase(s); String apmData = s4; if (!TextUtils.isEmpty(apmData)) { parseTime(isT2, apmData); } } }); } private void parseTime(boolean isT2, String s4) { String[] kvArrs = s4.split("\\^"); long miniStart = 0; // 小程序点击的时间 long miniPrepared = 0; // 小程序准备阶段完成的时间,首次会包含下载 long miniAppStarted = 0; // 小程序核心阶段完成的时间 long miniT2 = 0; // 小程序T2渲染完成的时间 boolean needIgnore = false; // true表示内部二级页面跳转时二级页面渲染回调,首屏需要忽略 for (String kvItem : kvArrs) { String[] kv = kvItem.split("="); if (kv.length == 2) { String key = kv[0]; String value = kv[1]; if ("mini_st_ts0".equalsIgnoreCase(key)) { // 小程序点击的时间 miniStart = Long.parseLong(value); } else if ("mini_st_ts7".equalsIgnoreCase(key)) { // 小程序准备阶段完成的时间 miniPrepared = Long.parseLong(value); } else if ("mini_st_end_ts".equalsIgnoreCase(key)) { // 小程序核心阶段完成的时间 miniAppStarted = Long.parseLong(value); } else if ("mini_t2_ts".equalsIgnoreCase(key)) { // 小程序T2渲染完成的时间 miniT2 = Long.parseLong(value); } else if (isT2 && "isFirstPage".equalsIgnoreCase(key)) { if ("false".equalsIgnoreCase(value)) { needIgnore = true; } } } } if (!needIgnore && miniStart > 0 && miniPrepared > 0 && miniAppStarted > 0 && miniT2 > 0) { final String toastStr = "准备耗时=" + (miniPrepared - miniStart) + " 核心耗时=" + (miniAppStarted - miniPrepared) + " 业务耗时=" + (miniT2 - miniAppStarted) + " 总耗时=" + (miniT2 - miniStart); Log.i("MiniStartTime", toastStr); mUIHandler.post(new Runnable() { @Override public void run() { Toast.makeText(MRiverApp.sApp, toastStr, Toast.LENGTH_LONG).show(); } }); } }
启动小程序时增加时间戳参数。
Bundle intent = new Bundle(); intent.putString("miniapp_start_ts", Long.toString(System.currentTimeMillis())); Mriver.startApp(FastStartActivity.this, “appId”, intent);
支持 Google 地图
打开开关切换到 Google 地图。
Mriver.setConfig("ta_map_type", "1");
添加 Google 地图依赖并配置 Google 地图的 key。