如何应对 Android 面试官 -> 手写 APM 核心实现

Android APM方案(一)完成代码注入 Android APM方案(一)完成代码注入 什么是APMAPM 是Application perfmance monitor的简称, 应用性能监控。在移动互联网对人们生活影响越来越大的今天,App的功能越来越全面,从吃穿住行,到支付开房,全方面覆盖。相同功能的App存在很多竞品,比如携程和艺龙,天猫和京东,网易云音乐和QQ音乐。随之而来的就是App性能的要求越来越高,不能被动的等待用户异常的发生,应该主动监控 阅读详情

前言


本章我们主要是来手写一个 APM 框架;

上一章节补充


上一周我们讲解了包体积优化,讲到了 SO 的动态加载,另外我们在加载 SO 的时候,经常会遇到

UnSatisfiedLinkError

这个错误,主要的原因是兼容性问题,包 SO 的裁剪,由于国内厂商 Rom 的魔改,修改了 SO 的加载路径;

这里我们通常都是使用一个开源框架 ReLinker 来解决这个问题。

核心是:解析 so 的二进制文件,获取 so 的依赖属性(这个是 Android 系统行为导致,如果这个 so 有依赖其他的 so, 那么就要先加载依赖的 so 然后才能加载目标 so,这个能力在 7.0 及以后的版本解决掉了)

APM 重点关注指标


搭建 APM 框架,我们要重点关注一些指标

  • 稳定性问题
  • 流量/网络问题(Http的可达率、拦截器)
  • 电量
  • 流量消耗
  • 内存(指标的统计、内存泄露)
  • FPS
  • 启动耗时监控

整体的 APM 框架,我们可以借助开源框架的思想来实现,这里大家可以深入学习了解下 ArgusAPM 和 Matrix

电量检测

整体的 APM 框架,我们通常需要定义一些核心接口类,让各个检测模块分别来实现,我们简单定义一个接口 ITracker 用来规范相关采集接口

public interface ITracker {
    
    void destroy(Application application);

    void startTrack(Application application);

    void pauseTrack(Application application);
}

再来定义一个生命周期接口处理类,我们在所需要的采集模块复写对应的生命周期接口即可

public abstract class ActivityLifeCycleCallbacks implements Application.ActivityLifecycleCallbacks {
    @Override
    public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
        
    }

    @Override
    public void onActivityStarted(@NonNull Activity activity) {

    }

    @Override
    public void onActivityResumed(@NonNull Activity activity) {

    }

    @Override
    public void onActivityPaused(@NonNull Activity activity) {

    }

    @Override
    public void onActivityStopped(@NonNull Activity activity) {

    }

    @Override
    public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {

    }

    @Override
    public void onActivityDestroyed(@NonNull Activity activity) {

    }
}

电量检测这块,本质就是接收电量信息的 receiver 发过来的电量信息,这个我们可以在页面的 onActivityStarted 和 onActivityStoped 的时候出来相关获取信息;

我们先来定一个电量信息类

public class BatteryInfo {
    public boolean charging;
    public String activityName;
    public float cost;
    public long duration;
    public String display;
    public int total;
    public int voltage;
    public float screenBrightness;
}

接下来我们来定义采集类 BatteryStatsTracker 我们在 onSctivityStarted 的时候获取电量的百分比,在 onActivityStoped 的时候采集相关信息,并将采集的信息回调出去;

回调接口定义类:

public interface IBatteryListener {
    void onBatteryCost(BatteryInfo batteryInfo);
}

BatteryStatsTracker 实现

public class BatteryStatsTracker  extends ActivityLifeCycleCallbacks implements ITracker {
    private static BatteryStatsTracker sInstance;
    private Handler mHandler;
    private String display;
    private int mStartPercent;
    private HandlerThread handlerThread;

    private BatteryStatsTracker() {
        handlerThread = new HandlerThread("BatteryStats",Thread.NORM_PRIORITY);
        mHandler = new Handler(handlerThread.getLooper());
    }

    public static BatteryStatsTracker getInstance() {
        if (sInstance == null) {
            synchronized (BatteryStatsTracker.class) {
                if (sInstance == null) {
                    sInstance = new BatteryStatsTracker();
                }
            }
        }
        return sInstance;
    }

    @Override
    public void destroy(Application application) {

    }

