微信Android客户端的卡顿监控方案

本文介绍了微信Android客户端的卡顿监控方案,包括ANR监控、UI线程监控、帧率检测、慢方法追踪等功能。通过插桩技术、LooperMonitor、AppProfiler等组件,实现对主线程消息处理、应用启动、帧率波动等关键指标的监控,以预防和定位性能问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

2021.8.1  Matrix 2.0 TraceCanary新增了以下功能

微信Android客户端的卡顿监控方案 https://mp.weixin.qq.com/s/3dubi2GVW_rVFZZztCpsKg 

1.监控IdleHandler卡顿 IdleHandlerLagTracer已在Matrix开源,本文已经对其源码分析

2.监控TouchEvent卡顿 提供了思路,在Matrix里没有找到

3.监控SyncBarrier泄漏 提供了代码,在Matrix没有找到

IdleHandler相关知识:https://blog.youkuaiyun.com/plokmju88/article/details/104386173/

微信Android客户端的ANR监控方案 

https://mp.weixin.qq.com/s/fWoXprt2TFL1tTapt7esYg

讲解了SignalAnrTracer,下面这篇文章讲解了源码分析

https://blog.youkuaiyun.com/stone_cold_cool/article/details/119464855

Android的离奇陷阱 — 设置线程优先级导致的微信卡顿惨案https://mp.weixin.qq.com/s/oLz_F7zhUN6-b-KaI8CMRw 

讲解了 ThreadPriorityTracer,本文已经对其源码分析

1.TracePlugin 启动创建UIThreadMonitor,AppMethodBeat,AnrTracer,FrameTracer,EvilMethodTracer,StartupTracer

//ok
public class TracePlugin extends Plugin {
    //region 参数
    private static final String TAG = "Matrix.TracePlugin";

    private final TraceConfig traceConfig;
    private EvilMethodTracer evilMethodTracer;
    private StartupTracer startupTracer;
    private FrameTracer frameTracer;
    private AnrTracer anrTracer;
    //endregion

    //初始化传入TraceConfig
    public TracePlugin(TraceConfig config) {
        this.traceConfig = config;
    }

    @Override
    public void init(Application app, PluginListener listener) {
        super.init(app, listener);
        MatrixLog.i(TAG, "trace plugin init, trace config: %s", traceConfig.toString());
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
            MatrixLog.e(TAG, "[FrameBeat] API is low Build.VERSION_CODES.JELLY_BEAN(16), TracePlugin is not supported");
            unSupportPlugin();
            return;
        }
        
        //创建4个tracer

        anrTracer = new AnrTracer(traceConfig);

        frameTracer = new FrameTracer(traceConfig);

        evilMethodTracer = new EvilMethodTracer(traceConfig);

