文档

功能配置

支持区分体验版/非体验版

  1. 开启功能。

    Mriver.setConfig("mr_experience_required", "YES");
  2. 打开小程序体验/测试版本。

    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);

支持小程序页面返回询问对话框

  1. 开启返回拦截开关。

    Mriver.setConfig("enable_back_perform", "YES");
  2. 升级 Appx 版本。

    // build.gradle中添加
    api ('com.mpaas.mriver:mriverappxplus-build:2.7.18.20230130001@aar') {
          force=true
    }
  3. 小程序开发时调用相关 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)

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);

资源管理

// 主动删除本地小程序
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,不要带版本号。

    image.png

    小程序信息放到 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

  1. 升级 appx 到 2.7.18 版本。

    api ('com.mpaas.mriver:mriverappxplus-build:2.7.18.20220825001@aar') {
        force=true
    }
  2. 调用相关 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;
                    }
                });
  3. 实现自定义 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)

页面生命周期监听

  1. 初始化时调用如下代码。

    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);
  2. 实现 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

  1. 初始化时设置如下内容。

    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();
                    }
                });
            }
        }
  2. 启动小程序时增加时间戳参数。

    Bundle intent  = new Bundle();
    intent.putString("miniapp_start_ts", Long.toString(System.currentTimeMillis()));
    Mriver.startApp(FastStartActivity.this, “appId”, intent);

支持 Google 地图

  1. 打开开关切换到 Google 地图。

    Mriver.setConfig("ta_map_type", "1");
  2. 添加 Google 地图依赖并配置 Google 地图的 key。

  • 本页导读 (0)
文档反馈