主頁 > 移動端開發 > Android Matrix Resource-Canary 原始碼決議

Android Matrix Resource-Canary 原始碼決議

2021-07-25 08:05:21 移動端開發

1.ResourcePlugin init方法里創建執行activity泄漏檢測的類ActivityRefWatcher,start方法里啟動檢測mWatcher.start();

public class ResourcePlugin extends Plugin {
    private static final String TAG = "Matrix.ResourcePlugin";
    //配置類
    private final ResourceConfig mConfig;
    //執行activity泄漏檢測的類
    private ActivityRefWatcher mWatcher = null;

    public ResourcePlugin(ResourceConfig config) {
        mConfig = config;
    }

    //todo 沒有用到
    public static void activityLeakFixer(Application application) {
        // Auto break the path from Views in their holder to gc root when activity is destroyed.
        application.registerActivityLifecycleCallbacks(new ActivityLifeCycleCallbacksAdapter() {
            @Override
            public void onActivityDestroyed(Activity activity) {
                ActivityLeakFixer.fixInputMethodManagerLeak(activity);
                ActivityLeakFixer.unbindDrawables(activity);
                ActivityLeakFixer.fixViewLocationHolderLeakApi28(activity);
            }
        });
    }

    public ActivityRefWatcher getWatcher() {
        return mWatcher;
    }

    @Override
    public void init(Application app, PluginListener listener) {
        super.init(app, listener);
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            MatrixLog.e(TAG, "API is low Build.VERSION_CODES.ICE_CREAM_SANDWICH(14), ResourcePlugin is not supported");
            unSupportPlugin();
            return;
        }
        //執行activity泄漏檢測的類
        mWatcher = new ActivityRefWatcher(app, this);
    }

    @Override
    public void start() {
        super.start();
        if (!isSupported()) {
            MatrixLog.e(TAG, "ResourcePlugin start, ResourcePlugin is not supported, just return");
            return;
        }
        //開啟檢測
        mWatcher.start();
    }

    @Override
    public void stop() {
        super.stop();
        if (!isSupported()) {
            MatrixLog.e(TAG, "ResourcePlugin stop, ResourcePlugin is not supported, just return");
            return;
        }
        //關掉檢測
        mWatcher.stop();
    }

    @Override
    public void destroy() {
        super.destroy();
        if (!isSupported()) {
            MatrixLog.e(TAG, "ResourcePlugin destroy, ResourcePlugin is not supported, just return");
            return;
        }
        //destroy檢測
        mWatcher.destroy();
    }

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

    @Override
    public void onForeground(boolean isForeground) {
        MatrixLog.d(TAG, "onForeground: %s", isForeground);
        if (isPluginStarted() && mWatcher != null) {
            mWatcher.onForeground(isForeground);
        }
    }

    public ResourceConfig getConfig() {
        return mConfig;
    }

}

2.ActivityRefWatcher具體檢測的類

public class DestroyedActivityInfo {
    //為每個已經ondestroy的activity設定一個key
    public final String mKey;
    //activity的名字
    public final String mActivityName;
    //已經呼叫onDestroy方法的activity的弱參考
    public final WeakReference<Activity> mActivityRef;
    //強制gc多次發現這個activity泄漏的次數
    public int mDetectedCount = 0;

    public DestroyedActivityInfo(String key, Activity activity, String activityName) {
        mKey = key;
        mActivityName = activityName;
        mActivityRef = new WeakReference<>(activity);
    }
}
//ok - HeapDump代表
public class HeapDump implements Serializable {
    //Hprof檔案
    private final File mHprofFile;
    //DestroyedActivityInfo類中為每個已經ondestroy的activity設定的key
    private final String mRefKey;
    //activity名字
    private final String mActivityName;


    public HeapDump(File hprofFile, String refKey, String activityName) {
        mHprofFile = Preconditions.checkNotNull(hprofFile, "hprofFile");
        mRefKey = Preconditions.checkNotNull(refKey, "refKey");
        mActivityName = Preconditions.checkNotNull(activityName, "activityName");
    }

    public File getHprofFile() {
        return mHprofFile;
    }

    public String getReferenceKey() {
        return mRefKey;
    }

    public String getActivityName() {
        return mActivityName;
    }

}
public final class ResourceConfig {
    public static final String TAG = "Matrix.ResourceConfig";
    //應用在前臺時,每分鐘去檢測是否有泄漏
    private static final long DEFAULT_DETECT_INTERVAL_MILLIS = TimeUnit.MINUTES.toMillis(1);
    //應用在后臺時,每20分鐘檢測是否有泄漏
    private static final long DEFAULT_DETECT_INTERVAL_MILLIS_BG = TimeUnit.MINUTES.toMillis(20);
    //檢測一個activity是否泄漏,是檢測了10次它還沒回收,就說明它泄漏了
    private static final int DEFAULT_MAX_REDETECT_TIMES = 10;
    //默認的模式是MANUAL_DUMP,手動dump
    private static final DumpMode DEFAULT_DUMP_HPROF_MODE = DumpMode.MANUAL_DUMP;
    private final IDynamicConfig mDynamicConfig;//todo,一個動態配置類
    //dump模式
    private final DumpMode mDumpHprofMode;
    private final boolean mDetectDebugger;//是否在debugger模式支持
    private final String mTargetActivity;//查看泄漏的Activity,在ManualDumpProcessor里會用到

    private ResourceConfig(IDynamicConfig dynamicConfig, DumpMode dumpHprofMode, boolean detectDebuger, String targetActivity) {
        this.mDynamicConfig = dynamicConfig;
        this.mDumpHprofMode = dumpHprofMode;
        this.mDetectDebugger = detectDebuger;
        this.mTargetActivity = targetActivity;
    }

    //應用在前臺時,每分鐘去檢測是否有泄漏
    public long getScanIntervalMillis() {
        return mDynamicConfig.get(IDynamicConfig.ExptEnum.clicfg_matrix_resource_detect_interval_millis.name(), DEFAULT_DETECT_INTERVAL_MILLIS);
    }

    //應用在后臺時,每20分鐘檢測是否有泄漏
    public long getBgScanIntervalMillis() {
        return mDynamicConfig.get(IDynamicConfig.ExptEnum.clicfg_matrix_resource_detect_interval_millis_bg.name(), DEFAULT_DETECT_INTERVAL_MILLIS_BG);
    }

    //檢測10次
    public int getMaxRedetectTimes() {
        return mDynamicConfig.get(IDynamicConfig.ExptEnum.clicfg_matrix_resource_max_detect_times.name(), DEFAULT_MAX_REDETECT_TIMES);
    }

    //檢測模式
    public DumpMode getDumpHprofMode() {
        return mDumpHprofMode;
    }

    //手動dump模式的時候,跳轉activity
    public String getTargetActivity() {
        return mTargetActivity;
    }

    //debug模式是否可以檢測
    public boolean getDetectDebugger() {
        return mDetectDebugger;
    }

    //如果跳過觸發Dump Hprof,甚至可以把監測步驟在現網環境啟用,以發現測驗階段難以觸發的Activity泄漏
    public enum DumpMode {
        NO_DUMP, // report only,只上報名字
        AUTO_DUMP, // auto dump hprof,
        MANUAL_DUMP, // notify only
        SILENCE_ANALYSE, // dump and analyse hprof when screen off
        FORK_DUMP, // fork dump hprof immediately TODO
        FORK_ANALYSE, // fork dump and analyse hprof immediately TODO
    }

    public static final class Builder {

        private DumpMode mDefaultDumpHprofMode = DEFAULT_DUMP_HPROF_MODE;
        private IDynamicConfig dynamicConfig;
        private String mTargetActivity;
        private boolean mDetectDebugger = false;

        public Builder dynamicConfig(IDynamicConfig dynamicConfig) {
            this.dynamicConfig = dynamicConfig;
            return this;
        }

        public Builder setAutoDumpHprofMode(DumpMode mode) {
            mDefaultDumpHprofMode = mode;
            return this;
        }

        public Builder setDetectDebuger(boolean enabled) {
            mDetectDebugger = true;
            return this;
        }

        public Builder setManualDumpTargetActivity(String targetActivity) {
            mTargetActivity = targetActivity;
            return this;
        }

        public ResourceConfig build() {
            return new ResourceConfig(dynamicConfig, mDefaultDumpHprofMode, mDetectDebugger, mTargetActivity);
        }
    }
}
public class SharePluginInfo {