    @Override
    public void startTrack(Application application) {
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
    }

    @Override
    public void pauseTrack(Application application) {

    }

    @Override
    public void onActivityStarted(@NonNull Activity activity) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
                Intent batteryStatus = activity.getApplication().registerReceiver(null, filter);
                // 获取电量百分比
                mStartPercent = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
            }
        });
    }


    @Override
    public void onActivityStopped(@NonNull Activity activity) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                if (mListeners.size() > 0) {
                    BatteryInfo batteryInfo = getBatteryInfo(activity.getApplication());
                    for (IBatteryListener listener : mListeners) {
                        listener.onBatteryCost(batteryInfo);
                    }
                }
            }
        });
    }

    private BatteryInfo getBatteryInfo(Application application) {
        if (TextUtils.isEmpty(display)) {
            display = "" + application.getResources().getDisplayMetrics().widthPixels + "*" + application.getResources().getDisplayMetrics().heightPixels;
        }
        BatteryInfo batteryInfo = new BatteryInfo();
        try {
            IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
            Intent batteryStatus = application.registerReceiver(null, filter);
            int status = batteryStatus.getIntExtra("status", 0);
            boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
                    status == BatteryManager.BATTERY_STATUS_FULL;
            int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
            batteryInfo.charging = isCharging;
            batteryInfo.cost = isCharging ? 0 : mStartPercent - batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
            // 时间这里取了个巧,就是在 provider 中申请一个静态变量,这样进程启动的时候获取下当前时间
            batteryInfo.duration += (SystemClock.uptimeMillis() - LauncherHelpProvider.sStartUpTimeStamp) / 1000;
            batteryInfo.screenBrightness = getSystemScreenBrightnessValue(application);
            batteryInfo.display = display;
            batteryInfo.total = scale;
            Log.v("Battery", "total " + batteryInfo.total + " 用时间 " + batteryInfo.duration / 1000 + " 耗电  " + batteryInfo.cost);
        } catch (Exception e) {
            //
        }

        return batteryInfo;
    }

    public int getSystemScreenBrightnessValue(Application application) {
        ContentResolver contentResolver = application.getContentResolver();
        int defVal = 125;
        return Settings.System.getInt(contentResolver,
                Settings.System.SCREEN_BRIGHTNESS, defVal);
    }

    private List<IBatteryListener> mListeners = new ArrayList<>();

    public void addBatteryListener(IBatteryListener listener) {
        mListeners.add(listener);
    }

    public void removeBatteryListener(IBatteryListener listener) {
        mListeners.remove(listener);
    }
}

LaunchHelpProvider

public class LauncherHelpProvider extends ContentProvider {

    // 核心代码就在这里
    public static long sStartUpTimeStamp = SystemClock.uptimeMillis();

    @Override
    public boolean onCreate() {
        return false;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
        return null;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
        return 0;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
        return 0;
    }
}

这样,我们就简单实现了 电量的采集处理;

内存信息

我们先来定义内存信息类 MemoryInfo

public class MemoryInfo {
    public String procName;
    public AppMemory appMemory;
    public SystemMemory systemMemoryInfo;
    public String display;
    public int activityCount;

    public static class AppMemory {
        public long dalvikPss;//java占用内存大小
        public long nativePss;//前进程总私有已用内存大小
        public long totalPss;//当前进程总内存大小
        public Debug.MemoryInfo mMemoryInfo;
    }

    public static class SystemMemory {
        public long availMem;
        public boolean lowMemory;
        public long threshold;
        public long totalMem;
    }
}

内存这块主要分为两块,一个是内存指标的获取,另一个就是内存泄露;

我们来定义一个内存泄露回调接口类 ITrackMemoryListener

public interface ITrackMemoryListener {

    void onLeakActivity(String activity, int count);

    void onCurrentMemoryCost(MemoryInfo trackMemoryInfo);
}

接下来我们来定义采集类

public class MemoryLeakTrack extends ActivityLifeCycleCallbacks implements ITracker {

    private static volatile MemoryLeakTrack sInstance = null;
    private HandlerThread handlerThread = new HandlerThread("BatteryStats",Thread.NORM_PRIORITY);

    private MemoryLeakTrack() {
    }