        startupTracer = new StartupTracer(traceConfig);
    }

    //step 1
    @Override
    public void start() {
        super.start();
        if (!isSupported()) {
            MatrixLog.w(TAG, "[start] Plugin is unSupported!");
            return;
        }
        MatrixLog.w(TAG, "start!");
        Runnable runnable = new Runnable() {
            @Override
            public void run() {

                //UIThreadMonitor 初始化
                if (!UIThreadMonitor.getMonitor().isInit()) {
                    try {
                        UIThreadMonitor.getMonitor().init(traceConfig);
                    } catch (java.lang.RuntimeException e) {
                        MatrixLog.e(TAG, "[start] RuntimeException:%s", e);
                        return;
                    }
                }

                //AppMethodBeat 启动
                AppMethodBeat.getInstance().onStart();

                //UIThreadMonitor 启动
                UIThreadMonitor.getMonitor().onStart();


                //4个tracer启动
                anrTracer.onStartTrace();

                frameTracer.onStartTrace();

                evilMethodTracer.onStartTrace();

                startupTracer.onStartTrace();
            }
        };

        if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
            runnable.run();
        } else {
            MatrixLog.w(TAG, "start TracePlugin in Thread[%s] but not in mainThread!", Thread.currentThread().getId());
            MatrixHandlerThread.getDefaultMainHandler().post(runnable);
        }

    }

    @Override
    public void stop() {
        super.stop();
        if (!isSupported()) {
            MatrixLog.w(TAG, "[stop] Plugin is unSupported!");
            return;
        }
        MatrixLog.w(TAG, "stop!");
        Runnable runnable = new Runnable() {
            @Override
            public void run() {

                AppMethodBeat.getInstance().onStop();

                UIThreadMonitor.getMonitor().onStop();

                anrTracer.onCloseTrace();

                frameTracer.onCloseTrace();

                evilMethodTracer.onCloseTrace();

                startupTracer.onCloseTrace();

            }
        };

        if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
            runnable.run();
        } else {
            MatrixLog.w(TAG, "stop TracePlugin in Thread[%s] but not in mainThread!", Thread.currentThread().getId());
            MatrixHandlerThread.getDefaultMainHandler().post(runnable);
        }

    }

    @Override
    public void onForeground(boolean isForeground) {
        super.onForeground(isForeground);
        if (!isSupported()) {
            return;
        }

        if (frameTracer != null) {
            frameTracer.onForeground(isForeground);
        }

        if (anrTracer != null) {
            anrTracer.onForeground(isForeground);
        }

        if (evilMethodTracer != null) {
            evilMethodTracer.onForeground(isForeground);
        }

        if (startupTracer != null) {
            startupTracer.onForeground(isForeground);
        }

    }

    @Override
    public void destroy() {
        super.destroy();
    }


    //region get方法

    @Override
    public String getTag() {
        return SharePluginInfo.TAG_PLUGIN;
    }

    public FrameTracer getFrameTracer() {
        return frameTracer;
    }

    public AppMethodBeat getAppMethodBeat() {
        return AppMethodBeat.getInstance();
    }

    public AnrTracer getAnrTracer() {
        return anrTracer;
    }

    public EvilMethodTracer getEvilMethodTracer() {
        return evilMethodTracer;
    }

    public StartupTracer getStartupTracer() {
        return startupTracer;
    }

    public UIThreadMonitor getUIThreadMonitor() {
        if (UIThreadMonitor.getMonitor().isInit()) {
            return UIThreadMonitor.getMonitor();
        } else {
            return null;
        }
    }

    public TraceConfig getTraceConfig() {
        return traceConfig;
    }
    //endregion
}

2.TraceConfig配置类,Constant类定义了各种阈值

        public boolean defaultFpsEnable;        //是否开启Fps监控
        public boolean defaultMethodTraceEnable;//是否开启方法监控
        public boolean defaultStartupEnable;    //是否开启startup监控
        public boolean defaultAnrEnable;        //是否开启anr监控
        public boolean isDebug;                 //是否debug
        public boolean isDevEnv;                //是否dev环境
        public String splashActivities;         //splash activity
        public Set<String> splashActivitiesSet; //splash activity set
        public boolean isHasActivity;           //true

        getSplashActivities
        getEvilThresholdMs  //evil方法,超时方法阈值 700ms
        getTimeSliceMs      //帧累计超时时间限制,超过10s上报
        getColdStartupThresholdMs//10s 冷启动时间,超过10s上报
        getWarmStartupThresholdMs//4s 热启动时间,超过4s上报
        getFrozenThreshold  //掉了42帧,特别严重的掉帧
        getHighThreshold    //掉了24帧,比较高的掉帧
        getMiddleThreshold  //9,中等掉帧
        getNormalThreshold  //3,一般掉帧
public class Constants {