    //plugin名字
    public static final String TAG_PLUGIN = "memory";
    //Hprof壓縮檔案位置?
    public static final String ISSUE_RESULT_PATH = "resultZipPath";
    //dump的模式
    public static final String ISSUE_DUMP_MODE     = "dump_mode";
    //泄漏的activity的名字
    public static final String ISSUE_ACTIVITY_NAME = "activity";
    //泄漏的acitivity的唯一key
    public static final String ISSUE_REF_KEY       = "ref_key";
    //refChain gc鏈
    public static final String ISSUE_LEAK_DETAIL   = "leak_detail";
    //花了多長時間,dump+分析的消耗時間
    public static final String ISSUE_COST_MILLIS   = "cost_millis";
    //行程名字
    public static final String ISSUE_LEAK_PROCESS  = "leak_process";
    //沒用到
    public static final String ISSUE_NOTIFICATION_ID     = "notification_id";

    public static final class IssueType {
        public static final int LEAK_FOUND         = 0;
        public static final int ERR_FILE_NOT_FOUND = 2;
        public static final int ERR_ANALYSE_OOM    = 3;
    }
}

/**
 * ActivityRefWatcher繼承于FilePublisher,可以將檢測出來的泄漏activity保存到SharedPreferences里,設定了一天過期日期
 */
public class ActivityRefWatcher extends FilePublisher implements Watcher {
    private static final String TAG = "Matrix.ActivityRefWatcher";

    private static final int CREATED_ACTIVITY_COUNT_THRESHOLD = 1;
    //檔案過期時間一天,FilePublisher
    private static final long FILE_CONFIG_EXPIRED_TIME_MILLIS = TimeUnit.DAYS.toMillis(1);
    //為每個destory activity設定的唯一key的前綴
    private static final String ACTIVITY_REFKEY_PREFIX = "MATRIX_RESCANARY_REFKEY_";
    //ResourcePlugin
    private final ResourcePlugin mResourcePlugin;

    private final RetryableTaskExecutor mDetectExecutor;//定位泄漏的Executor,重復回圈檢測
    private final int mMaxRedetectTimes;//幾次gc之后才確定是否屬于泄漏了,10次
    private final long mBgScanTimes;//在后臺的時候20分鐘掃描一次
    private final long mFgScanTimes;//在前臺的時候1分鐘掃描一次

    //處理執行緒
    private final HandlerThread mHandlerThread;
    //延時handler
    private final Handler mHandler;
    //存放destory的activity的鏈表
    private final ConcurrentLinkedQueue<DestroyedActivityInfo> mDestroyedActivityInfos;

    private final BaseLeakProcessor mLeakProcessor;//檢測到泄漏了,處理程式
    //dump模式
    private final ResourceConfig.DumpMode mDumpHprofMode;
    public static class ComponentFactory {//工廠

        //創建回圈重復檢測的執行器,傳入回圈時間,后臺執行緒
        protected RetryableTaskExecutor createDetectExecutor(ResourceConfig config, HandlerThread handlerThread) {
            return new RetryableTaskExecutor(config.getScanIntervalMillis(), handlerThread);
        }

        //發生泄漏后,創建處理器
        protected BaseLeakProcessor createCustomLeakProcessor(ResourceConfig.DumpMode dumpMode, ActivityRefWatcher watcher) {
            return null;
            }

        private BaseLeakProcessor createLeakProcess(ResourceConfig.DumpMode dumpMode, ActivityRefWatcher watcher) {
            BaseLeakProcessor leakProcessor = createCustomLeakProcessor(dumpMode, watcher);
            if (leakProcessor != null) {
                return leakProcessor;
            }
            //根據模式,創建處理器
            switch (dumpMode) {
                case AUTO_DUMP:
                    return new AutoDumpProcessor(watcher);
                case MANUAL_DUMP:
                    //manual會讓用戶跳到一個activity里處理
                    return new ManualDumpProcessor(watcher, watcher.getResourcePlugin().getConfig().getTargetActivity());
                case SILENCE_ANALYSE:
                    return new SilenceAnalyseProcessor(watcher);
                case NO_DUMP:
                default:
                    return new NoDumpProcessor(watcher);
                }
                }
            }

    public ActivityRefWatcher(Application app,
                              final ResourcePlugin resourcePlugin) {
        this(app, resourcePlugin, new ComponentFactory());
    }

    private ActivityRefWatcher(Application app,
                               ResourcePlugin resourcePlugin,
                               ComponentFactory componentFactory) {
        super(app, FILE_CONFIG_EXPIRED_TIME_MILLIS, resourcePlugin.getTag(), resourcePlugin);
        this.mResourcePlugin = resourcePlugin;
        final ResourceConfig config = resourcePlugin.getConfig();//動態配置
        //todo MatrixHandlerThread使用
        mHandlerThread = MatrixHandlerThread.getNewHandlerThread("matrix_res", Thread.NORM_PRIORITY); // avoid blocking default matrix thread
        mHandler = new Handler(mHandlerThread.getLooper());//創建一個單獨執行緒,調度檢測任務
        mDumpHprofMode = config.getDumpHprofMode();//dump模式
        mBgScanTimes = config.getBgScanIntervalMillis();//后臺檢測時間20分鐘
        mFgScanTimes = config.getScanIntervalMillis();//前臺檢測時間1分鐘
        mDetectExecutor = componentFactory.createDetectExecutor(config, mHandlerThread);//工廠創建調度任務器
        mMaxRedetectTimes = config.getMaxRedetectTimes();//發現幾次就說明泄漏了
        mLeakProcessor = componentFactory.createLeakProcess(mDumpHprofMode, this);//泄漏之后處理器
        mDestroyedActivityInfos = new ConcurrentLinkedQueue<>();//鏈表保存泄漏acitvity結構體
    }

    public void onForeground(boolean isForeground) {
        if (isForeground) {
            MatrixLog.i(TAG, "we are in foreground, modify scan time[%sms].", mFgScanTimes);
            mDetectExecutor.clearTasks();
            mDetectExecutor.setDelayMillis(mFgScanTimes);
            mDetectExecutor.executeInBackground(mScanDestroyedActivitiesTask);
        } else {
            MatrixLog.i(TAG, "we are in background, modify scan time[%sms].", mBgScanTimes);
            mDetectExecutor.setDelayMillis(mBgScanTimes);
        }
    }

