Support for experience edition and non-experience edition
Enable the function.
Mriver.setConfig("mr_experience_required", "YES");
Open the mini program experience/test version.
MriverResource.de leteApp(appId); // Delete the local version. Bundle bundle = new Bundle(); bundle.putString(RVStartParams.LONG_NB_UPDATE, "synctry"); // The version is forcibly updated. You can use this parameter in combination. bundle.putInt(RVStartParams.LONG_NB_EXPERIENCE_REQUIRED, 1); // 1 indicates the experience version. If no parameter is passed, the official version is used. Mriver.startApp(this, appId, bundle);
Support mini program page return query dialog box
Turn on the return interception switch.
Mriver.setConfig("enable_back_perform", "YES");
Upgrade the Appx version.
// Add the value to build.gradle. api ('com.mpaas.mriver:mriverappxplus-build:2.7.18.20230130001@aar') { force=true }
Call related APIs during mini program development.
// Enable basic retouching. my.enableAlertBeforeUnload({ message: 'Are you sure you want to leave this page?', }); // Disable basic retouching. my.disableAlertBeforeUnload()
Real machine debug /preview
MriverDebug.setWssHost("Real WSS address");
MriverDebug.debugAppByScan(activity);
Custom title bar
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<>();
// The container view of the entire TitleBar.
protected TitleBarFrameLayout contentView;
// The number of OptionMenu items in the upper-right corner. Default value: 1.
protected int visibleOptionNum;
protected Page mPage;
// Bottom line separator
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();
}
/***
* Turn on immersive status bar support
*/
@Override
public void setStatusBarColor(int color) {
if (StatusBarUtils.isSupport()) {
int statusBarHeight = StatusBarUtils.getStatusBarHeight(mContext);
If (statusBarHeight == 0) { // protection, in case rom cannot get the height of the status bar, it will not take effect here.
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();
}
}
}
Custom mini program loading animation
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);// Default transparent color
private static final long TIME_DELAY_FOR_SHOW_PERCENTAGE=2000; // Show the percentage of the delay time 2s.
public final static String MSG_UPDATE_APPEARANCE = "UPDATE_APPEARANCE";
public final static String DATA_UPDATE_APPEARANCE_BG_COLOR = "UPDATE_APPEARANCE_BG_COLOR"; // Page background color# RGB
public final static String DATA_UPDATE_APPEARANCE_LOADING_ICON = "UPDATE_APPEARANCE_LOADING_ICON"; // Loading icon Drawable
public final static String DATA_UPDATE_APPEARANCE_LOADING_TEXT = "UPDATE_APPEARANCE_LOADING_TEXT"; //loading copy
public final static String DATA_UPDATE_APPEARANCE_LOADING_TEXT_COLOR = "UPDATE_APPEARANCE_LOADING_TEXT_COLOR"; //loading copy color# RGB
public final static String DATA_UPDATE_APPEARANCE_LOADING_BOTTOM_TIP = "UPDATE_APPEARANCE_LOADING_BOTTOM_TIP"; // Prompt copy at bottom
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);
// Load percentage
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();
// Update the value of the percentage.
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);
}
}
});
}
}
Supports the debug panel
// Call when Mriver is initialized. By default, only the preview and real-machine debugging mini programs are displayed.
MriverEngine.enableDebugConsole();
// Force all mini programs to display the debugging panel.
Mriver.setConfig("mriver_show_debug_menu_all", "YES");
Customize more menu bars
Mriver.setProxy(MRTinyMenuProxy.class, new MRTinyMenuProxy() {
@Override
public ITinyMenuPopupWindow createTinyMenuPopupWindow(Context context, TinyMenuViewModel tinyMenuViewModel) {
return new DemoTinyMenuPopupWindow(context, tinyMenuViewModel);
}
});
// DemoTinyMenuPopupWindow implementation references internal TinyMenuModalWindow
Listener interception return
Mriver.setConfig("enable_back_perform", "YES");
List<String> tt = new ArrayList<String>();
tt.add(BackInterceptPoint.class.getName());// The name of the interface class.
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(), "Return key" ,Toast.LENGTH_LONG).show();
}
});
return false; // The value true.
}
@Override
public void onInitialized() {
Log.i("BackPoint", "BackInterceptPoint--onInitialized--:");
}
@Override
public void onFinalized() {
Log.i("BackPoint", "BackInterceptPoint--onFinalized--:");
}
}
Listener and interception disable
Mriver.setProxy(AppCloseInterceptProxy.class, new AppCloseInterceptProxy() {
@Override
public boolean intercept(Context context, Page page) {
showToast("Close key");
return false; // The value true.
}
});
Custom appx loading animation (only GIF)
Add "nonLoadingIndicator": false
in the mini.project.json
file of the mini program.
The code example is as follows:
Mriver.registerPoint(MriverResourceInterceptor.class.getName(),
Arrays.asList("com.alibaba.ariver.resource.api.extension.ResourceInterceptPoint"));
Mriver.setConfig("mriver_custom_appxloading", CUSTOM_LOADING_RESOURCE);
// Loading size: the proportion of the screen width. 20 means the loading control size is 20% of the screen width.
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) {
// All resources can be intercepted
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;
}
Resource management
// Delete local mini programs.
MriverResource.deleteApp("xxxx");
// Obtain the information about all mini programs.
Map<String, List<AppModel>> allApp = MriverResource.getAllApp();
// Actively update all
MriverResource.updateAll(new UpdateAppCallback() {
@Override
public void onSuccess(List<AppModel> list) {
showToast("All information mini programs that can be pulled by userid are updated successfully");
}
@Override
public void onError(UpdateAppException e) {
showToast(e.getMessage());
}
});
// Actively update a specific mini program.
Map<String, String> updateApp = new HashMap<>();
updateApp.put("xxx", "");
MriverResource.updateApp(updateApp, new UpdateAppCallback() {
@Override
public void onSuccess(List<AppModel> list) {
showToast("The mini program with appid=2021042520210425 is updated successfully");
}
@Override
public void onError(UpdateAppException e) {
showToast(e.getMessage());
}
});
// Actively download the mini program.
MriverResource.downloadAppPackage("xxx", new PackageDownloadCallback() {
@Override
public void onPrepare(String s) {
// Do some auxiliary work, such as logging
}
@Override
public void onProgress(String s, int i) {
// The progress.
showToast("i=" + i);
}
@Override
public void onCancel(String s) {
// The user does not need to worry. Cancellation is the cancellation api for the internal network library
}
@Override
public void onFinish(String s) {
showToast(s);
}
@Override
public void onFailed(String s, int i, String s1) {
showToast("onFailed--" + s);
}
});
Preset mini program
Put the mini program
.amr
package and mini program information into theassets/mriver/legacy
directory.ImportantThe file name rule is: appId.amr. Do not include the version number.
The mini program information is put into the
nebula_preset.json
, and the reference is as follows:{ "config":{ "updateReqRate":16400, "limitReqRate":13600, "appPoolLimit":3, "versionRefreshRate":86400 }, "data":[ { "app_desc":"Preset mini program", "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":"Preset mini program", "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":"The operation is successful.", "state":"success" }
The following code provides an example on how to install the package in advance.
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 { // Determine whether to install the online version in advance. } } } } }
Resource load interception
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) {
// All resources can be blocked.
if (TextUtils.equals("Specify the resource path", query.pureUrl)) {
return getLocalResource(UIStyleActivity.this, query);
}
return null;
}
};
GlobalPackagePool.getInstance().add(resourcePackage);
Signature verification
// Enable the signature.
MriverResource.enableVerify(MriverResource.VERIFY_TYPE_YES,"public key");
// Disable the signature.
MriverResource.disableVerify( );
Enable keep-alive
Mriver.setConfig("enable_keep_alive", "YES");
Mriver.setConfig("mriver_keep_alive_time", "120000"); // The keep-alive time of 2 minutes.
Mriver.setConfig("mriver_keepalive_max", "3"); // Maximum number of keep-alive 3
Android mini programs are implemented based on the Activity Task Stack. If the mini program jumps to the native page (such as login) or other App pages (such as Third party payment and sharing), note that:
There is no risk if it is in the same stack as the mini program page.
If these pages are separate activity stacks, the return stack order may be affected and relevant logic needs to be regressed.
By default, if a page is specified during a keep-alive wake-up and the specified page is not the current page of the mini program, the specified page will be refreshed during wake-up.
You can set the refererBiz parameter to ignore the refresh and directly evoke the current page:
Bundle intent = new Bundle();
intent.putString("page", "page/component/view/view");
intent.putString("refererBiz", "home");
Mriver.startApp(appId, intent);
If the refererBiz value is fixed to home, the specified page is ignored during the keep-alive wake-up. If no mini program is alive, the specified page opens.
If the refererBiz value is set to another value, the referer value is compared with the referer value that was opened last time. If the refererBiz value is the same, the specified page is ignored. If no mini program is alive, the specified page opens.
Start a mini program of a specified version
MriverResource.de leteApp("2022080918000001"); // Delete the local mini program.
Bundle bundle = new Bundle();
bundle.putString(RVStartParams.LONG_NB_TARGET_VERSION, "the specified version number");
Mriver.startApp(TargetVersionActivity.this, "2022080918000001", bundle);
Custom JSAPI
// Customize the jsapi of tinyToNative.
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", "The client receives parameters:" + param1 + ", " + param2 + "\nThe current package name of the demo:" + apiContext.getActivity().getPackageName());
// Return the result to the mini program.
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", "The client receives parameters:" + param1 + ", " + param2 + "\nThe current package name of the demo:" + apiContext.getActivity().getPackageName());
result.put("message", tinyappStr);
callback.sendJSONResponse(result);
}
}
Enable the sharing feature
Mriver.setConfig("mr_showShareMenuItem", "YES");
// Implement 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();
// You can call the sharing component to implement subsequent features.
String message = "Application ID: " + appId + "\n"
+ "title: " + title + "\n"
+ "desc: " + desc + "\n"
+ "myprop: " + myprop + "\n"
+ "path: " + path + "\n";
AUNoticeDialog dialog = new AUNoticeDialog(apiContext.getActivity(),
"Sharing result", message, "Sharing successful", "Sharing failed");
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, "Sharing failed"));
}
});
dialog.show();
}
}
Permission pop-up window
// Customize the pop-up window for permission control alerts.
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 + "mini program: " + appId + "page: " + page);
return false;
}
});
// Example of a pop-up window
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) {
// Supports customization based on these parameters, including multi-level pop-up windows and custom copy styles.
// permissions: the list of all permissions required by the current action.
// appModel: the appModel of the current action, which can obtain the relevant copy defined by the mini program.
// action: the action of the corresponding JSAPI.
// scope: the scope of the JSAPI.
}
public void setDialogContent(String content, String title, String icon) {
AlertDialog.Builder builder = new AlertDialog.Builder(this.mContext);
builder.setTitle("Permission pop-up window");
builder.setMessage(content);
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface pDialogInterface, int pI) {
if (DemoLocalPermissionDialog.this.mPermissionPermitListener != null) {
DemoLocalPermissionDialog.this.mPermissionPermitListener.onSuccess();
}
}
});
builder.setNegativeButton("Cancel", 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();
}
}
Added more capabilities to the setExtData support permission pop-up window.
Custom view
Upgrade appx to the
2.7.18
version.api ('com.mpaas.mriver:mriverappxplus-build:2.7.18.20220825001@aar') { force=true }
Call related APIs.
RVProxy.set(RVEmbedProxy.class, new RVEmbedProxy() { @Override public Class<?> getEmbedViewClass(String type) { if ("custom_barrage".equalsIgnoreCase(type)) { // The type must correspond to the type of the mini program. The corresponding custom view is returned based on the type. return EmbedCustomView.class; } return null; } });
Implement a custom 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; // The example of the pop-up 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; } // Receive the data sent by the mini program. @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"); // Set the barrage data. JSONArray barrages = dataJSON.getJSONArray("barrages"); mCustomBarrageView.setData(barrages); // Bind the 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) { } }
Mini program-side implementation
//page.axml
<mpaas-component
id="mpaas-barrage"
type="custom_barrage" // The value of the type parameter. The value must be the same as the value of native.
style="{{ width: 400, height: 200 }}" // You can only configure the width and height.
onMpaasCustomEvent="onMpaasCustomEvent" // Receive native events
/>
//page.js
barrageContext = my.createMpaasComponentContext('mpaas-barrage');
// Send data to native.
barrageContext.mpaasCustomEvent({
actionType: 'bindLivePlayer',
data: {
"id": "liveplayer",
"barrages": ["Interesting", "Sofa", "Barrage 1", "Barrage 2", "Barrage 3"]
}
});
}, 100)
Page lifecycle listener
The following code is called during initialization.
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);
Achieve
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 listeners
If the blank area of the page is large, the MiniPage_Load_T2
is not called back.
Set the following content during initialization.
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) { // Non-UI thread 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; // The time when the mini program is clicked. long miniPrepared = 0; // The time when the mini program preparation phase is completed. The first time the mini program is downloaded. long miniAppStarted = 0; // The time when the mini program core phase is completed. long miniT2 = 0; // The time when the T2 mini program is rendered. boolean needIgnore=false; // true indicates the second-level page rendering callback when the internal second-level page jumps. The first screen needs to be ignored. 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)) { // The time when the mini program is clicked. miniStart = Long.parseLong(value); } else if ("mini_st_ts7".equalsIgnoreCase(key)) { // The time when the mini program preparation phase is completed. miniPrepared = Long.parseLong(value); } else if ("mini_st_end_ts".equalsIgnoreCase(key)) { // The time when the core phase of the mini program was completed. miniAppStarted = Long.parseLong(value); } else if ("mini_t2_ts".equalsIgnoreCase(key)) { // The time when the T2 mini program is rendered. 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 = "Preparation time=" + (miniPrepared-miniStart) + "Core time=" + (miniAppStarted-miniPrepared) + "Business time=" + (miniT2-miniAppStarted) + "Total time=" + (miniT2-miniStart); Log.i("MiniStartTime", toastStr); mUIHandler.post(new Runnable() { @Override public void run() { Toast.makeText(MRiverApp.sApp, toastStr, Toast.LENGTH_LONG).show(); } }); } }
Add the timestamp parameter when you start the mini program.
Bundle intent = new Bundle(); intent.putString("miniapp_start_ts", Long.toString(System.currentTimeMillis())); Mriver.startApp(FastStartActivity.this, “appId”, intent);
Support for Google Maps
Turn on the switch to switch to Google Maps.
Mriver.setConfig("ta_map_type", "1");
Add the Google Maps dependency and configure the key for Google Maps.