    public static MemoryLeakTrack getInstance() {
        if (sInstance == null) {
            synchronized (MemoryLeakTrack.class) {
                if (sInstance == null) {
                    sInstance = new MemoryLeakTrack();
                }
            }
        }
        return sInstance;
    }

    private Handler mHandler = new Handler(handlerThread.getLooper());
    private WeakHashMap<Activity, String> mActivityStringWeakHashMap = new WeakHashMap<>();

    @Override
    public void destroy(final Application application) {
        mHandler.removeCallbacksAndMessages(null);
    }

    @Override
    public void startTrack(final Application application) {
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                if (mMemoryListeners.size() > 0 && !ActivityStack.getInstance().isInBackGround()) {
                    MemoryInfo trackMemoryInfo = collectMemoryInfo(application);
                    for (ITrackMemoryListener listener : mMemoryListeners) {
                        listener.onCurrentMemoryCost(trackMemoryInfo);
                    }
                }
                mHandler.postDelayed(this, 30 * 1000);
            }
        }, 30 * 1000);
    }

    @Override
    public void pauseTrack(Application application) {

    }

    private Set<ITrackMemoryListener> mMemoryListeners = new HashSet<>();

    public void addOnMemoryLeakListener(ITrackMemoryListener leakListener) {
        mMemoryListeners.add(leakListener);
    }

    public void removeOnMemoryLeakListener(ITrackMemoryListener leakListener) {
        mMemoryListeners.remove(leakListener);
    }

    @Override
    public void onActivityStopped(@NonNull Activity activity) {
        //  退后台,GC 找LeakActivity
        if (!ActivityStack.getInstance().isInBackGround()) {
            return;
        }
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                mallocBigMem();
                Runtime.getRuntime().gc();
            }
        }, 1000);

        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                try {
                    if (!ActivityStack.getInstance().isInBackGround()) {
                        return;
                    }
                    //  分配大点内存促进GC
                    mallocBigMem();
                    Runtime.getRuntime().gc();
                    SystemClock.sleep(100);
                    System.runFinalization();
                    HashMap<String, Integer> hashMap = new HashMap<>();
                    for (Map.Entry<Activity, String> activityStringEntry : mActivityStringWeakHashMap.entrySet()) {
                        String name = activityStringEntry.getKey().getClass().getSimpleName();
                        Integer value = hashMap.get(name);
                        if (value == null) {
                            hashMap.put(name, 1);
                        } else {
                            hashMap.put(name, value + 1);
                        }
                    }
                    if (mMemoryListeners.size() > 0) {
                        for (Map.Entry<String, Integer> entry : hashMap.entrySet()) {
                            for (ITrackMemoryListener listener : mMemoryListeners) {
                                listener.onLeakActivity(entry.getKey(), entry.getValue());
                            }
                        }
                    }
                } catch (Exception ignored) {
                }
            }
        }, 10000);
    }

    @Override
    public void onActivityDestroyed(@NonNull Activity activity) {
        mActivityStringWeakHashMap.put(activity, activity.getClass().getSimpleName());
    }

    private static String display;

    private MemoryInfo collectMemoryInfo(Application application) {
        if (TextUtils.isEmpty(display)) {
            display = "" + application.getResources().getDisplayMetrics().widthPixels + "*" + application.getResources().getDisplayMetrics().heightPixels;
        }
        ActivityManager activityManager = (ActivityManager) application.getSystemService(Context.ACTIVITY_SERVICE);
        // 系统内存
        ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
        activityManager.getMemoryInfo(memoryInfo);
        MemoryInfo.SystemMemory systemMemory = new MemoryInfo.SystemMemory();
        systemMemory.availMem = memoryInfo.availMem >> 20;
        systemMemory.totalMem = memoryInfo.totalMem >> 20;
        systemMemory.lowMemory = memoryInfo.lowMemory;
        systemMemory.threshold = memoryInfo.threshold >> 20;

        //java内存
        Runtime rt = Runtime.getRuntime();

        //进程Native内存
        MemoryInfo.AppMemory appMemory = new MemoryInfo.AppMemory();
        Debug.MemoryInfo debugMemoryInfo = new Debug.MemoryInfo();
        Debug.getMemoryInfo(debugMemoryInfo);
        appMemory.nativePss = debugMemoryInfo.nativePss >> 10;
        appMemory.dalvikPss = debugMemoryInfo.dalvikPss >> 10;
        appMemory.totalPss = debugMemoryInfo.getTotalPss() >> 10;
        appMemory.mMemoryInfo = debugMemoryInfo;

        MemoryInfo trackMemoryInfo = new MemoryInfo();
        trackMemoryInfo.systemMemoryInfo = systemMemory;
        trackMemoryInfo.appMemory = appMemory;
        trackMemoryInfo.procName = ProcessUtils.Companion.getCurrentProcessName(application);
        trackMemoryInfo.display = display;
        trackMemoryInfo.activityCount = ActivityStack.getInstance().getSize();
        return trackMemoryInfo;
    }

    private void mallocBigMem() {
        byte[] leakHelpBytes = new byte[4 * 1024 * 1024];
        for (int i = 0; i < leakHelpBytes.length; i += 1024) {
            leakHelpBytes[i] = 1;
        }
    }
}