    public static final int BUFFER_SIZE = 100 * 10000;  // 7.6M
    public static final int TIME_UPDATE_CYCLE_MS = 5;   //ok 通过一个线程,每五毫秒更新变量,每个方法调用前后只需读取该变量获取时间
    //Matrix默认最多上传30个堆栈。如果堆栈调用超过30条,需要裁剪堆栈。裁剪策略如下:
    //从后往前遍历先序遍历结果,如果堆栈大小大于30,则将执行时间小于5*整体遍历次数的节点剔除掉
    //最多整体遍历60次,每次整体遍历,比较时间增加5ms
    //如果遍历了60次,堆栈大小还是大于30,将后面多余的删除掉
    public static final int TARGET_EVIL_METHOD_STACK = 30;          //最多上传30个堆栈
    public static final int FILTER_STACK_MAX_COUNT = 60;            //stack裁剪次数
    public static final float FILTER_STACK_KEY_ALL_PERCENT = .3F;   //statck里key方法,时常超过一个帧的时常的30%的方法
    public static final int DEFAULT_EVIL_METHOD_THRESHOLD_MS = 700;         //邪恶方法,执行时间长的方法阈值
    public static final int DEFAULT_FPS_TIME_SLICE_ALIVE_MS = 10 * 1000;    //掉帧累计10s,就上报
    public static final int TIME_MILLIS_TO_NANO = 1000000;
    public static final int DEFAULT_ANR = 5 * 1000;             //anr 5s
    public static final int DEFAULT_NORMAL_LAG = 2 * 1000;      //lag 消息 2s
    public static final int DEFAULT_ANR_INVALID = 6 * 1000;     //无效anr,anr倒计时线程,没有在5s唤醒
    public static final long DEFAULT_FRAME_DURATION = 16666667L;//一帧时间

    public static final int DEFAULT_DROPPED_NORMAL = 3; //ok 掉帧
    public static final int DEFAULT_DROPPED_MIDDLE = 9; //ok 150ms
    public static final int DEFAULT_DROPPED_HIGH = 24;  //ok 384ms
    public static final int DEFAULT_DROPPED_FROZEN = 42;//ok 672ms

    public static final int DEFAULT_STARTUP_THRESHOLD_MS_WARM = 4 * 1000; //热启动4s阈值
    public static final int DEFAULT_STARTUP_THRESHOLD_MS_COLD = 10 * 1000;//冷启动10s阈值

    public static final int DEFAULT_RELEASE_BUFFER_DELAY = 15 * 1000;//15秒
    
    public enum Type {
        NORMAL, ANR, STARTUP, LAG
    }
}

3.LooperMonitor   Looper 的监控是由类 LooperMonitor 实现的,原理很简单,为主线程 Looper 设置一个 Printer 即可,LooperDispatchListener既可以知道消息开始,结束

但值得一提的是,LooperMonitor 不会直接设置 Printer,而是先获取旧对象,并创建代理对象,避免影响到其它用户设置的 Printer

    /**
     * 监听Looper 消息分发开始,结束
     */
    public abstract static class LooperDispatchListener {

        boolean isHasDispatchStart = false;

        public boolean isValid() {
            return false;
        }


        public void dispatchStart() {

        }

        @CallSuper
        public void onDispatchStart(String x) {
            this.isHasDispatchStart = true;
            dispatchStart();
        }

        @CallSuper
        public void onDispatchEnd(String x) {
            this.isHasDispatchStart = false;
            dispatchEnd();
        }


        public void dispatchEnd() {
        }
    }

4.UIThreadMonitor使用LooperMonitor监控每个消息开始结束的时间。vsync同步也是消息的一种(所以也监控每一帧)通过Choreographer编舞者,计算每一帧input/动画/绘制三个队列所花的时间。并通过LooperObserver接口通知各LooperObserver。

4.1使用LooperMonitor监控每个消息开始结束的时间。

4.2vsync同步也是消息的一种(所以也监控每一帧)通过Choreographer编舞者,计算每一帧input/动画/绘制三个队列所花的时间

4.3并通过LooperObserver接口通知各LooperObserver

/**
 * 通过LooperMonitor监听ui线程的消息分发,vsync同步也是消息的一种,将结果通过LooperObserver分发出去
 * 通过往编舞者Choreographer ALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL三种类型的CallbackQueue
 * 里添加Runnable,即UIThreadMonitor,当UIThreadMonitor的run被调用了说明这个消息是vsync消息
 */