    private final Application.ActivityLifecycleCallbacks mRemovedActivityMonitor = new ActivityLifeCycleCallbacksAdapter() {

        @Override
        public void onActivityDestroyed(Activity activity) {
            //記錄已被destory的Activity
            pushDestroyedActivityInfo(activity);
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    triggerGc();
                }
            }, 2000);
        }
    };

    @Override
    public void start() {
        stopDetect();
        final Application app = mResourcePlugin.getApplication();
        if (app != null) {
            //注冊監聽
            app.registerActivityLifecycleCallbacks(mRemovedActivityMonitor);
            //開始檢測
            scheduleDetectProcedure();
            MatrixLog.i(TAG, "watcher is started.");
        }
    }

    @Override
    public void stop() {
        stopDetect();
        MatrixLog.i(TAG, "watcher is stopped.");
    }

    private void stopDetect() {
        final Application app = mResourcePlugin.getApplication();
        if (app != null) {
            //停止注冊activity生命周期監聽
            app.unregisterActivityLifecycleCallbacks(mRemovedActivityMonitor);
            //關掉檢測
            unscheduleDetectProcedure();
        }
    }

    @Override
    public void destroy() {
        //停止檢測
        mDetectExecutor.quit();
        //釋放執行緒
        mHandlerThread.quitSafely();
        //釋放處理器
        mLeakProcessor.onDestroy();
        MatrixLog.i(TAG, "watcher is destroyed.");
    }

    private void pushDestroyedActivityInfo(Activity activity) {
        final String activityName = activity.getClass().getName();
        //在NO_DUMP,AUTO_DUMP模式下,為什么?這兩個模式下才執行以下邏輯,輕量級模式
        if ((mDumpHprofMode == ResourceConfig.DumpMode.NO_DUMP || mDumpHprofMode == ResourceConfig.DumpMode.AUTO_DUMP)
                && !mResourcePlugin.getConfig().getDetectDebugger()
                && isPublished(activityName)) {//該Activity確認存在泄漏,已經上報了就return
            MatrixLog.i(TAG, "activity leak with name %s had published, just ignore", activityName);
            return;
        }
        final UUID uuid = UUID.randomUUID();
        final StringBuilder keyBuilder = new StringBuilder();
        //生成Activity實體的唯一標識
        keyBuilder.append(ACTIVITY_REFKEY_PREFIX).append(activityName)
                .append('_').append(Long.toHexString(uuid.getMostSignificantBits())).append(Long.toHexString(uuid.getLeastSignificantBits()));
        final String key = keyBuilder.toString();
        //構造一個資料結構,表示一個已被destroy的Activity
        final DestroyedActivityInfo destroyedActivityInfo
                = new DestroyedActivityInfo(key, activity, activityName);
        //放入后續待檢測的Activity list
        mDestroyedActivityInfos.add(destroyedActivityInfo);
        synchronized (mDestroyedActivityInfos) {
            mDestroyedActivityInfos.notifyAll();
        }
        MatrixLog.d(TAG, "mDestroyedActivityInfos add %s", activityName);
    }

    /**
     * 將mScanDestroyedActivitiesTask放到mDetectExecutor執行
     */
    private void scheduleDetectProcedure() {
        mDetectExecutor.executeInBackground(mScanDestroyedActivitiesTask);
    }

    /**
     * mDetectExecutor是一個可以重復執行的
     */
    private void unscheduleDetectProcedure() {
//        mDetectExecutor是一個可以重復執行的,清空它里邊的訊息
        mDetectExecutor.clearTasks();
        //清空mDestroyedActivityInfos鏈表
        mDestroyedActivityInfos.clear();
    }

    /**
     * 可以重復執行的任務,execute回傳RETRY,handler會將其重新發送到訊息佇列里,在一個后臺執行緒里呼叫
     */
    private final RetryableTask mScanDestroyedActivitiesTask = new RetryableTask() {

        @Override
        public Status execute() {
            // If destroyed activity list is empty, just wait to save power.
            //鏈表為空,說明destroy的activity為空,等待
            if (mDestroyedActivityInfos.isEmpty()) {
                MatrixLog.i(TAG, "DestroyedActivityInfo is empty! wait...");
                synchronized (mDestroyedActivityInfos) {
                    try {
                        mDestroyedActivityInfos.wait();
                    } catch (Throwable ignored) {
                        // Ignored.
                    }
                }
                MatrixLog.i(TAG, "DestroyedActivityInfo is NOT empty! resume check");
                return Status.RETRY;
            }

            // Fake leaks will be generated when debugger is attached.
            //Debug除錯模式,檢測可能失效,直接return
            if (Debug.isDebuggerConnected() && !mResourcePlugin.getConfig().getDetectDebugger()) {
                MatrixLog.w(TAG, "debugger is connected, to avoid fake result, detection was delayed.");
                return Status.RETRY;
            }

//            final WeakReference<Object[]> sentinelRef = new WeakReference<>(new Object[1024 * 1024]); // alloc big object
            triggerGc();
            triggerGc();
            triggerGc();
//            if (sentinelRef.get() != null) {
//                // System ignored our gc request, we will retry later.
//                MatrixLog.d(TAG, "system ignore our gc request, wait for next detection.");
//                return Status.RETRY;
//            }

            final Iterator<DestroyedActivityInfo> infoIt = mDestroyedActivityInfos.iterator();

            while (infoIt.hasNext()) {
                //從鏈表里回圈取出已經destroy的DestroyedActivityInfo資訊
                final DestroyedActivityInfo destroyedActivityInfo = infoIt.next();
                //如果NO_DUMP AUTO_DUMP為什么這兩個模式?才isPublished檢測?
                if ((mDumpHprofMode == ResourceConfig.DumpMode.NO_DUMP || mDumpHprofMode == ResourceConfig.DumpMode.AUTO_DUMP)
                        && !mResourcePlugin.getConfig().getDetectDebugger()
                        && isPublished(destroyedActivityInfo.mActivityName)) {//已經檢查過了
                    MatrixLog.v(TAG, "activity with key [%s] was already published.", destroyedActivityInfo.mActivityName);
                    infoIt.remove();
                    continue;
                }
                triggerGc();
                //為null的畫,說明回收了,將其洗掉
                if (destroyedActivityInfo.mActivityRef.get() == null) {
                    // The activity was recycled by a gc triggered outside.
                    MatrixLog.v(TAG, "activity with key [%s] was already recycled.", destroyedActivityInfo.mKey);
                    infoIt.remove();
                    continue;
                }
                //如果沒回收,將其發現次數增加
                ++destroyedActivityInfo.mDetectedCount;
                //如果發現次數小于10次的畫,檢查下一個
                if (destroyedActivityInfo.mDetectedCount < mMaxRedetectTimes
                        && !mResourcePlugin.getConfig().getDetectDebugger()) {
                    // Although the sentinel tell us the activity should have been recycled,
                    // system may still ignore it, so try again until we reach max retry times.
                    MatrixLog.i(TAG, "activity with key [%s] should be recycled but actually still exists in %s times, wait for next detection to confirm.",
                            destroyedActivityInfo.mKey, destroyedActivityInfo.mDetectedCount);

                    triggerGc();
                    continue;
                }

                MatrixLog.i(TAG, "activity with key [%s] was suspected to be a leaked instance. mode[%s]", destroyedActivityInfo.mKey, mDumpHprofMode);
                //發現次數大于10次了,如果處理器為null,報空指標
                if (mLeakProcessor == null) {
                    throw new NullPointerException("LeakProcessor not found!!!");
                }

                triggerGc();
                //處理器處理泄漏的activiy
                if (mLeakProcessor.process(destroyedActivityInfo)) {
                    MatrixLog.i(TAG, "the leaked activity [%s] with key [%s] has been processed. stop polling", destroyedActivityInfo.mActivityName, destroyedActivityInfo.mKey);
                    infoIt.remove();
                }
            }

            triggerGc();
            return Status.RETRY;
        }
    };

    /**
     * 回傳根據dump模式,回傳不同處理器
     * @return
     */
    public BaseLeakProcessor getLeakProcessor() {
        return mLeakProcessor;
    }

    /**
     * 回傳ResourcePlugin
     * @return
     */
    public ResourcePlugin getResourcePlugin() {
        return mResourcePlugin;
    }

    /**
     * 回傳DestroyedActivityInfo鏈表
     *
     * @return
     */
    public Collection<DestroyedActivityInfo> getDestroyedActivityInfos() {
        return mDestroyedActivityInfos;
    }

    /**
     * triggerGc的時候
     */
    public void triggerGc() {
        MatrixLog.v(TAG, "triggering gc...");
        //1.呼叫gc
        Runtime.getRuntime().gc();
        //2.睡眠100毫秒
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            MatrixLog.printErrStackTrace(TAG, e, "");
        }
        //3.運行Finalization方法
        Runtime.getRuntime().runFinalization();
        MatrixLog.v(TAG, "gc was triggered.");
    }
}

3.FilePublisher可以將activity名字和發生泄漏的時間保存到SharedPreferences,設定了一天的過期日期

public class FilePublisher extends IssuePublisher {
    private static final String TAG = "Matrix.FilePublisher";

    private final long                     mExpiredTime;//一天過期日期
    private final SharedPreferences.Editor mEditor;
    private final HashMap<String, Long>    mPublishedMap;//activity名字和發生泄漏的時間

    private final Context mContext;


    public FilePublisher(Context context, long expire, String tag, OnIssueDetectListener issueDetectListener) {
        super(issueDetectListener);
        this.mContext = context;
        mExpiredTime = expire;//1天過期日期
        SharedPreferences sharedPreferences = context.getSharedPreferences(tag + MatrixUtil.getProcessName(context), Context.MODE_PRIVATE);
        mPublishedMap = new HashMap<>();//activity名字和發生泄漏的時間
        long current = System.currentTimeMillis();
        mEditor = sharedPreferences.edit();
        HashSet<String> spKeys = null;
        if (null != sharedPreferences.getAll()) {
            spKeys = new HashSet<>(sharedPreferences.getAll().keySet());
        }
        if (null != spKeys) {
            for (String key : spKeys) {
                long start = sharedPreferences.getLong(key, 0);
                long costTime = current - start;
                if (start <= 0 || costTime > mExpiredTime) {
                    mEditor.remove(key);//超時則洗掉
                } else {
                    mPublishedMap.put(key, start);//沒有的話,添加到map里
                }
            }
        }
        if (null != mEditor) {
            mEditor.apply();
        }
    }

