支援區分體驗版/非體驗版
開啟功能。
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();
// 主動更新特定小程式
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"); // 最大保活個數3Android 小程式保活基於 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。