public class UIThreadMonitor implements BeatLifecycle, Runnable {
    //region 参数
    /**
     * Callback type: Input callback.  Runs first.
     *
     * @hide
     */
    public static final int CALLBACK_INPUT = 0;
    /**
     * Callback type: Animation callback.  Runs before traversals.
     *
     * @hide
     */
    public static final int CALLBACK_ANIMATION = 1;
    /**
     * Callback type: Commit callback.  Handles post-draw operations for the frame.
     * Runs after traversal completes.
     *
     * @hide
     */
    public static final int CALLBACK_TRAVERSAL = 2;
    /**
     * never do queue end code
     */
    public static final int DO_QUEUE_END_ERROR = -100;
    private static final String TAG = "Matrix.UIThreadMonitor";
    // Choreographer 中一个内部类的方法,用于添加回调
    private static final String ADD_CALLBACK = "addCallbackLocked";
    // The time of the oldest input event,没有用到
    private static final int OLDEST_INPUT_EVENT = 3;
    // The time of the newest input event,没用到
    private static final int NEWEST_INPUT_EVENT = 4;
    private static final int CALLBACK_LAST = CALLBACK_TRAVERSAL;
    // 回调类型,分别为输入事件、动画、View 绘制三种
    private final static UIThreadMonitor sInstance = new UIThreadMonitor();
    private static final int DO_QUEUE_DEFAULT = 0;
    private static final int DO_QUEUE_BEGIN = 1;
    private static final int DO_QUEUE_END = 2;
    private final HashSet<LooperObserver> observers = new HashSet<>();  //用于通知帧状态
    private final long[] dispatchTimeMs = new long[4];//dispatchTimeMs 0,2位置为开始时间,0为纳秒,2为毫秒,1,3为结束时间
    private volatile boolean isAlive = false;
    private volatile long token = 0L;       //消息执行开始时间,也用做token
    private boolean isVsyncFrame = false;   //frame开始标记
    private TraceConfig config;
    private Object callbackQueueLock;   //用于同步
    private Object[] callbackQueues;    //队列数组,保存
    private Method addTraversalQueue;   //往绘制队列添加callback的方法
    private Method addInputQueue;
    private Method addAnimationQueue;
    private Choreographer choreographer;//Choreographer机制,用于同vsync机制配合,统一动画,输入,绘制时机。
    private Object vsyncReceiver;       //获取了一个getIntendedFrameTimeNs vsync开始的时间,校对时间
    private long frameIntervalNanos = 16666666;
    private int[] queueStatus = new int[CALLBACK_LAST + 1];                 //队列状态
    private boolean[] callbackExist = new boolean[CALLBACK_LAST + 1];       //用于标记callback是否添加
    private long[] queueCost = new long[CALLBACK_LAST + 1];                 //每个队列花费时间
    private boolean isInit = false;                                         //是否init
    private long[] frameInfo = null;//没用到
    //endregion