    public void markPublished(String key, boolean persist) {
        if (key == null) {
            return;
        }
        if (mPublishedMap.containsKey(key)) {//如果含有則回傳
            return;
        }
        final long now = System.currentTimeMillis();
        mPublishedMap.put(key, now);
        //如果保存檔案里則保存到SharedPreferences里
        if (persist) {
            SharedPreferences.Editor e = mEditor.putLong(key, now);
            if (null != e) {
                e.apply();
            }
        }
    }

    @Override
    public void markPublished(String key) {
        markPublished(key, true);
    }

    @Override
    public void unMarkPublished(String key) {
        if (key == null) {
            return;
        }
        if (!mPublishedMap.containsKey(key)) {
            return;
        }
        mPublishedMap.remove(key);
        SharedPreferences.Editor e = mEditor.remove(key);
        if (null != e) {
            e.apply();
        }
    }

    @Override
    public boolean isPublished(String key) {
        if (!mPublishedMap.containsKey(key)) {
            return false;
        }
        long start = mPublishedMap.get(key);
        //時間判斷,超過1天洗掉,并回傳false
        if (start <= 0 || (System.currentTimeMillis() - start) > mExpiredTime) {
            SharedPreferences.Editor e = mEditor.remove(key);
            if (null != e) {
                e.apply();
            }
            mPublishedMap.remove(key);
            return false;
        }
        return true;
    }

    public Context getContext() {
        return mContext;
    }
}

4.RetryableTaskExecutor再次執行某個任務的執行器,如果RetryableTask execute回傳的是RETRY則重新執行,用在回圈定時檢測有沒有記憶體泄漏

//再次執行某個任務的執行器
public class RetryableTaskExecutor {
    //后臺handler,將task發送到后臺執行緒執行
    private final Handler mBackgroundHandler;
    //主執行緒handler,將task發送到主執行緒執行
    private final Handler mMainHandler;
    //執行時間delay時間
    private long mDelayMillis;

    public RetryableTaskExecutor(long delayMillis, HandlerThread handleThread) {
        mBackgroundHandler = new Handler(handleThread.getLooper());
        mMainHandler = new Handler(Looper.getMainLooper());
        mDelayMillis = delayMillis;
    }

    //設定delay時間
    public void setDelayMillis(long delayed) {
        this.mDelayMillis = delayed;
    }

    //沒有用
    public void executeInMainThread(final RetryableTask task) {
        postToMainThreadWithDelay(task, 0);
    }

    //發送到后臺執行緒執行任務
    public void executeInBackground(final RetryableTask task) {
        postToBackgroundWithDelay(task, 0);
    }

    //清空任務
    public void clearTasks() {
        mBackgroundHandler.removeCallbacksAndMessages(null);
        mMainHandler.removeCallbacksAndMessages(null);
    }

    //丟棄
    public void quit() {
        clearTasks();
    }

    //沒有用
    private void postToMainThreadWithDelay(final RetryableTask task, final int failedAttempts) {
        mMainHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                RetryableTask.Status status = task.execute();
                if (status == RetryableTask.Status.RETRY) {
                    postToMainThreadWithDelay(task, failedAttempts + 1);
                }
            }
        }, mDelayMillis);
    }

    /**
     * 發送任務到后臺執行緒
     * @param task 代表任務,它的execute回傳RETRY的時候,還需將其再次發送任務到后臺執行緒,再次執行
     * @param failedAttempts 傳入失敗次數
     */
    private void postToBackgroundWithDelay(final RetryableTask task, final int failedAttempts) {
        mBackgroundHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                RetryableTask.Status status = task.execute();
                if (status == RetryableTask.Status.RETRY) {
                    postToBackgroundWithDelay(task, failedAttempts + 1);
                }
            }
        }, mDelayMillis);
    }

    /**
     * 代表可以執行再次執行的任務
     */
    public interface RetryableTask {
//        如果回傳了RETRY,將其再次發送到執行緒執行
        Status execute();

        enum Status {
            DONE, RETRY
        }
    }
}

5.BaseLeakProcessor 基礎處理類

public class AndroidHeapDumper {
    private static final String TAG = "Matrix.AndroidHeapDumper";

    private final Context mContext;
    //檔案相關
    private final DumpStorageManager mDumpStorageManager;
    //主執行緒handler
    private final Handler mMainHandler;

    public AndroidHeapDumper(Context context, DumpStorageManager dumpStorageManager) {
        this(context, dumpStorageManager, new Handler(Looper.getMainLooper()));
    }

    public AndroidHeapDumper(Context context, DumpStorageManager dumpStorageManager, Handler mainHandler) {
        mContext = context;
        mDumpStorageManager = dumpStorageManager;
        mMainHandler = mainHandler;
    }

    //dump方法
    public File dumpHeap(boolean isShowToast) {
        //得到hprof檔案
        final File hprofFile = mDumpStorageManager.newHprofFile();

        if (null == hprofFile) {
            MatrixLog.w(TAG, "hprof file is null.");
            return null;
        }

        final File hprofDir = hprofFile.getParentFile();
        if (hprofDir == null) {
            MatrixLog.w(TAG, "hprof file path: %s does not indicate a full path.", hprofFile.getAbsolutePath());
            return null;
        }

        if (!hprofDir.canWrite()) {
            MatrixLog.w(TAG, "hprof file path: %s cannot be written.", hprofFile.getAbsolutePath());
            return null;
        }

        //1.5G 小于1.5G 空間回傳
        if (hprofDir.getFreeSpace() < 1.5 * 1024 * 1024 * 1024) {
            MatrixLog.w(TAG, "hprof file path: %s free space not enough", hprofDir.getAbsolutePath());
            return null;
        }

        //isShowToast 在AutoDumpProcessor模式會彈窗
        if (isShowToast) {
            final FutureResult<Toast> waitingForToast = new FutureResult<>();
            showToast(waitingForToast);

            if (!waitingForToast.wait(5, TimeUnit.SECONDS)) {//這里是等toast
                MatrixLog.w(TAG, "give up dumping heap, waiting for toast too long.");
                return null;
            }
            try {
                //這里是真正dump的地方
                Debug.dumpHprofData(hprofFile.getAbsolutePath());
                cancelToast(waitingForToast.get());
                return hprofFile;
            } catch (Exception e) {
                MatrixLog.printErrStackTrace(TAG, e, "failed to dump heap into file: %s.", hprofFile.getAbsolutePath());
                return null;
            }
        } else {
            try {
                //這里是真正dump的地方
                Debug.dumpHprofData(hprofFile.getAbsolutePath());
                return hprofFile;
            } catch (Exception e) {
                MatrixLog.printErrStackTrace(TAG, e, "failed to dump heap into file: %s.", hprofFile.getAbsolutePath());
                return null;
            }
        }
    }

    private void showToast(final FutureResult<Toast> waitingForToast) {
        mMainHandler.post(new Runnable() {
            @Override
            public void run() {
                final Toast toast = new Toast(mContext);
                toast.setDuration(Toast.LENGTH_LONG);
                toast.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
                LayoutInflater inflater = LayoutInflater.from(mContext);
                toast.setView(inflater.inflate(R.layout.resource_canary_toast_wait_for_heapdump, null));
                toast.show();
                // Waiting for Idle to make sure Toast gets rendered.
                Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
                    @Override
                    public boolean queueIdle() {//等toast花了點時間
                        waitingForToast.set(toast);
                        return false;
                    }
                });
            }
        });
    }

    private void cancelToast(final Toast toast) {
        mMainHandler.post(new Runnable() {
            @Override
            public void run() {
                toast.cancel();
            }
        });
    }

    public interface HeapDumpHandler {
        void process(HeapDump result);
    }
}
public class DumpStorageManager {
    private static final String TAG = "Matrix.DumpStorageManager";
    //后綴
    public static final String HPROF_EXT = ".hprof";
    //最多五個快取的hprof
    public static final int DEFAULT_MAX_STORED_HPROF_FILECOUNT = 5;

    protected final Context mContext;
    //最多快取的hprof數量
    protected final int mMaxStoredHprofFileCount;