在 ActivityStop 的时候,分别大内存,触发 gc,然后将泄露的 Activity 收集起来并回调出去;

ANR 检测

ANR 的检测借鉴的开源思路;

class AnrError(
    private val stackTraceThrowable: StackTraceCollector.StackTraceThrowable,
    private val duration: Long
) : Error("Application Not Responding for at least $duration ms.", stackTraceThrowable) {

    override fun fillInStackTrace(): Throwable {
        stackTrace = emptyArray()
        return this
    }

    companion object {
        private fun threadTitle(thread: Thread): String {
            return "${thread.name} (state = ${thread.state})"
        }

        fun newMainInstance(duration: Long): AnrError {
            val mainThread = Looper.getMainLooper().thread
            val stackTraces = mainThread.stackTrace
            return AnrError(
                StackTraceCollector(
                    threadTitle(mainThread),
                    stackTraces
                ).StackTraceThrowable(null), duration
            )
        }

        fun newInstance(
            duration: Long,
            prefix: String,
            logThreadsWithoutStackTrace: Boolean
        ): AnrError {
            val mainThread = Looper.getMainLooper().thread
            val threadStackTraces =
                sortedMapOf<Thread, Array<StackTraceElement>>(ThreadComparator())
            for (entry in Thread.getAllStackTraces().entries) {
                if (entry.key == mainThread || entry.key.name.startsWith(prefix) && (logThreadsWithoutStackTrace || !entry.value.isNullOrEmpty())) {
                    threadStackTraces[entry.key] = entry.value
                }
            }

            if (!threadStackTraces.containsKey(mainThread)) {
                threadStackTraces[mainThread] = mainThread.stackTrace
            }
            var throwable: StackTraceCollector.StackTraceThrowable? = null
            for (entry in threadStackTraces.entries) {
                throwable = StackTraceCollector(threadTitle(entry.key), entry.value).StackTraceThrowable(throwable)
            }
            return AnrError(throwable!!, duration)
        }
    }
}

主要核心思想在收集 anr,我们来定义 AnrMonitor