    //region step 1 init
    public void init(TraceConfig config) {
        if (Thread.currentThread() != Looper.getMainLooper().getThread()) {
            throw new AssertionError("must be init in main thread!");
        }
        this.config = config;
        choreographer = Choreographer.getInstance();
        //private final Object mLock = new Object(); 这是个同步锁,addFrameCallback会用到
        callbackQueueLock = ReflectUtils.reflectObject(choreographer, "mLock", new Object());
        // 回调队列 private final CallbackQueue[] mCallbackQueues;
        callbackQueues = ReflectUtils.reflectObject(choreographer, "mCallbackQueues", null);
        if (null != callbackQueues) {
            // 反射,找到在 Choreographer内部类 CallbackQueue 上添加回调的addCallbackLocked方法
            // 反射获取Choreographer中CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL三种类型的CallbackQueue的addCallbackLocked方法的句柄。
            addInputQueue = ReflectUtils.reflectMethod(callbackQueues[CALLBACK_INPUT], ADD_CALLBACK, long.class, Object.class, Object.class);
            addAnimationQueue = ReflectUtils.reflectMethod(callbackQueues[CALLBACK_ANIMATION], ADD_CALLBACK, long.class, Object.class, Object.class);
            addTraversalQueue = ReflectUtils.reflectMethod(callbackQueues[CALLBACK_TRAVERSAL], ADD_CALLBACK, long.class, Object.class, Object.class);
        }
        //这个Receiver用来获取屏幕lcd硬件显示vsync信息
        vsyncReceiver = ReflectUtils.reflectObject(choreographer, "mDisplayEventReceiver", null);
        //每一帧的时间16ms的纳秒值
        frameIntervalNanos = ReflectUtils.reflectObject(choreographer, "mFrameIntervalNanos", Constants.DEFAULT_FRAME_DURATION);
        //向LooperMonitor注册Message执行开始的回调、执行结束的回调。这里的Message是指主线程中发生的所有Message,
        //包括App自己的以及Framework中的,Choreographer中的自然也可以捕获到。
        LooperMonitor.register(new LooperMonitor.LooperDispatchListener() {
            @Override
            public boolean isValid() {
                return isAlive;
            }

            @Override
            public void dispatchStart() {
                super.dispatchStart();
                UIThreadMonitor.this.dispatchBegin();
            }

            @Override
            public void dispatchEnd() {
                super.dispatchEnd();
                UIThreadMonitor.this.dispatchEnd();
            }

        });
        this.isInit = true;
        MatrixLog.i(TAG, "[UIThreadMonitor] %s %s %s %s %s %s frameIntervalNanos:%s", callbackQueueLock == null, callbackQueues == null,
                addInputQueue == null, addTraversalQueue == null, addAnimationQueue == null, vsyncReceiver == null, frameIntervalNanos);


        if (config.isDevEnv()) {
            addObserver(new LooperObserver() {
                @Override
                public void doFrame(String focusedActivity, long startNs, long endNs, boolean isVsyncFrame, long intendedFrameTimeNs, long inputCostNs, long animationCostNs, long traversalCostNs) {
                    MatrixLog.i(TAG, "focusedActivity[%s] frame cost:%sms isVsyncFrame=%s intendedFrameTimeNs=%s [%s|%s|%s]ns",
                            focusedActivity, (endNs - startNs) / Constants.TIME_MILLIS_TO_NANO, isVsyncFrame, intendedFrameTimeNs, inputCostNs, animationCostNs, traversalCostNs);
                }
            });
        }
    }
    //endregion

    //region step 4 dispatchBegin
    private void dispatchBegin() {
        token = dispatchTimeMs[0] = System.nanoTime();              //记录时间,dispatchTimeMs 0,2位置为开始时间
        dispatchTimeMs[2] = SystemClock.currentThreadTimeMillis();  //记录时间
        AppMethodBeat.i(AppMethodBeat.METHOD_ID_DISPATCH);          //打个点

        synchronized (observers) {
            for (LooperObserver observer : observers) {
                if (!observer.isDispatchBegin()) {
                    observer.dispatchBegin(dispatchTimeMs[0], dispatchTimeMs[2], token);
                }
            }
        }
        if (config.isDevEnv()) {
            MatrixLog.d(TAG, "[dispatchBegin#run] inner cost:%sns", System.nanoTime() - token);
        }
    }

    private void dispatchEnd() {
        long traceBegin = 0;//用于devenv记录时间打log用
        if (config.isDevEnv()) {//用于devenv记录时间打log用
            traceBegin = System.nanoTime();//用于devenv记录时间打log用
        }
        long startNs = token;
        long intendedFrameTimeNs = startNs;
        if (isVsyncFrame) {
            doFrameEnd(token);
            intendedFrameTimeNs = getIntendedFrameTimeNs(startNs);//如果是vsyncframe的话,intendedFrameTimeNs代表vsync 帧的校对时间
        }

        long endNs = System.nanoTime();

        synchronized (observers) {
            for (LooperObserver observer : observers) {
                if (observer.isDispatchBegin()) {
                    observer.doFrame(AppMethodBeat.getVisibleScene(), startNs, endNs, isVsyncFrame, intendedFrameTimeNs, queueCost[CALLBACK_INPUT], queueCost[CALLBACK_ANIMATION], queueCost[CALLBACK_TRAVERSAL]);
                }
            }
        }

        dispatchTimeMs[3] = SystemClock.currentThreadTimeMillis();
        dispatchTimeMs[1] = System.nanoTime();

        AppMethodBeat.o(AppMethodBeat.METHOD_ID_DISPATCH);

        synchronized (observers) {
            for (LooperObserver observer : observers) {
                if (observer.isDispatchBegin()) {
                    observer.dispatchEnd(dispatchTimeMs[0], dispatchTimeMs[2], dispatchTimeMs[1], dispatchTimeMs[3], token, isVsyncFrame);
                }
            }
        }
        this.isVsyncFrame = false;

        if (config.isDevEnv()) {//用于devenv记录时间打log用
            MatrixLog.d(TAG, "[dispatchEnd#run] inner cost:%sns", System.nanoTime() - traceBegin);//用于devenv记录时间打log用
        }
    }
    //endregion