    public DumpStorageManager(Context context) {
        this(context, DEFAULT_MAX_STORED_HPROF_FILECOUNT);
    }

    public DumpStorageManager(Context context, int maxStoredHprofFileCount) {
        if (maxStoredHprofFileCount <= 0) {
            throw new IllegalArgumentException("illegal max stored hprof file count: "
                    + maxStoredHprofFileCount);
        }
        mContext = context;
        mMaxStoredHprofFileCount = maxStoredHprofFileCount;
    }

    //新建一個hprof
    public File newHprofFile() {
        final File storageDir = prepareStorageDirectory();
        if (storageDir == null) {
            return null;
        }
//        行程名+行程id+時間+后綴
        final String hprofFileName = "dump"
                + "_" + MatrixUtil.getProcessName(mContext).replace(".", "_").replace(":", "_")
                + "_" + Process.myPid()
                + "_"
                + new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).format(new Date())
                + HPROF_EXT;
        return new File(storageDir, hprofFileName);
    }

    //創建檔案夾
    private File prepareStorageDirectory() {
        final File storageDir = getStorageDirectory();
        if (!storageDir.exists() && (!storageDir.mkdirs() || !storageDir.canWrite())) {
            MatrixLog.w(TAG, "failed to allocate new hprof file since path: %s is not writable.",
                    storageDir.getAbsolutePath());
            return null;
        }
        final File[] hprofFiles = storageDir.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return name.endsWith(HPROF_EXT);
            }
        });
        if (hprofFiles != null && hprofFiles.length > mMaxStoredHprofFileCount) {
            for (File file : hprofFiles) {
                if (file.exists() && !file.delete()) {
                    MatrixLog.w(TAG, "faile to delete hprof file: " + file.getAbsolutePath());
                }
            }
        }
        return storageDir;
    }

    //獲取存盤位置
    private File getStorageDirectory() {
        final String sdcardState = Environment.getExternalStorageState();
        File root = null;
        if (Environment.MEDIA_MOUNTED.equals(sdcardState)) {
            root = mContext.getExternalCacheDir();
        } else {
            root = mContext.getCacheDir();
        }
        final File result = new File(root, "matrix_resource");

        MatrixLog.i(TAG, "path to store hprof and result: %s", result.getAbsolutePath());

        return result;
    }
}

public abstract class BaseLeakProcessor {
    private static final String TAG = "Matrix.LeakProcessor.Base";
    //檢測泄漏
    private final ActivityRefWatcher mWatcher;
    //dump檔案相關
    private DumpStorageManager                mDumpStorageManager;
    //dump的類
    private AndroidHeapDumper                 mHeapDumper;
    //dump完了回呼
    private AndroidHeapDumper.HeapDumpHandler mHeapDumpHandler;

    public BaseLeakProcessor(ActivityRefWatcher watcher) {
        mWatcher = watcher;
    }

    public abstract boolean process(DestroyedActivityInfo destroyedActivityInfo);

    public DumpStorageManager getDumpStorageManager() {
        if (mDumpStorageManager == null) {
            mDumpStorageManager = new DumpStorageManager(mWatcher.getContext());
        }
        return mDumpStorageManager;
    }

    public AndroidHeapDumper getHeapDumper() {
        if (mHeapDumper == null) {
            mHeapDumper = new AndroidHeapDumper(mWatcher.getContext(), getDumpStorageManager());
        }
        return mHeapDumper;
    }

    protected AndroidHeapDumper.HeapDumpHandler getHeapDumpHandler() {
        if (mHeapDumpHandler == null) {
            mHeapDumpHandler = new AndroidHeapDumper.HeapDumpHandler() {
                @Override
                public void process(HeapDump result) {
                    //裁剪Hprof并上傳,process流程最終呼叫CanaryWorkerService進行裁剪和上報
                    CanaryWorkerService.shrinkHprofAndReport(mWatcher.getContext(), result);
                }
            };
        }

        return mHeapDumpHandler;
    }

    public ActivityRefWatcher getWatcher() {
        return mWatcher;
    }

    public void onDestroy() {
    }

    /**
     * todo
     * SilenceAnalyseProcessor 呼叫了
     * ManualDumpProcessor呼叫了
     * @param hprofFile
     * @param referenceKey
     * @return
     */
    protected ActivityLeakResult analyze(File hprofFile, String referenceKey) {
        final HeapSnapshot heapSnapshot;
        ActivityLeakResult result;
        final ExcludedRefs excludedRefs = AndroidExcludedRefs.createAppDefaults(Build.VERSION.SDK_INT, Build.MANUFACTURER).build();
        try {
            heapSnapshot = new HeapSnapshot(hprofFile);
            //todo 這里是這個函式的主要邏輯,使用ActivityLeakAnalyzer分析heapSnapshot,回傳ActivityLeakResult
            result = new ActivityLeakAnalyzer(referenceKey, excludedRefs).analyze(heapSnapshot);
        } catch (IOException e) {
            result = ActivityLeakResult.failure(e, 0);
        }
        return result;
    }

    final protected void publishIssue(int issueType, ResourceConfig.DumpMode dumpMode, String activity, String refKey, String detail, String cost) {
        Issue issue = new Issue(issueType);
        JSONObject content = new JSONObject();
        try {
            content.put(SharePluginInfo.ISSUE_DUMP_MODE, dumpMode.name());
            content.put(SharePluginInfo.ISSUE_ACTIVITY_NAME, activity);
            content.put(SharePluginInfo.ISSUE_REF_KEY, refKey);
            content.put(SharePluginInfo.ISSUE_LEAK_DETAIL, detail);
            content.put(SharePluginInfo.ISSUE_COST_MILLIS, cost);
        } catch (JSONException jsonException) {
            MatrixLog.printErrStackTrace(TAG, jsonException, "");
        }
        issue.setContent(content);
        //todo matrix lib框架的功能
        getWatcher().getResourcePlugin().onDetectIssue(issue);
    }
}

6.NoDumpProcessor 可以用于線上的只上報名字

public class NoDumpProcessor extends BaseLeakProcessor {

    private static final String TAG = "Matrix.LeakProcessor.NoDump";

    public NoDumpProcessor(ActivityRefWatcher watcher) {
        super(watcher);
    }

    @Override
    public boolean process(DestroyedActivityInfo destroyedActivityInfo) {
        // Lightweight mode, just report leaked activity name.
        MatrixLog.i(TAG, "lightweight mode, just report leaked activity name.");
        //標記泄漏了
        getWatcher().markPublished(destroyedActivityInfo.mActivityName);
        getWatcher().triggerGc();
        //上報問題
        publishIssue(SharePluginInfo.IssueType.LEAK_FOUND, ResourceConfig.DumpMode.NO_DUMP, destroyedActivityInfo.mActivityName, destroyedActivityInfo.mKey, "no dump", "0");

        return true;
    }
}

7.SilenceAnalyseProcessor在熄屏的時候處理

public class SilenceAnalyseProcessor extends BaseLeakProcessor {

    private static final String TAG = "Matrix.LeakProcessor.SilenceAnalyse";

    private final BroadcastReceiver mReceiver;

    private boolean isScreenOff;
    private boolean isProcessing;