class AnrMonitor(private val timeoutInterval: Long = DEFAULT_ANR_TIMEOUT) :
    LifecycleEventObserver {
    private val mainHandler: Handler = Handler(Looper.getMainLooper())
    private var handlerThread: HandlerThread? = null
    private var backgroundHandler: Handler? = null

    init {
        handlerThread = object : HandlerThread(TAG, Thread.NORM_PRIORITY) {
            override fun onLooperPrepared() {
                super.onLooperPrepared()
                backgroundHandler = Handler(handlerThread!!.looper)
            }
        }
    }

    private var mAnrInterceptor: AnrInterceptor? = DEFAULT_ANR_INTERCEPTOR
    private var mAnrListener: AnrListener? = DEFAULT_ANR_LISTENER

    private var mPrefix: String? = ""
    private var mLogThreadWithoutStackTrace = false
    private var mIgnoreDebugger = false

    @Volatile
    private var mTick = 0L

    @Volatile
    private var mReported = false

    @Volatile
    private var mInterval: Long = timeoutInterval

    private val mTicker = Runnable {
        mTick = 0
        mReported = false
    }

    private val mAnrCollector = Runnable {
        // If the main thread has not handled mTicker, it is blocked. ANR.
        if (mTick != 0L && !mReported) {
            //noinspection ConstantConditions
            if (!mIgnoreDebugger && (Debug.isDebuggerConnected() || Debug.waitingForDebugger())) {
                Log.i("tag","An ANR was detected but ignored because the debugger is connected (you can prevent this with setIgnoreDebugger(true))")
                mReported = true
                delayToTryCollectingAnr()
                return@Runnable
            }
            mInterval = mAnrInterceptor?.intercept(mTick) ?: 0
            if (mInterval > 0) {
                delayToTryCollectingAnr()
                return@Runnable
            }
            val anrError = if (mPrefix == null) {
                AnrError.newMainInstance(mTick)
            } else {
                AnrError.newInstance(mTick, mPrefix!!, mLogThreadWithoutStackTrace)
            }
            mAnrListener?.onAppNotResponding(anrError)
            mInterval = timeoutInterval
            mReported = true
        }
        delayToTryCollectingAnr()
    }

    fun setAnrListener(anrListener: AnrListener?): AnrMonitor {
        mAnrListener = anrListener ?: DEFAULT_ANR_LISTENER
        return this
    }

    fun setAnrInterceptor(anrInterceptor: AnrInterceptor?): AnrMonitor {
        mAnrInterceptor = anrInterceptor ?: DEFAULT_ANR_INTERCEPTOR
        return this
    }

    fun setReportThreadNamePrefix(prefix: String): AnrMonitor {
        mPrefix = prefix
        return this
    }

    fun setReportMainThreadOnly(): AnrMonitor {
        mPrefix = null
        return this
    }

    fun setReportAllThreads(): AnrMonitor {
        mPrefix = ""
        return this
    }

    fun setLogThreadWithoutStackTrace(logThreadsWithoutStackTrace: Boolean): AnrMonitor {
        mLogThreadWithoutStackTrace = logThreadsWithoutStackTrace
        return this
    }

    fun setIgnoreDebugger(ignoreDebugger: Boolean): AnrMonitor {
        mIgnoreDebugger = ignoreDebugger
        return this
    }

    override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
        if (event == Lifecycle.Event.ON_CREATE) {//app is created
            onAppCreate()
        } else if (event == Lifecycle.Event.ON_STOP) {//no activities in stack
            onAppStop()
        } else if (event == Lifecycle.Event.ON_START) {//when first activity is started
            onAppStart()
        }
    }

    private fun onAppStart() {
        delayToTryCollectingAnr()
    }

    @Synchronized
    private fun delayToTryCollectingAnr() {
        val needPost = mTick == 0L
        mTick += mInterval
        if (needPost) {
            mainHandler.post(mTicker)
        }
        backgroundHandler?.postDelayed(mAnrCollector, ANR_COLLECTING_INTERVAL)
    }

    private fun onAppCreate() {
        handlerThread?.start()
    }

    private fun onAppStop() {
        backgroundHandler?.removeCallbacksAndMessages(null)
        mainHandler.removeCallbacksAndMessages(null)
    }

    fun onAppTerminate() {
        handlerThread?.quitSafely()
        handlerThread = null
    }

    companion object {
        const val DEFAULT_ANR_TIMEOUT = 5000L
        const val ANR_COLLECTING_INTERVAL = 2000L
        private const val TAG = "||ANR-Monitor||"

        private val DEFAULT_ANR_LISTENER = object : AnrListener {
            override fun onAppNotResponding(error: AnrError) {
                throw error
            }
        }

        private val DEFAULT_ANR_INTERCEPTOR = object : AnrInterceptor {
            override fun intercept(duration: Long): Long {
                return 0
            }
        }
    }
}

主要逻辑在 mAnrCollector 这个 Runnable 中;

卡顿

卡顿这块在将卡顿和布局优化的时候,也有提到过几种监控方式,这里可以采用 编舞者 的方式

private void getFps(){
    if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN){
        return;
    }
    Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
        @Override
        public void doFrame(long timeNanos) {
            if(mStartFrameTime == 0){
                mStartFrameTime = timeNanos;
            }

            float interval = (timeNanos - mStartFrameTime) / 1000000.0f;
            if(interval > MONITOR_INTERVAL){
                double fps = (mStartFrameCount*1000L)/interval;
                Log.e("tag","fps"+fps);
                mStartFrameCount = 0;
                mStartFrameTime = 0;
            }else{
                ++mStartFrameCount;
            }
            Choreographer.getInstance().postFrameCallback(this);
        }
    });
}

好了,APM 整体就讲到这里吧