    //region step 2 onStart,初始化callbackExist,queueStatus,queueCost三个值,然后调用addFrameCallback CALLBACK_INPUT
//    归根结底就是向Choreograpger注册了一个回调(即UIThreadMonitor自身),这样下次Vsync信号来到时,
//    就会触发这个callback(即会执行UIThreadMonitor的 run方法 *****很重要*****,执行这个run即表明是vsync帧)。
    @Override
    public synchronized void onStart() {
        if (!isInit) {
            MatrixLog.e(TAG, "[onStart] is never init.");
            return;
        }
        if (!isAlive) {
            this.isAlive = true;
            synchronized (this) {
                MatrixLog.i(TAG, "[onStart] callbackExist:%s %s", Arrays.toString(callbackExist), Utils.getStack());
                callbackExist = new boolean[CALLBACK_LAST + 1];//记录是否已经向该类型的CallbackQueue添加了Runnable,避免重复添加
            }
            //记录CallbackQueue中添加的Runnable的运行状态,分为默认状态(DO_QUEUE_DEFAULT)、已经添加的状态
            // (DO_QUEUE_BEGIN)、运行结束的状态(DO_QUEUE_END)
            queueStatus = new int[CALLBACK_LAST + 1];
            //记录上面某种类型的Runnable的执行起始的耗时,这可以反映出当前这一次执行CallbackQueue里面的任务耗时有多久。
            queueCost = new long[CALLBACK_LAST + 1];
//            这里首先会判断某种type类型的callback是否已经添加,UIThreadMonitor是否已经启动等等检查,
//            然后根据type取得需要invoke的方法句柄,然后调用该方法并设置callbackExist标志位。
            addFrameCallback(CALLBACK_INPUT, this, true);
        }
    }
    //endregion

    //region step 3 addFrameCallback run
    //    addFrameCallback方法将一个Runnable(自己)插到了INPUT类型的CallbackQueue的头部。
    //    CallbackQueue是一个单链表组织起来的队列,里面按照时间从小到大进行组织。
    private synchronized void addFrameCallback(int type, Runnable callback, boolean isAddHeader) {
        if (callbackExist[type]) {
            MatrixLog.w(TAG, "[addFrameCallback] this type %s callback has exist! isAddHeader:%s", type, isAddHeader);
            return;
        }

        if (!isAlive && type == CALLBACK_INPUT) {
            MatrixLog.w(TAG, "[addFrameCallback] UIThreadMonitor is not alive!");
            return;
        }
        try {
            synchronized (callbackQueueLock) {
                Method method = null;
                switch (type) {
                    case CALLBACK_INPUT:
                        method = addInputQueue;
                        break;
                    case CALLBACK_ANIMATION:
                        method = addAnimationQueue;
                        break;
                    case CALLBACK_TRAVERSAL:
                        method = addTraversalQueue;
                        break;
                }
                if (null != method) {
                    method.invoke(callbackQueues[type], !isAddHeader ? SystemClock.uptimeMillis() : -1, callback, null);
                    callbackExist[type] = true;
                }
            }
        } catch (Exception e) {
            MatrixLog.e(TAG, e.toString());
        }
    }

