仿bugtags实现App线上Crash监控及问题复现

本文介绍了一种基于BugMonitor工具的App线上Crash监控方案,包括收集设备信息、用户操作步骤及现场截图等内容,并通过小米推送与Bmob实现故障上报与热修复流程。

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

仿bugtags实现App线上Crash监控及问题复现

实现线上crash监控,包含了手机堆栈信息和设备信息,及时上传,后台修复并启用推送进行热修复流程,重要的是要手机用户的使用步骤及现场截屏,以便更快的复现问题。

大致的流程如下:

欢迎关注我的公众号:

我们预览下结果:

这是携带设备信息的堆栈信息文件

这是用户的使用步骤文件

这是最终上传到服务端后的文件

这是发生crash时候的现场截屏

下面按照项目的实现过程介绍怎么具体实现发生fc之后及时上传。

1、首先初始化BugMonitor工具BugMonitor.init(this);及小米推送MiPushClient.registerPush(this, APP_ID, APP_KEY);和文件上传的工具Bmob.initialize(this,BMOB_APPID);(其实小米推送和文件上传可以自己后台实现,我是因为个人没有服务器,用的免费推送和文件存储服务,在此谢谢这些sdk公司)

 @Override
    public void onCreate() {
        super.onCreate();
        BugMonitor.init(this);
        Bmob.initialize(this,BMOB_APPID);
        MiPushClient.registerPush(this, APP_ID, APP_KEY);
        LoggerInterface newLogger = new LoggerInterface() {
            @Override
            public void setTag(String tag) {
                // ignore
            }
            @Override
            public void log(String content, Throwable t) {
                Log.d(TAG, "---->>t"+content, t);
            }
            @Override
            public void log(String content) {
                Log.d(TAG, "---->>content"+content);
            }
        };
        Logger.setLogger(this, newLogger);

    }

2、在BugMonitor中初始化CrashHandler,因为安卓中可以通过这个Handler来捕捉crash信息

public static void init(Context context) {
        mContext = context;
        initCrashHandler(context);
    }

    private static void initCrashHandler(Context context) {
        Thread.UncaughtExceptionHandler exceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
        CrashHandler crashHandler = new CrashHandler(context, exceptionHandler);
        Thread.setDefaultUncaughtExceptionHandler(crashHandler);
    }

3、CrashHandler 实现了 Thread.UncaughtExceptionHandler接口,并在uncaughtException方法中获取发生crash时的堆栈信息。

4、当发生crash的时候我们需要做的就是尽量收集有用的信息,下面我们仔细看下onCrash方法。

@Override
    public void onCrash(String crashInfo) {
        Activity activity = ActivityHelper.getActivity();
        long mills = System.currentTimeMillis();
        dirName = TIME_FORMATTER.format(mills);

        Display defaultDisplay = activity.getWindowManager().getDefaultDisplay();
        Point point = new Point();
        defaultDisplay.getSize(point);
        float density = activity.getResources().getDisplayMetrics().density;

        String networkState = NetUtil.getNetworkState(BugMonitor.mContext);
        PackageInfo info = null;
        try {
            info = BugMonitor.mContext.getPackageManager().getPackageInfo(BugMonitor.mContext.getPackageName(), 0);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }

        long freeMemory = MemoryUtil.getFreeMemory();
        long totalMemory = MemoryUtil.getTotalMemory();
        String cpuName = CPUUtil.getCpuName();
        String curCpuFreq = CPUUtil.getCurCpuFreq();
        String maxCpuFreq = CPUUtil.getMaxCpuFreq();
        String minCpuFreq = CPUUtil.getMinCpuFreq();
        int numCores = CPUUtil.getNumCores();



        boolean rooted = SystemInfoUtil.checkSuFile();


        activity.getWindow().getDecorView().setDrawingCacheEnabled(true);
        Bitmap bmp = activity.getWindow().getDecorView().getDrawingCache();
        BitmapHelper.saveBitmap(bmp, dirName);
        final String s = CrashInfo.newInstance()
                .setStackStrings(crashInfo)
                .setScreenResolution(point)
                .setDensity(density)
                .setNetwork(networkState)
                .setPackageInfo(info)
                .setMemory(freeMemory,totalMemory)
                .setCpuInfo(numCores,cpuName,curCpuFreq,maxCpuFreq)
                .setIsRoot(rooted)
                .flushString()
                .toString();
        saveLogToSDCard(s,"crashInfo");
        String step = StepQueueHelper.flushString();
        Log.e(TAG, "---->>step: "+step );
        saveLogToSDCard(step,"step");
        Log.e(TAG, s );

        //1、将文件夹目录写入sp中,再次启动时读出其中的内容
        //2、特殊情况:已启动就fc,根本来不及上传
        Intent intent = new Intent(activity, UploadService.class);
        activity.startService(intent);


    }