    public SilenceAnalyseProcessor(ActivityRefWatcher watcher) {
        super(watcher);
        //注冊監聽
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        filter.addAction(Intent.ACTION_SCREEN_ON);

        mReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                //設定標記位
                if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
                    isScreenOff = true;
                    MatrixLog.i(TAG, "[ACTION_SCREEN_OFF]");
                } else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) {
                    isScreenOff = false;
                    MatrixLog.i(TAG, "[ACTION_SCREEN_ON]");
                }
            }
        };

        try {
            watcher.getResourcePlugin().getApplication().registerReceiver(mReceiver, filter);
        } catch (Throwable e) {
            MatrixLog.printErrStackTrace(TAG, e, "");
        }
    }

    @Override
    public boolean process(DestroyedActivityInfo destroyedActivityInfo) {
        //處理方法
        return onLeak(destroyedActivityInfo.mActivityName, destroyedActivityInfo.mKey);
    }

    @Override
    public void onDestroy() {
        MatrixLog.i(TAG, "onDestroy: unregister receiver");
        //解除注冊
        getWatcher().getResourcePlugin().getApplication().unregisterReceiver(mReceiver);
    }

    private boolean onLeak(final String activity, final String refString) {
        MatrixLog.i(TAG, "[onLeak] activity=%s isScreenOff=%s isProcessing=%s", activity, isScreenOff, isProcessing);
        //leak監聽過了
        if (getWatcher().isPublished(activity)) {
            MatrixLog.i(TAG, "this activity has been dumped! %s", activity);
            return true;
        }

        if (!isProcessing && isScreenOff) {
            isProcessing = true;

            getWatcher().triggerGc();

            boolean res = dumpAndAnalyse(activity, refString);

            if (res) {
                //又將leak洗掉了,因為在silent模式下不支持isPublished,在ActivityRefWatcher里只有NO_DUMP,AUTO_DUMP才支持
                getWatcher().markPublished(activity, false);
            }

            isProcessing = false;

            return res;
        }
        return false;
    }

    // todo analyse
    private boolean dumpAndAnalyse(String activity, String refString) {

        long dumpBegin = System.currentTimeMillis();

        File file = getHeapDumper().dumpHeap(false);

        if (file == null || file.length() <= 0) {
            publishIssue(SharePluginInfo.IssueType.ERR_FILE_NOT_FOUND, activity, refString, "file is null", "0");
            MatrixLog.e(TAG, "file is null!");
            return true;
        }

        MatrixLog.i(TAG, String.format("dump cost=%sms refString=%s path=%s",
                System.currentTimeMillis() - dumpBegin, refString, file.getAbsolutePath()));

        long analyseBegin = System.currentTimeMillis();
        try {
            // todo analyse 這里分析了
            final ActivityLeakResult result = analyze(file, refString);
            MatrixLog.i(TAG, String.format("analyze cost=%sms refString=%s",
                    System.currentTimeMillis() - analyseBegin, refString));

            String refChain = result.toString();
            if (result.mLeakFound) {
                //這里分析了
                publishIssue(SharePluginInfo.IssueType.LEAK_FOUND, activity, refString, refChain, String.valueOf(
                        System.currentTimeMillis() - dumpBegin));
                MatrixLog.i(TAG, refChain);
            } else {
                MatrixLog.i(TAG, "leak not found");
            }
        } catch (OutOfMemoryError error) {
            publishIssue(SharePluginInfo.IssueType.ERR_ANALYSE_OOM, activity, refString, "OutOfMemoryError", "0");
            MatrixLog.printErrStackTrace(TAG, error.getCause(), "");
        } finally {
            file.delete();
        }
        return true;
    }

    private void publishIssue(int issueType, String activity, String refKey, String detail, String cost) {
        publishIssue(issueType, ResourceConfig.DumpMode.SILENCE_ANALYSE, activity, refKey, detail, cost);
    }
}

8.ManualDumpProcessor跳轉到一個activity處理

/**
 * X process leaked -> send notification -> main process activity
 * -> dump and analyse in X process -> show result in main process activity
 * Created by Yves on 2021/3/4
 */
public class ManualDumpProcessor extends BaseLeakProcessor {

    private static final String TAG = "Matrix.LeakProcessor.ManualDump";

    private static final int NOTIFICATION_ID = 0x110;

    private final String mTargetActivity;

    private final NotificationManager mNotificationManager;

    private final List<DestroyedActivityInfo> mLeakedActivities = new ArrayList<>();

    private boolean isMuted;

    public ManualDumpProcessor(ActivityRefWatcher watcher, String targetActivity) {
        super(watcher);
        //處理activity
        mTargetActivity = targetActivity;
        //notification
        mNotificationManager = (NotificationManager) watcher.getContext().getSystemService(Context.NOTIFICATION_SERVICE);
        //注冊BroadcastReceiver
        ManualDumpProcessorHelper.install(watcher.getContext(), this);
    }

    @Override
    public boolean process(DestroyedActivityInfo destroyedActivityInfo) {
        final Context context = getWatcher().getContext();
        //gc了以下
        getWatcher().triggerGc();
        //如果回收了回傳true
        if (destroyedActivityInfo.mActivityRef.get() == null) {
            MatrixLog.v(TAG, "activity with key [%s] was already recycled.", destroyedActivityInfo.mKey);
            return true;
        }
        //保存了下泄漏的資訊
        mLeakedActivities.add(destroyedActivityInfo);

        MatrixLog.i(TAG, "show notification for activity leak. %s", destroyedActivityInfo.mActivityName);
        //?todo 不知道干哈的
        if (isMuted) {
            MatrixLog.i(TAG, "is muted, won't show notification util process reboot");
            return true;
        }
        //發送通知
        //通知點擊之后跳轉的activity
        Intent targetIntent = new Intent();
        targetIntent.setClassName(getWatcher().getContext(), mTargetActivity);
        targetIntent.putExtra(SharePluginInfo.ISSUE_ACTIVITY_NAME, destroyedActivityInfo.mActivityName);
        targetIntent.putExtra(SharePluginInfo.ISSUE_REF_KEY, destroyedActivityInfo.mKey);
        targetIntent.putExtra(SharePluginInfo.ISSUE_LEAK_PROCESS, MatrixUtil.getProcessName(context));

        //點擊之后的回應pendingintent
        PendingIntent pIntent = PendingIntent.getActivity(context, 0, targetIntent, PendingIntent.FLAG_UPDATE_CURRENT);
        //title
        String dumpingHeapTitle = context.getString(R.string.resource_canary_leak_tip);
        ResourceConfig config = getWatcher().getResourcePlugin().getConfig();
        //content
        String dumpingHeapContent =
                String.format(Locale.getDefault(), "[%s] has leaked for [%s]min!!!",
                        destroyedActivityInfo.mActivityName,
                        TimeUnit.MILLISECONDS.toMinutes(
                                config.getScanIntervalMillis() * config.getMaxRedetectTimes()));

        Notification.Builder builder = null;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            builder = new Notification.Builder(context, getNotificationChannelIdCompat(context));
        } else {
            builder = new Notification.Builder(context);
        }
        builder.setContentTitle(dumpingHeapTitle)
                .setPriority(Notification.PRIORITY_DEFAULT)
                .setStyle(new Notification.BigTextStyle().bigText(dumpingHeapContent))
                .setContentIntent(pIntent)
                .setAutoCancel(true)
                .setSmallIcon(R.drawable.ic_launcher)
                .setWhen(System.currentTimeMillis());