    //执行run方法,代表这个消息是vsync frame消息
    @Override
    public void run() {
        final long start = System.nanoTime();
        try {
            //执行run方法,代表这个消息是vsync frame消息
            doFrameBegin(token);
            doQueueBegin(CALLBACK_INPUT);

            addFrameCallback(CALLBACK_ANIMATION, new Runnable() {

                @Override
                public void run() {
                    doQueueEnd(CALLBACK_INPUT);
                    doQueueBegin(CALLBACK_ANIMATION);
                }
            }, true);

            addFrameCallback(CALLBACK_TRAVERSAL, new Runnable() {

                @Override
                public void run() {
                    doQueueEnd(CALLBACK_ANIMATION);
                    doQueueBegin(CALLBACK_TRAVERSAL);
                }
            }, true);

        } finally {
            if (config.isDevEnv()) {
                MatrixLog.d(TAG, "[UIThreadMonitor#run] inner cost:%sns", System.nanoTime() - start);
            }
        }
    }

    private void doQueueBegin(int type) {
        queueStatus[type] = DO_QUEUE_BEGIN;//设置queueStatus[type]状态为begin
        queueCost[type] = System.nanoTime();//记录初始值时间
    }

    private void doQueueEnd(int type) {
        queueStatus[type] = DO_QUEUE_END;//设置queueStatus[type]状态为end
        queueCost[type] = System.nanoTime() - queueCost[type];//记录消耗的时间
        synchronized (this) {
            callbackExist[type] = false;//重置状态
        }
    }

    //endregion

    //region vsync相关的开始doFrameBegin doFrameEnd
    private void doFrameBegin(long token) {
        this.isVsyncFrame = true;
    }

    private void doFrameEnd(long token) {
        doQueueEnd(CALLBACK_TRAVERSAL);

        for (int i : queueStatus) {
            if (i != DO_QUEUE_END) {
                queueCost[i] = DO_QUEUE_END_ERROR;
                if (config.isDevEnv) {
                    throw new RuntimeException(String.format("UIThreadMonitor happens type[%s] != DO_QUEUE_END", i));
                }
            }
        }
        queueStatus = new int[CALLBACK_LAST + 1];

        addFrameCallback(CALLBACK_INPUT, this, true);
    }
    //endregion


    //vsync开始的时间,校对时间
    private long getIntendedFrameTimeNs(long defaultValue) {
        try {
            return ReflectUtils.reflectObject(vsyncReceiver, "mTimestampNanos", defaultValue);
        } catch (Exception e) {
            e.printStackTrace();
            MatrixLog.e(TAG, e.toString());
        }
        return defaultValue;
    }
}

5.LooperObserver  它是 Looper 的观察者,在 Looper 分发消息、刷新 UI、分发结束时回调,这几个回调方法也是 ANR、慢方法等模块的判断依据

//它是 Looper 的观察者,在 Looper 分发消息、刷新 UI 、vsync时回调,这几个回调方法也是 ANR、慢方法等模块的判断依据
public abstract class LooperObserver {

    private boolean isDispatchBegin = false;