5、首先我们获取当前的activity,截下当前界面的图片,并收集收集系统版本,api,屏幕分辨率,屏幕密度,内存情况,网络情况,cpu,是否root过等等信息,并通过一个Builder模式构建出一个String。同时,将用户的使用步骤队列输出为一个String。(这部分是核心,大家可以看代码https://github.com/lantier743865/kso

6、关于使用步骤我们要看一下

package android.com.bugmonitor.base;

import android.com.bugmonitor.collector.BugMonitor;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.MotionEvent;

/**
 * Created by wuxiaolong on 2018/3/9.
 */

public class BaseActivity extends AppCompatActivity {
    private static final String TAG = "BaseActivity";

    @Override
    protected void onResume() {
        super.onResume();
        BugMonitor.onResume(this);
    }

    @Override
    protected void onPause() {
        super.onPause();
        BugMonitor.onPause(this);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        BugMonitor.onDispatchTouchEvent(this,ev);
        return super.dispatchTouchEvent(ev);
    }
}

在这BaseActivity中我们记录了每个界面的进入退出,及事件分发情况。我们重点看事件这部分,因为这部分会找到对应的view的id。

 @Override
    public void onDispatchTouchEvent(Activity activity, MotionEvent event) {
        if(event.getAction() == 0) {
            this.lastX = event.getRawX();
            this.lastY = event.getRawY();
        } else if(event.getAction() == 1) {
            float currentX = event.getRawX();
            float currentY = event.getRawY();
            if(this.lastX == currentX && this.lastY == currentY) {
                View var5 = this.getView(activity.getWindow().getDecorView(), currentX, currentY);
                Log.e(TAG, "---->>var5: "+var5 );
                StepQueueHelper.enqueueStep(activity,var5);
            }
        }
    }

其实,我们在获取到event后,可以通过当前的activity的decorView的位置算出当前是哪个view,这一点我也是从bugtags的源码看出来的,可以混淆过我看了半天才想明白。

public static void enqueueStep(Activity activity, View view) {
        String path  = view.getResources().getResourceName(view.getId());
        String vName = path.substring(path.indexOf("/")+1);
        String format = TIME_FORMATTER.format(System.currentTimeMillis());
        stepBuilder.append(format)
                .append(SPACE)
                .append(activity.getClass().getName())
                .append(SPACE)
                .append(EVENT)
                .append(VIEWID)
                .append(vName)
                .append(SPACE)
                .append(TYPE)
                .append(view.getClass().getName());
        String s = stepBuilder.toString();
        stepQueue.add(s);
        stepBuilder.delete(0,stepBuilder.length());
    }

有了view我们可以通过R文件获取它在我们项目中设置的id名称,这有助于我们找到对应的发生问题的空间。代码中一系列append是为了打印出来的信息格式化更容易分辨。

6、还有关键的一步时应用要挂掉了,怎么把信息传到服务端去?答案是另开一个service(在其他进程中)

Intent intent = new Intent(activity, UploadService.class);
        activity.startService(intent);

关于BugMonitor关键的部分讲完了,剩下的就是代码编写和实现了。

其中upload是一个接口,大家可以用自己的服务器规范去实现,我个人用了bmob的免费存储。大致的目录结构是这样的:

7、我们看下最终的结果:

这是携带设备信息的堆栈信息文件

这是用户的使用步骤文件

这是最终上传到服务端后的文件

这是发生crash时候的现场截屏

最后总结:

  1. 工具是为了发生某些crash之后我们尽可能的多收集现场信息
  2. mipush和bmob是为了流程能够完善,最终我们还需要热修复来善后
  3. 代码地址:![https://github.com/lantier743865/kso]
  4. 写的不好,大家请见谅,以后会继续努力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值