欢迎三连


来都来了,点个关注,点个赞吧,你的支持是我最大的动力~

Android 性能监控系列一(原理篇) 在此为大家准备了四节优质的Android高级进阶视频:架构师项目实战——全球首批Android开发者对Android架构的见解。 阅读详情

相关推荐

深入探索Android稳定性优化(详细解析)

前言:成为一名优秀的Android开发,需要一份完备的知识体系,在这里,让我们一起成长为自己所想的那样。 众所周知,移动开发已经来到了后半场,为了能够在众多开发者中脱颖而出,我们需要对某一个领域有深入地研究与心得,对于Android开发者来说,目前,有几个好的细分领域值得我们去建立自己的技术壁垒,如下所示: 1、性能优化专家:具备深度性能优化与体系化APM建设的能力 2、架构师:具有丰富的应用架构设计经验与心得,对Android Framework层与热门三方库的实现原理与架构设计了如.

m0_62167422的博客 1184

Android轻量级APM性能监测方案

App性能如何量化 如何衡量一个APP性能好坏?直观感受就是:启动快、流畅、不闪退、耗电少等感官指标,反应到技术层面包装下就是:FPS(帧率)、界面渲染速度、Crash率、网络、CPU使用率、电量损耗速度等,一般挑其中几个关键指标作为APP质量的标尺。目前也有多种开源APM监控方案,但大部分偏向离线检测,对于线上监测而言显得太重,可能会适得其反,方案简单对比如下: SDK 现状与问题 是否推荐直接线上使用 腾讯matrix

fishmai的专栏 2613

Android应用接入ApmInsight APM

有统计显示,性能问题是导致用户流失的重要原因之一。如果用户在使用App的过程中出现诸如应用崩溃、页面卡顿、响应速度慢、发热严重、流量电量消耗大等问题时,很有可能就会卸载掉App。而目前获客成本是非常高昂的,因此为了避免因为性能问题导致用户流失的情况,我们需要搭建一套移动性能监控平台,然后再使用其他的技术及时的修复这些线上问题。

xiangzhihong8的专栏 4123

Android 性能优化 APM 平台搭建

由于答应微信群友分享 Android APM 平台分享,不拖欠了,今天开始分享,否则发红包,既然说了,就总结一下前阵子研究的 Android 性能监控平台 开发缘由 微信张绍文大佬 《Android开发高手课》中提到 性能优化章节提到过,优化70% 依赖监控性能,30%需要治理,比如App 启动时间第一次优化了 40%,期间升上来了,第二次又优化了40% ,所以需要性能监控平台 期间找到业内...

杨哲丶的专栏 2274

Android版APM地面站

Android版APM地面站

开源的 Android APM (应用性能监测) 工具.zip

开源的 Android APM (应用性能监测) 工具.zip,Scouter是一个开源的APM(应用程序性能管理)工具。

Android工程师面试进阶:大厂核心考点与系统设计

Android开发中,掌握系统设计和架构思维是进阶的关键。从JVM原理到Kotlin协程,深入理解语言特性对比能体现开发者技术深度。Android Framework层的Binder机制、View事件分发等核心原理,是面试中的高频考点。性能优化实战如内存泄漏定位、启动速度优化等,结合工具链使用和监控体系搭建,能显著提升应用质量。在组件化架构设计中,依赖管理和通信方案选型尤为重要,而跨平台技术如Flutter的渲染管线原理也值得关注。对于Android开发者来说,构建完整的知识体系,结合项目经验梳理和算法准备

cneo2012的博客 437

国外无人机蜂群作战典型战例及发展趋势

参与袭击的无人机为简易组装的固定翼飞机,采用小排量活塞发动机和手机GPS通信模块零件,机翼为轻木加泡沫板混合结构,尾梁为铝合金方管,主要依靠胶带粘接,以上全部配件均可在商用市场买到,成本较低,可在短时间内装配完成。③战场纵深短、瞬时性强。蜂群作战的参战数量显著增加,这一方面得益于无人机技术的飞速发展使得廉价无人机可以大量生产和仿制,另一方面凭借迅速发展的投放和回收技术以及集群控制技术,使得无人机蜂群可在极短时间内实现编队饱和攻击,在快速消耗敌方高价值防空武器系统的同时,保证己方物资系统的突防概率。