        Notification notification = builder.build();
        //發送通知
        mNotificationManager.notify(
                NOTIFICATION_ID + destroyedActivityInfo.mKey.hashCode(), notification);
        //上報了問題
        publishIssue(destroyedActivityInfo.mActivityName, destroyedActivityInfo.mKey);
        MatrixLog.i(TAG, "shown notification!!!3");
        return true;
    }

    //創建通知channel
    private String getNotificationChannelIdCompat(Context context) {
        if (SDK_INT >= Build.VERSION_CODES.O) {
            String channelName = "com.tencent.matrix.resource.processor.ManualDumpProcessor";
            NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
            NotificationChannel notificationChannel = notificationManager.getNotificationChannel(channelName);
            if (notificationChannel == null) {
                MatrixLog.v(TAG, "create channel");
                notificationChannel = new NotificationChannel(channelName, channelName, NotificationManager.IMPORTANCE_HIGH);
                notificationManager.createNotificationChannel(notificationChannel);
            }
            return channelName;
        }
        return null;
    }

    public void setMuted(boolean mute) {
        isMuted = mute;
    }

    /**
     * run in leaked process
     *
     * @param activity
     * @param refString
     * @return ManualDumpData 包含dump檔案+參考鏈
     */
    private ManualDumpData dumpAndAnalyse(String activity, String refString) {
        long dumpBegin = System.currentTimeMillis();

        getWatcher().triggerGc();

        File file = getHeapDumper().dumpHeap(false);
        if (file == null || file.length() <= 0) {
//            publishIssue(SharePluginInfo.IssueType.ERR_FILE_NOT_FOUND, activity, refString, "file is null", "0");
            MatrixLog.e(TAG, "file is null!");
            return null;
        }

        MatrixLog.i(TAG, String.format("dump cost=%sms refString=%s path=%s",
                System.currentTimeMillis() - dumpBegin, refString, file.getAbsolutePath()));

        long analyseBegin = System.currentTimeMillis();
        try {
            final ActivityLeakResult result = analyze(file, refString);
            MatrixLog.i(TAG, String.format("analyze cost=%sms refString=%s",
                    System.currentTimeMillis() - analyseBegin, refString));

            String refChain = result.toString();
            if (result.mLeakFound) {
//                publishIssue(SharePluginInfo.IssueType.LEAK_FOUND, activity, refString, refChain, String.valueOf(System.currentTimeMillis() - dumpBegin));
                MatrixLog.i(TAG, "leakFound,refcChain = %s", refChain);
                return new ManualDumpData(file.getAbsolutePath(), refChain);
            } else {
                MatrixLog.i(TAG, "leak not found");
                return new ManualDumpData(file.getAbsolutePath(), null);
            }
        } catch (OutOfMemoryError error) {
//            publishIssue(SharePluginInfo.IssueType.ERR_ANALYSE_OOM, activity, refString, "OutOfMemoryError", "0");
            MatrixLog.printErrStackTrace(TAG, error.getCause(), "");
        }
        return null;
    }

    //單純上報問題
    private void publishIssue(String activity, String refKey) {
        publishIssue(SharePluginInfo.IssueType.LEAK_FOUND, ResourceConfig.DumpMode.MANUAL_DUMP, activity, refKey, "manual_dump", "0");
    }

    /**
     * multi process dump helper.
     * 多行程dump幫組類
     */
    public static class ManualDumpProcessorHelper extends BroadcastReceiver {
        //permission
        private static final String DUMP_PERMISSION_SUFFIX = ".manual.dump";

        private static final String ACTION_DUMP = "com.tencent.matrix.manual.dump";
        private static final String ACTION_RESULT = "com.tencent.matrix.manual.result";

        private static final String KEY_RESULT_PROCESS = "result_process";
        private static final String KEY_LEAK_ACTIVITY = "leak_activity";
        private static final String KEY_LEAK_PROCESS = "leak_process";
        private static final String KEY_LEAK_REFKEY = "leak_refkey";
        private static final String KEY_HPROF_PATH = "hprof_path";
        private static final String KEY_REF_CHAIN = "ref_chain";
        //是否注冊了BroadcastReceiver
        private static boolean hasInstalled;

        private static ManualDumpProcessor sProcessor;
        private static IResultListener sListener; // only not null in process who called dumpAndAnalyse

        @Override
        public void onReceive(final Context context, final Intent intent) {
            if (intent == null) {
                MatrixLog.e(TAG, "intent is null");
                return;
            }

            MatrixHandlerThread.getDefaultHandler().postAtFrontOfQueue(new Runnable() {
                @Override
                public void run() {
                    if (ACTION_DUMP.equals(intent.getAction())) {
                        //行程
                        String leakProcess = intent.getStringExtra(KEY_LEAK_PROCESS);
                        String currentProcess = MatrixUtil.getProcessName(context);
                        //如果是這個行程泄漏了
                        if (!currentProcess.equals(leakProcess)) {
                            MatrixLog.v(TAG, "ACTION_DUMP: current process [%s] is NOT leaked process [%s]", currentProcess, leakProcess);
                            return;
                        }

                        // leaked process
                        MatrixLog.v(TAG, "ACTION_DUMP: current process [%s] is leaked process [%s]", currentProcess, leakProcess);

                        String leakActivity = intent.getStringExtra(KEY_LEAK_ACTIVITY);
                        String refKey = intent.getStringExtra(KEY_LEAK_REFKEY);
                        //dump資訊并分析
                        ManualDumpData data = sProcessor.dumpAndAnalyse(leakActivity, refKey);
                        Intent resultIntent = new Intent(ACTION_RESULT);
                        if (data != null) {
                            resultIntent.putExtra(KEY_HPROF_PATH, data.hprofPath);
                            resultIntent.putExtra(KEY_REF_CHAIN, data.refChain);
                        }
                        String resultProcess = intent.getStringExtra(KEY_RESULT_PROCESS);
                        resultIntent.putExtra(KEY_RESULT_PROCESS, resultProcess);
                        context.sendBroadcast(resultIntent,
                                context.getPackageName() + DUMP_PERMISSION_SUFFIX);
                    } else if (ACTION_RESULT.equals(intent.getAction())) {
                        // result process
                        final String resultProcess = intent.getStringExtra(KEY_RESULT_PROCESS);
                        final String currentProcess = MatrixUtil.getProcessName(context);
                        if (!currentProcess.equals(resultProcess)) {
                            MatrixLog.v(TAG, "ACTION_RESULT: current process [%s] is NOT result process [%s]", currentProcess, resultProcess);
                            return;
                        }

                        MatrixLog.v(TAG, "ACTION_RESULT: current process [%s] is result process [%s]", currentProcess, resultProcess);

                        // generally, sListener must be NOT null
                        if (sListener == null) {
                            throw new NullPointerException("result listener is null!!!");
                        }

                        final String hprofPath = intent.getStringExtra(KEY_HPROF_PATH);
                        if (hprofPath == null) {
                            sListener.onFailed();
                            return;
                        }
                        final String refChain = intent.getStringExtra(KEY_REF_CHAIN);
                        sListener.onSuccess(hprofPath, refChain);
                        sListener = null;
                    }
                }
            });
        }

        //注冊BroadcastReceiver
        private static void install(Context context, ManualDumpProcessor processor) {
            IntentFilter filter = new IntentFilter();
            filter.addAction(ACTION_DUMP);
            filter.addAction(ACTION_RESULT);
            final String dumpPermission = context.getPackageName() + DUMP_PERMISSION_SUFFIX;
            //注冊BroadcastReceiver
            context.registerReceiver(new ManualDumpProcessorHelper(), filter, dumpPermission, null);
            MatrixLog.d(TAG, "[%s] DUMP_PERMISSION is %s", MatrixUtil.getProcessName(context), dumpPermission);
            hasInstalled = true;
            sProcessor = processor;
        }

        //nouse
        public static void dumpAndAnalyse(Context context, String leakProcess, String activity, String refKey, IResultListener resultListener) {
            if (!hasInstalled) {
                throw new IllegalStateException("ManualDumpProcessorHelper was not installed yet!!! maybe your target activity is not running in right process.");
            }
            final String currentProcess = MatrixUtil.getProcessName(context);
            if (currentProcess.equalsIgnoreCase(leakProcess)) {
                // dump and analyze for current process
                ManualDumpData data = sProcessor.dumpAndAnalyse(activity, refKey);
                if (data == null) {
                    resultListener.onFailed();
                } else {
                    resultListener.onSuccess(data.hprofPath, data.refChain);
                }
            } else {
                sListener = resultListener;
                MatrixLog.v(TAG, "[%s] send broadcast with permission: %s", currentProcess,
                        context.getPackageName() + DUMP_PERMISSION_SUFFIX);
                Intent intent = new Intent(ACTION_DUMP);
                intent.putExtra(KEY_LEAK_PROCESS, leakProcess);
                intent.putExtra(KEY_LEAK_ACTIVITY, activity);
                intent.putExtra(KEY_LEAK_REFKEY, refKey);
                intent.putExtra(KEY_RESULT_PROCESS, currentProcess);
                context.sendBroadcast(intent, context.getPackageName() + DUMP_PERMISSION_SUFFIX);
            }
        }
    }

    public interface IResultListener {
        void onSuccess(String hprof, String leakReference);

        void onFailed();
    }

    /**
     * hprof檔案路徑
     * 鏈
     */
    public static class ManualDumpData {
        public final String hprofPath;
        public final String refChain;

        public ManualDumpData(String hprofPath, String refChain) {
            this.hprofPath = hprofPath;
            this.refChain = refChain;
        }
    }
}

9.AutoDumpProcessor 會在本地裁剪,然后上傳裁剪后的hprof壓縮,上報

public class AutoDumpProcessor extends BaseLeakProcessor {

    private static final String TAG = "Matrix.LeakProcessor.AutoDump";

    public AutoDumpProcessor(ActivityRefWatcher watcher) {
        super(watcher);
    }