    /**
     * 消息分发前回调
     *
     * @param beginNs    开始纳秒单位的时间
     * @param cp
<think>好的,我现在要帮用户介绍一下微信小程序的大文件上传。首先,我需要了解用户的需求,他们可能遇到了上传大文件的问题,或者想知道如何在小程序中实现这个功能。微信小程序的开发有一些限制,比如单个文件上传的大小限制,所以用户可能需要绕过这些限制的方法。 接下来,我要回忆微信小程序的官方文档内容。记得默认情况下,微信小程序的上传文件接口(wx.uploadFile)有大小限制,以前是10MB,但可能现在有变化,需要确认。比如,现在是否还是10MB,或者是否有调整。不过,不管具体数字,大文件上传肯定需要分片处理。 然后,用户可能需要分片上传的具体步骤。分片上传的基本流程包括文件分片、并行上传、服务端合并等。同时,还要考虑如何计算分片,使用File对象或者Blob对象进行切割。另外,每个分片需要有一个唯一的标识,可能用MD5或者hash值来确保文件的唯一性,避免重复上传。 还要考虑网络问题,比如断点续传。如果上传中途失败,如何从断点继续上传,而不是重新开始。这需要客户端记录上传进度,服务端保存已上传的分片信息。 另外,微信小程序中使用的API需要注意兼容性和权限问题。比如,选择文件时使用wx.chooseMessageFile还是其他API,不同API支持的文件类型和大小可能不同。例如,wx.chooseMessageFile可以选聊天文件,可能支持更大的文件,但需要用户授权。 然后,服务端的处理也很重要。服务端需要接收分片,存储,并在所有分片上传完成后合并。合并时要注意顺序,确保文件的正确性。可能还需要处理并发上传的问题,比如多个分片同时上传时的冲突。 另外,前端显示上传进度也是一个关键点。可能需要计算每个分片的上传进度,然后综合整个文件的进度,给用户反馈。可以使用wx.uploadFile的progress回调来实现。 还有,大文件上传可能涉及到性能问题,比如内存占用过高,导致小程序卡顿。需要合理设置分片大小,避免分片过小导致请求过多,或者分片过大导致内存不足。 可能还需要处理一些边界情况,比如文件刚好是分片大小的整数倍,或者最后一个分片较小。同时,文件校验,上传完成后检查文件的完整性,比如比对MD5值。 总结下来,实现步骤大致是:1. 选择文件并获取文件对象;2. 分片处理;3. 上传分片;4. 服务端合并;5. 处理进度和异常。 在具体编码时,需要注意微信小程序的API限制,比如无法直接操作文件二进制数据,可能需要使用ArrayBuffer或者Base64转换。此外,分片上传需要维护每个分片的状态,可能需要使用本地缓存或者变量存储。 还需要考虑用户体验,比如上传过程中允许用户取消,或者后台继续上传。微信小程序的后台运行限制较多,可能需要使用Worker或者其他方式,但可能复杂。 最后,测试不同场景,比如大文件、网络不稳定、服务端重启等,确保上传的可靠性和稳定性。</think>微信小程序大文件上传解决方案(分片上传实现原理) $$time = \frac{文件大小}{网络速度} + 分片数 \times 传输延迟$$ 一、核心限制与解决方案 1. 微信默认限制 - 单文件上传限制:10MB(可通过配置提升至20MB) - 内存限制:iOS 200MB / Android 100MB 2. 突破限制方案 $$分片大小 = \min(文件大小, 内存限制 \times 0.8)$$ 采用分片上传(chunked upload)+ 断点续传机制 二、实现步骤 1. 前端分片处理 ```javascript // 文件选择 wx.chooseMessageFile({ success(res) { const file = res.tempFiles[0] const chunkSize = 5 * 1024 * 1024 // 5MB/片 const chunks = Math.ceil(file.size / chunkSize) } }) ``` 2. 分片上传逻辑 $$上传成功率 = 1 - (1 - p)^n$$ (p为单分片成功率,n为分片数) ```javascript for(let i=0; i<chunks; i++){ const blob = file.slice(i*chunkSize, (i+1)*chunkSize) wx.uploadFile({ url: 'https://your.domain/upload', filePath: blob, formData: { chunkNumber: i, totalChunks: chunks, fileHash: fileHash } }) } ``` 3. 服务端处理要点 - 分片临时存储 - 哈希校验(推荐SHA-256) - 分片合并 $$合并耗时 = \frac{文件大小}{磁盘IO速度}$$ 三、性能优化策略 1. 并发控制 $$最优并发数 = \frac{带宽(Mbps) \times 1024}{分片大小(KB) \times 8}$$ 2. 断点续传实现 - 本地存储记录已上传分片 - 服务端返回缺失分片索引 四、注意事项 1. 安全机制 - 文件类型白名单校验 - 病毒扫描(建议使用云函数) 2. 用户体验优化 - 进度计算:$$总进度 = \frac{\sum 已传分片大小}{文件总大小} \times 100\%$$ - 后台持续上传(需申请相关权限) 五、完整技术架构 ``` [小程序端] ├─ 文件选择 → 分片处理 → 并发上传 └─ 进度监控 ← 失败重试 ← 断点记录 [服务端] ├─ 分片接收 → 临时存储 → 完整性校验 └─ 合并请求 → 持久化存储 → 结果返回 ``` 建议测试场景: 1. 弱网络环境(3G模拟) 2. 上传中断恢复 3. 重复文件秒传 4. 不同文件类型(视频/压缩包/文档) 通过上述方案可实现稳定上传2GB以内文件(实际测试数据),更大文件建议使用微信原生视频接口或云存储方案
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值