renhongxia1的博客 2479

太牛了!Android开发高级工程师实战手写框架

导语 又到了金九银十的面试季,自己也不得不参与到这场战役中来,其实是从去年底就开始看,Android的好机会确实不太多,但也还好,3年+的android开发经历还是有一些面试机会的,不过确实不像几年前门槛那么低了,总的体会就是小的创业公司比较注重你的项目经历是否和自己的贴合,直接能过来独当一面。 大厂除了看中项目经历外,还比较注重你知识面的广度,是广度、深度和解决方案等多方面的考察,平时够工作要好好积累临时刷题只聊点皮毛估计是过不了关的。下面就总结一些大厂面试遇到必定会问的知识点,我把网络上讲解的最好的

CHAMPION8888的博客 561

【DMA】认识 DMA 及其工作流程(与传统CPU存取方式的区别)

【DMA】认识 DMA 及其工作流程(与传统CPU存取方式的区别)

challenglistic的博客 5万+

SAP VF01开票报错:出口贸易数据不完整

找了半天资料,最后发现是没有维护凭证抬头的数据;由于我不是SD模块顾问,整体流程使用的是原本环境里面的数据,用新建工厂引用,所以也不知道为啥会出发这个贸易业务。正常传到FICO模块,生成会计凭证、获利分析、利润中心及CO凭证。需要维护这三个字段后才能正常转到FI生成会计凭证。VF01开票报错:出口贸易数据不完整。

MaiKouBei的博客 1539

apm性能监控系统,Android开发岗还不会这些问题,系列教学

前言 金三银四,又是一年校招季。 经历过,才深知不易。最近,和作为校招面试官的同事聊了聊,问他们是如何去考察一个学生的,我简单归为以下几点: 聪明、反应快,这点自不必说,聪明意味着学习能力、适应力强,能够快速胜任工作。 算法不错,代码基本功好,这点其实考察的是算法能力和代码是否写得优雅。 基础过硬,技术岗面试最核心的还是考察「技术储备」,包括了语言基本功,操作系统、网络、体系结构、系统设计。 语言组织和表达能力,这点很重要,很多同学懂得某个知识点,却很难用简洁准确的语言表述出来。 想必有很多同学在刷题.

m0_56146626的博客 148

手把手教你用Qt和QScada框架,从零搭建一个简易的工业组态监控界面

本文详细介绍了如何使用Qt和QScada框架从零搭建工业组态监控界面,涵盖环境配置、界面设计、动态数据绑定及高级功能实现。通过实战案例演示,帮助开发者快速掌握SCADA系统开发技巧,提升工业自动化项目的开发效率。

weixin_32932149的博客 340

apm性能监控系统,Android开发岗还不会这些问题,真香

前言 金三银四,又是一年校招季。 经历过,才深知不易。最近,和作为校招面试官的同事聊了聊,问他们是如何去考察一个学生的,我简单归为以下几点: 聪明、反应快,这点自不必说,聪明意味着学习能力、适应力强,能够快速胜任工作。 算法不错,代码基本功好,这点其实考察的是算法能力和代码是否写得优雅。 基础过硬,技术岗面试最核心的还是考察「技术储备」,包括了语言基本功,操作系统、网络、体系结构、系统设计。 语言组织和表达能力,这点很重要,很多同学懂得某个知识点,却很难用简洁准确的语言表述出来。 想必有很多同学在刷题.

m0_56169789的博客 130

Unity嵌套ScrollView滑动冲突解决方案:ExecuteEvents.Execute事件透传

在Unity UGUI中,竖向与横向ScrollView嵌套时出现滑动失效,本质是EventSystem事件分发机制的拦截限制。其原理在于PointerDown被外层捕获后,后续PointerMove默认不向下透传;ExecuteEvents.Execute提供了一条绕过常规冒泡流程的官方事件旁路,支持低延迟、方向自适应的精准透传。该技术具备高兼容性与零侵入特性,广泛应用于电商商品页、内容流卡片组、多级导航等需‘列表中嵌套轮播/标签’的真实场景,是解决UGUI嵌套滚动问题的核心工程实践方案。

weixin_33164198的博客 629

华为OD新系统机试真题 - 获取大写字母瓷砖拼出独特图案数量