    @Override
    public boolean process(DestroyedActivityInfo destroyedActivityInfo) {
//        dump
        final File hprofFile = getHeapDumper().dumpHeap(true);
        if (hprofFile != null) {
//標記
            getWatcher().markPublished(destroyedActivityInfo.mActivityName);
            getWatcher().triggerGc();
            //
            final HeapDump heapDump = new HeapDump(hprofFile, destroyedActivityInfo.mKey, destroyedActivityInfo.mActivityName);
            getHeapDumpHandler().process(heapDump);
        } else {
            MatrixLog.i(TAG, "heap dump for further analyzing activity with key [%s] was failed, just ignore.",
                    destroyedActivityInfo.mKey);
        }
        return true;
    }
}

11.CanaryWorkerService裁剪

//裁剪hprof,并上報
public class CanaryWorkerService extends MatrixJobIntentService {
    private static final String TAG = "Matrix.CanaryWorkerService";

    private static final int JOB_ID = 0xFAFBFCFD;
    private static final String ACTION_SHRINK_HPROF = "com.tencent.matrix.resource.worker.action.SHRINK_HPROF";
    private static final String EXTRA_PARAM_HEAPDUMP = "com.tencent.matrix.resource.worker.param.HEAPDUMP";

    //入口
    public static void shrinkHprofAndReport(Context context, HeapDump heapDump) {
        final Intent intent = new Intent(context, CanaryWorkerService.class);
        intent.setAction(ACTION_SHRINK_HPROF);
        intent.putExtra(EXTRA_PARAM_HEAPDUMP, heapDump);
        enqueueWork(context, CanaryWorkerService.class, JOB_ID, intent);
    }

    @Override
    protected void onHandleWork(Intent intent) {
        if (intent != null) {
            final String action = intent.getAction();
            if (ACTION_SHRINK_HPROF.equals(action)) {
                try {
                    intent.setExtrasClassLoader(this.getClassLoader());
                    final HeapDump heapDump = (HeapDump) intent.getSerializableExtra(EXTRA_PARAM_HEAPDUMP);
                    if (heapDump != null) {
                        //裁剪并上報
                        doShrinkHprofAndReport(heapDump);
                    } else {
                        MatrixLog.e(TAG, "failed to deserialize heap dump, give up shrinking and reporting.");
                    }
                } catch (Throwable thr) {
                    MatrixLog.printErrStackTrace(TAG, thr, "failed to deserialize heap dump, give up shrinking and reporting.");
                }
            }
        }
    }

    private void doShrinkHprofAndReport(HeapDump heapDump) {
        final File hprofDir = heapDump.getHprofFile().getParentFile();
        //裁剪之后的Hprof檔案名
        final File shrinkedHProfFile = new File(hprofDir, getShrinkHprofName(heapDump.getHprofFile()));
        //壓縮檔案
        final File zipResFile = new File(hprofDir, getResultZipName("dump_result_" + android.os.Process.myPid()));
        final File hprofFile = heapDump.getHprofFile();
        ZipOutputStream zos = null;
        try {
            long startTime = System.currentTimeMillis();
            //執行Hprof裁剪
            new HprofBufferShrinker().shrink(hprofFile, shrinkedHProfFile);
            MatrixLog.i(TAG, "shrink hprof file %s, size: %dk to %s, size: %dk, use time:%d",
                    hprofFile.getPath(), hprofFile.length() / 1024, shrinkedHProfFile.getPath(), shrinkedHProfFile.length() / 1024, (System.currentTimeMillis() - startTime));
            //打成壓縮包
            zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipResFile)));
            //記錄一些設備資訊
            final ZipEntry resultInfoEntry = new ZipEntry("result.info");
            //裁剪后的Hprof檔案
            final ZipEntry shrinkedHProfEntry = new ZipEntry(shrinkedHProfFile.getName());

            zos.putNextEntry(resultInfoEntry);
            final PrintWriter pw = new PrintWriter(new OutputStreamWriter(zos, StandardCharsets.UTF_8));
            pw.println("# Resource Canary Result Infomation. THIS FILE IS IMPORTANT FOR THE ANALYZER !!");
            pw.println("sdkVersion=" + Build.VERSION.SDK_INT);//系統版本
            pw.println("manufacturer=" + Build.MANUFACTURER);//廠商資訊
            pw.println("hprofEntry=" + shrinkedHProfEntry.getName());//裁剪后Hprof檔案名
            pw.println("leakedActivityKey=" + heapDump.getReferenceKey());//泄漏Activity實體的key
            pw.flush();
            zos.closeEntry();

            zos.putNextEntry(shrinkedHProfEntry);
            copyFileToStream(shrinkedHProfFile, zos);
            zos.closeEntry();

            //原始資料洗掉
            shrinkedHProfFile.delete();
            hprofFile.delete();

            MatrixLog.i(TAG, "process hprof file use total time:%d", (System.currentTimeMillis() - startTime));
            //CanaryResultService執行上報邏輯
            CanaryResultService.reportHprofResult(this, zipResFile.getAbsolutePath(), heapDump.getActivityName());
        } catch (IOException e) {
            MatrixLog.printErrStackTrace(TAG, e, "");
        } finally {
            closeQuietly(zos);
        }
    }

    //回傳壓縮后的 _shrink.hprof
    private String getShrinkHprofName(File origHprof) {
        final String origHprofName = origHprof.getName();
        final int extPos = origHprofName.indexOf(DumpStorageManager.HPROF_EXT);
        final String namePrefix = origHprofName.substring(0, extPos);
        return namePrefix + "_shrink" + DumpStorageManager.HPROF_EXT;
    }

    //回傳zip todo 會不會重復
    private String getResultZipName(String prefix) {
        StringBuilder sb = new StringBuilder();
        sb.append(prefix).append('_')
                .append(new SimpleDateFormat("yyyyMMddHHmmss", Locale.ENGLISH).format(new Date()))
                .append(".zip");
        return sb.toString();
    }
}

12.CanaryResultService發現問題了,上報

public class CanaryResultService extends MatrixJobIntentService {
    private static final String TAG = "Matrix.CanaryResultService";

    private static final int JOB_ID = 0xFAFBFCFE;
    private static final String ACTION_REPORT_HPROF_RESULT = "com.tencent.matrix.resource.result.action.REPORT_HPROF_RESULT";
    private static final String EXTRA_PARAM_RESULT_PATH = "RESULT_PATH";
    private static final String EXTRA_PARAM_ACTIVITY = "RESULT_ACTIVITY";

    //入口
    public static void reportHprofResult(Context context, String resultPath, String activityName) {
        final Intent intent = new Intent(context, CanaryResultService.class);
        intent.setAction(ACTION_REPORT_HPROF_RESULT);
        intent.putExtra(EXTRA_PARAM_RESULT_PATH, resultPath);
        intent.putExtra(EXTRA_PARAM_ACTIVITY, activityName);
        enqueueWork(context, CanaryResultService.class, JOB_ID, intent);
    }

    @Override
    protected void onHandleWork(Intent intent) {
        if (intent != null) {
            final String action = intent.getAction();
            if (ACTION_REPORT_HPROF_RESULT.equals(action)) {
                final String resultPath = intent.getStringExtra(EXTRA_PARAM_RESULT_PATH);
                final String activityName = intent.getStringExtra(EXTRA_PARAM_ACTIVITY);

                if (resultPath != null && !resultPath.isEmpty()
                    && activityName != null && !activityName.isEmpty()) {
                    //上報問題
                    doReportHprofResult(resultPath, activityName);
                } else {
                    MatrixLog.e(TAG, "resultPath or activityName is null or empty, skip reporting.");
                }
            }
        }
    }

    // notice: compatible
    //上報問題
    private void doReportHprofResult(String resultPath, String activityName) {
        Issue issue = new Issue(SharePluginInfo.IssueType.LEAK_FOUND);
        final JSONObject resultJson = new JSONObject();
        try {
            resultJson.put(SharePluginInfo.ISSUE_RESULT_PATH, resultPath);
            resultJson.put(SharePluginInfo.ISSUE_ACTIVITY_NAME, activityName);
            issue.setContent(resultJson);
        } catch (Throwable thr) {
            MatrixLog.printErrStackTrace(TAG, thr, "unexpected exception, skip reporting.");
        }

        Plugin plugin =  Matrix.with().getPluginByClass(ResourcePlugin.class);
        if (plugin != null) {
            plugin.onDetectIssue(issue);
        }
    }
}

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/289897.html

標籤:其他

上一篇:Java , C++ , Python , 編程語言怎么選?

下一篇:Flutter 里的語法糖決議,知其所然方能瀟灑舞劍

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more