华为OD机试新系统真题【获取大写字母瓷砖拼出独特图案数量】,提供C语言、Java、C++、Python、Go、JavaScript源码实现,并提供完整思路讲解,以及在线OJ模拟做题。华为OD新系统四月二十九号机试真题【获取大写字母瓷砖拼出独特图案数量】

qq_45776114的博客 1218

apm性能监控系统,一次关于JVM的面试经历,系列篇

程序员与别的专业有所不同,其他专业都是越老越香,而程序员却是一个例外,因为计算机技术更新太快,而且工作强度很大,因此大部分程序员只会写 3 年代码。3 年后要不晋升做项目经理,要么转行,个别研究所除外。 常规电话面试 1 JAVA基础思想:设计模式与面向对象 2 安卓View绘制流程 3 常规的组件问题 4 事件分发机制 5 多线程和安全问题 6 安卓性能优化和兼容问题: 性能优化回答 具体面试 1 线程池原理 2 线程安全有多少种实现方式 3 图片加载框架原理 4 Http 协议原理 5 Okhttp.

Sunbuyi的博客 197

2025华为od机试真题B卷【停车场费用统计】Python实现

2025华为od机试真题B卷【停车场费用统计】Python实现

MISAYAONE的博客 535

apm性能监控系统,写给1-3年安卓程序员的几点建议,算法太TM重要了

前言 回顾一下自己这段时间的经历,因公司突然通知裁员,我匆匆忙忙地出去面了几家,但最终都没有拿到offer,我感觉今年的寒冬有点冷。公司开始第二波裁员,我决定主动拿赔偿走人。后续的面试过程我做了一些准备,基本都能走到hr面,后面我也顺利地拿到了offer,我给大家分享下我的求职体会,希望能给大家一些参考。 1、应用层开发,不限于各种产品,主要还是Android或iOS原生开发,主要是各种性能优化。 2、嵌入式开发,不限于各种开发板子,物联网,智能家居 3、安全开发,不限于各种反逆向,反汇编,寻找安全漏洞 4

m0_56130999的博客 487

[ vulhub漏洞复现篇 ] Django SQL注入漏洞复现 CVE-2021-35042

[ vulhub漏洞复现篇 ] Django SQL注入漏洞复现 CVE-2021-35042 Django 组件存在 SQL 注入漏洞,该漏洞是由于对 QuerySet.order_by()中用户提供数据的过滤不足,攻击者可利用该漏洞在未授权的情况下,构造恶意数据执行 SQL 注入攻击,最终造成服务器敏感信息泄露。

qq_51577576的博客 3228

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

深入探索Android稳定性优化

前言 成为一名优秀的Android开发,需要一份完备的知识体系,在这里,让我们一起成长为自己所想的那样~。 众所周知,移动开发已经来到了后半场,为了能够在众多开发者中脱颖而出,我们需要对某一个领域有深入地研究与心得,对于Android开发者来说,目前,有几个好的细分领域值得我们去建立自己的技术壁垒,如下所示: 1、性能优化专家:具备深度性能优化与体系化APM建设的能力。 2、架构师:具有丰富的应用架构设计经验与心得,对Android Framework层与热门三方库的实现原理与架构设计了如指掌。

fishmai的专栏 1557

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

apm性能监控系统,一次关于JVM的面试经历,系列篇_apm链路追踪面试

今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司19年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。还有。

2401_84181193的博客 752

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

apm性能监控系统,靠着这份900多页的PDF面试整理,灵魂拷问

前言 尤其是在最近一段时间内,感觉一天天的时间过得又慢又快,慢的是感觉复工了以后在公司的8.9个小时简直算是煎熬了,快的是常常感觉时间一天天,一月月的过去了,可是发现自己还在原路踏步走。看似每天忙成狗,回头缺发现,月复一月,日复一日,薪资没涨一点,年龄越来越大了。不知道时间都到哪里去了,明明是每天兢兢业业的工作,可是怎么就得不到提升呢?其实是我们的学习效率太低,没有合理的规划而已! 一面(104min) 自我介绍。 线程和进程的区别。 线程安全。面试官追问是否了解volite关键字,小金忘了没回

m0_56228922的博客 215
上一篇: 如何应对 Android 面试官 -> 包体积如何优化?
下一篇: 如何应对 Android 面试官 -> 玩转 JetPack Hilt
老A说
博客等级 码龄14年 1181粉丝 85原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值