Android异常crash监听

android进阶篇11、crash监控与anr分析简述 anr全称application not responding,应用程序无响应,是通过AMS检测的,分为四种类型KeyDispatchTimeout 输入事件无响应,超时时间5sBroadcastTimeout 广播超时,前台广播10s,后台广播60sServiceTimeout 服务响应超时,前台服务20s,后台服务200sContentProviderTimeout 内容提供器超时,10s内没有处理完触发。 阅读详情

背景

目前做的项目为座舱app,与智驾交互很多。如果遇到异常crash退出,会导致智驾域控的状态机不对,无法重置。所以要解决当前问题,需要在APP崩溃的时候发送重置信号,解决状态机不同步问题。

方案

使用CrashHandle

public class CrashHandler implements Thread.UncaughtExceptionHandler {

    private static final String TAG = "APA-CrashHandler";
    private static final String INT_CRASH_TIME = "int_crash_time";
    private static final String DEAD_SYSTEM_EXCEPTION = "DeadSystemException";
    private static final String DEAD_OBJECT_EXCEPTION = "DeadObjectException";

    private static final int MAX_CRASH_TIME = 3;

    private static CrashHandler instance;
    private Context mContext;
    private Thread.UncaughtExceptionHandler mOriginalHandler;

    public static CrashHandler getInstance() {
        if (instance == null) {
            instance = new CrashHandler();
        }
        return instance;
    }

    /**
     * 初始化
     */
    public void init(Context context) {
        mContext = context;
        mOriginalHandler = Thread.getDefaultUncaughtExceptionHandler();

        Thread.setDefaultUncaughtExceptionHandler(this);
    }

    /**
     * 把崩溃信息放在sd卡缓存目录下,若没有sd卡,就放在data/data中的缓存目录下
     *
     * <p>SDCard/Android/data/你的应用包名/cache/<p/>
     */
    private static File getDiskCrashDir(Context context, String dirName) {
        String cachePath;
        if (context.getExternalCacheDir() != null) {
            cachePath = context.getExternalCacheDir().getPath();
        } else {
            cachePath = context.getFilesDir().getPath();
        }
        return new File(cachePath + File.separator + dirName);
    }

    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        try {
            DateFormat formatter = new SimpleDateFormat("MM.dd@HH.mm", Locale.CHINA);
            String time = formatter.format(new Date());
            Log.e(TAG, "APA_CRASH  time : " + time + " " + ex.toString());
            StackTraceElement[] stackTrace = ex.getStackTrace();
            for (int i = 0; i < stackTrace.length; i++) {
                Log.e(TAG, "APA_CRASH  stackTraceElement ---<" + i+">---" + stackTrace[i].toString());
            }
            handleCrash();
            dealwithUnity(ex);
            if (BuildConfig.DEBUG) {
                saveCrash2File(ex);
            }
            uploadExceptionToService(ex);
            //发送崩溃广播通知其他APP
            MyUtils.sendApaExitBroadcast(BaseConfigApplication.getApplication());
            //  off
            MyUtils.STATUS_APA_LIFE = STATUS_APA_OFF;
            //处理其他特殊情况
            CarConnectionHelper mCarHelper = CarConnectionHelper.getInstance();
            if (mCarHelper != null) {
                if (mCarHelper.isConnected()) {
                    int avmSts = mCarHelper.getIntProperty(HzVehiclePropertyIds.HZ_AVM_STATUS, AREA_DEFAULT, -1);
                    LogUtils.i(TAG, "crash apa fail : avmSts : "+avmSts);
                    if (avmSts == 6) {
                    	//发送信号同步给智驾
                        Log.d(TAG, "avmSts == 6  need  aidl seed off");
                        mCarHelper.sendCanMsg(HzVehiclePropertyIds.HZ_HPA_ON, AppConstants.AREA_DEFAULT, 2);
                        Thread.sleep(100);
                        //发送信号同步给智驾 ,信号属于同一组,防止中间件没收到
                        mCarHelper.sendCanMsg(HzVehiclePropertyIds.HZ_APA_MODE_SET, AppConstants.AREA_DEFAULT, 2);
                    }
                }
            }
            if (isInterruptException(ex)) {
                // 异常逻辑 1: 继续执行,进程不结束
                resumeMainThreadLoop();
                //异常逻辑 2: 重启
//                restartApp(mContext);

                return;
            }
        } catch (Exception e) {
            Log.e("uncaughtException又抛出的异常", ex.toString());
        }


        mOriginalHandler.uncaughtException(thread, ex);
    }

    private void dealwithUnity(Throwable ex) {
        //暂不处理了,全部有远程服务处理
//        String exError = ex.toString();
//        if(exError.contains("FATAL EXCEPTION [UnityMain]")){
//            Log.e(TAG,"dealwithUnity");
//            FilePermissionHelper.fixUpDirectoryPermission(FilePermissionHelper.unityFilePath);
//        }

    }


    private  boolean isInterruptException(Throwable e){
        if (e.toString().contains(DEAD_SYSTEM_EXCEPTION) || e.toString().contains(DEAD_OBJECT_EXCEPTION)){
            //拦截DeadSystemException
            return true;
        }
        return false;
    }



    /**
     * 重启服务
     * @param context the context
     */
    public void restartApp(Context context){
        // 重新启动应用程序或者系统服务
        try {
            Log.d(TAG, "Restarting");
            final Intent intent = new Intent();
            intent.setClassName("com.hozonauto.panoramic","com.hozonauto.panoramic.service.AIDLService");
//        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
            PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0);
            AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
            alarmManager.set(AlarmManager.RTC, System.currentTimeMillis() + 1000, pendingIntent);
        } catch (Exception e) {
            Log.e("uncaughtException restartApp crash", e.toString());
            e.printStackTrace();
        }
        // 退出应用程序或者停止服务
        System.exit(0);
    }
    public void resumeMainThreadLoop() {
        if (Looper.myLooper() != Looper.getMainLooper()) {
            return;
        }
        try {
            Looper.loop();
        } catch (Exception e) {
            Log.e("uncaughtException resumeMainThreadLoop crash", e.toString());
            uncaughtException(Thread.currentThread(), e);
        }
    }


    private void uploadExceptionToService(Throwable ex) {
        //上传操作
        // 一定要获取一些硬件信息(在不侵犯隐私的前提下)
    }

    private void handleCrash() {
//        if (MyApplication.getInstance() != null) {
//            //elapsedTime 这个是 系统启动后到发送崩溃的时间
//            final long elapsedTime = SystemClock.elapsedRealtime() - MyApplication.getInstance().getStartTime();
//            Log.e(TAG, "elapsedTime:" + elapsedTime);
//            //如果在启动后10s内就崩溃了,那就需要清除缓存了,很大概率就是缓存出现了脏数据,导致启动读取缓存数据就崩溃了。
//            if (elapsedTime < 10 * 1000) {
//                int time = (Integer) SPUtil.get(MyApplication.getInstance(),"app",INT_CRASH_TIME, 0);
//                time += 1;
//                Log.e(TAG, "崩溃次数" + time);
//                if (time >= MAX_CRASH_TIME) {
//                    Log.e(TAG, "崩溃次数达到上限,清除缓存");
//                    //reset
//                    SPUtil.put(MyApplication.getInstance(),"app",INT_CRASH_TIME, 0);
//                    //到达上限 清除缓存相关数据
//                    CacheUtil.clean();
//                } else {
//                    SPUtil.put(MyApplication.getInstance(),"app",INT_CRASH_TIME, time);
//                }
//            }
//        } else {
//            Log.e(TAG, "获取不到context");
//        }
    }

    /**
     * 保存错误信息到文件中
     *
     * @param ex 崩溃信息
     * @throws IOException
     */
    public void saveCrash2File(Throwable ex) throws IOException {

//        StringBuilder sb = new StringBuilder();
//        Writer writer = new StringWriter();
//        PrintWriter printWriter = new PrintWriter(writer);
//        ex.printStackTrace(printWriter);
//        Throwable cause = ex.getCause(); // 返回此异常的原因(尝试加载类时发生错误引发的异常;否则返回 null)
//        while (cause != null) {
//            cause.printStackTrace(printWriter);
//            cause = cause.getCause();
//        }
//        printWriter.close();
//        String result = writer.toString();
//        sb.append(result);
//        try {
//            // 用于格式化日期,作为日志文件名的一部分
//            DateFormat formatter = new SimpleDateFormat("MM.dd@HH.mm", Locale.CHINA);
//            String time = formatter.format(new Date());
//            String fileName = time + ".log";
//            File dir = getDiskCrashDir(mContext, "crash");
//            if (!dir.exists()) {
//                if (!dir.mkdirs()) {
//                    return;
//                }
//            }
//            String filePath = dir.getAbsolutePath() + File.separator + fileName;
//            FileOutputStream fos = new FileOutputStream(filePath);
//            fos.write(sb.toString().getBytes());
//            fos.close();
//        } catch (IOException e) {
//            Log.e(TAG,e.toString());
//        }
    }

}

使用远程服务

可以通过绑定服务来监听主进程是否死亡,IBinder.DeathRecipient来处理死亡监听。


/**
 * @author hj
 */
public class AIDLAliveHelp {


    private static final String TAG = AIDLService.class.getSimpleName();
    private final Context mContext;

    public AIDLAliveHelp(Context context) {
        mContext = context;
    }


    @RequiresApi(api = Build.VERSION_CODES.R)
    public void bindAliveService() {
        try {
            LogUtils.d(TAG, "bindAliveService");
            Intent intent = new Intent();
            ComponentName component;
            component = new ComponentName("com.hozonauto.panoramic",
                    "com.hozonauto.panoramic.service.ApaAliveService");

            intent.setComponent(component);
            mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
        } catch (Exception e) {
            Log.e(TAG, "Exception creating of bindAliveService : " + e.getMessage());
        }
    }


    private IAliveInterface mIAliveInterface;
    ServiceConnection mConnection = new ServiceConnection() {

        @Override
        public void onServiceDisconnected(ComponentName name) {
            LogUtils.i(TAG, "AIDLAliveHelp----onServiceDisconnected");
            mIAliveInterface = null;

        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            LogUtils.i(TAG, "AIDLAliveHelp---onServiceConnected");
            mIAliveInterface = IAliveInterface.Stub.asInterface(service);
            try {
                if (service != null) {
                    service.linkToDeath(mDeathRecipient, 0);
                }

            } catch (Exception e) {
                Log.e(TAG, "AIDLAliveHelp---onServiceConnected has error : " + e.getMessage());
            }
        }
    };

    private final IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            LogUtils.d(TAG, "AIDLAliveHelp---IAliveInterface is died");
            try {
                mContext.unbindService(mConnection);
                CarConnectionHelper mCarHelper = CarConnectionHelper.getInstance();
                if (mCarHelper != null) {
                    if (mCarHelper.isConnected()) {
                        int avmSts = mCarHelper.getIntProperty(HzVehiclePropertyIds.HZ_AVM_STATUS, AREA_DEFAULT, -1);
                        LogUtils.w(TAG, "main app binderDied : avmSts : " + avmSts);
                        if (avmSts == 6) {
                            LogUtils.i(TAG, "avmSts == 6 need to send off mode");
                            mCarHelper.sendCanMsg(HzVehiclePropertyIds.HZ_APA_MODE_SET, AppConstants.AREA_DEFAULT, 2);

                        }
                    }
                }
                MyUtils.sendApaExitBroadcast(BaseConfigApplication.getApplication());

                ThreadUtils.getMainHandler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        boolean system = FilePermissionHelper.checkDirectoryOwner(FilePermissionHelper.unityFilePath, "system");
                        LogUtils.w(TAG, "main app binderDied : unity files system "+ system);
                        FilePermissionHelper.executeError();
                    }
                },1000);
            } catch (Exception e) {
                LogUtils.e(TAG, "IAliveInterface binderDied has error :" + e.getMessage());
                e.printStackTrace();
            }
            mIAliveInterface = null;

        }
    };

    public void unbindService() {
        if (mContext != null) {
            try {
                MyApplication.getApplication().unbindService(mConnection);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

在远程服务里调用该工具类:

public class AIDLService extends Service {
	 @Override
    public void onCreate() {
        super.onCreate();
		.......

        mAidlAliveHelp = new AIDLAliveHelp(this);

		.......
    }

  	@RequiresApi(api = Build.VERSION_CODES.R)
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        LogUtils.i(TAG, "------ onStartCommand() -----");
        // if (intent != null && intent.getAction() != null) { }
        if (mAidlAliveHelp != null) {
            mAidlAliveHelp.bindAliveService();
        }
        return START_STICKY;
    }

 	@Override
    public void onDestroy() {
        if (mAidlAliveHelp != null) {
            mAidlAliveHelp.unbindService();
        }
        super.onDestroy();
    }


}

AidlService配置:独立的远程进程。

<service
            android:name=".service.AIDLService"
            android:enabled="true"
            android:exported="true"
            android:permission="com.hozonauto.panoramic.permission.REMOTE_CONTROL"
            android:process=":remote" />

综上:2个方案并行,可以解决应用进程Crash或者底层Crash的兜底方案,防止异常暴露。

Android代码-CrashMonitor 监听程序崩溃日志 MNCrashMonitor MNCrashMonitor 监听程序崩溃日志,直接页面展示崩溃日志列表,调试方便,测试人员可以随时给程序猿查看日志详情,可以动态添加日志内容,手机直接查看日志内容可以分享,复制,生成长截图,高亮显示。 截图 如何添加 Gradle添加: 1.在Project的build.gradle中添加仓库地址 allprojects { repositories { ... maven { url "https://jitpack.io" } } } 2.在app目录下的build.gradle中添加依赖 dependencies { (support 包版本随意) compile 'com.android.support:appcompat-v7:26.1.0' compile 'com.android.support:recyclerview-v7:26.1.0' compil 立即下载

相关推荐

phpstudy自定义安装mysql8.3并启动

下载压缩包文件然后按下面的图片一步一步操作选择版本,选择第一个压缩包文件,下载下载完成后,解压到phpstudy环境目录下

互联网碎碎念 2802

Android性能优化—Crash监控方案

Overridetry {= null) {//私有目录,无需权限if (!f.mkdirs();//写入crash堆栈pw.flush();pw.close();//App版本//Android版本号//手机制造商//手机型号//CPU架构} else {

sziitjin的博客 1812

WinCC与PLC在西门子逻辑赛项中的通信配置避坑指南

本文针对西门子逻辑赛项中WinCC与PLC通信配置的常见问题,提供了一套实战避坑指南。重点解析了WinCC、PLC与EET仿真平台的三角通信架构,强调了IP地址规划、防火墙设置等关键环节的注意事项,并给出了系统化的故障诊断流程与排查技巧,帮助选手和技术人员快速定位并解决通信故障,确保监控系统稳定运行。

weixin_29165725的博客 107

Android Crash监控方案

Crash(应用崩溃)是由于代码异常而导致 App 非正常退出,导致应用程序无法继续使用,所有工作都停止的现象。发生 Crash 后需要重新启动应用(有些情况会自动重启),而且不管应用在开发阶段做得多么优秀,也无法避免 Crash 发生,特别是在 Android 系统中,系统碎片化严重、各 ROM 之间的差异,甚至系统Bug,都可能会导致Crash的发生。 在 Android 应用中发生的 Crash 有两种类型,Java 层的 Crash 和 Native 层 Crash。这两种Crash 的监控和获取堆

yzpbright的博客 1513

房产平台系统小程序源码

🏠 一键解锁购房新体验 🔓房产小程序 实现前端 发布二手房 租房 商品 求租 求购等信息;后台发布 新房,二手房租房 商品 写字楼 求租求购等房源信息;功能完善的一块房产小程序。

2401_84413757的博客 796

DDR5内存Training全解析:从信号完整性到CS训练模式的实战指南

本文深入解析了DDR5内存Training的全过程,从信号完整性挑战到CS训练模式(CSTM)的实战应用。文章详细阐述了Training在DDR5高频时代的关键作用,对比了In-Band与OOB两种架构,并拆解了Intel MRC的核心训练步骤,为硬件与固件工程师提供了诊断与优化系统稳定性的实用指南。

weixin_29252171的博客 1031

Android 性能优化系列:崩溃原因及捕获

java crash 由 JVM 触发处理,最终走到 /data/system/dropbox 目录用文件保存native crash 由管道通信建立 socket 接收通知,最终走到 /data/system/dropbox 目录用文件保存anr 由多种情况(事件、前后台服务)触发器处理,最终走到 /data/system/dropbox 目录用文件保存所有的 crash 处理在 Android 系统内部都会将对应的数据收集到 /data/system/dropbox 目录下。

Vincent的博客 3138

基于 MATLAB 的精馏塔模拟仿真

基于 MATLAB 的精馏塔模拟仿真精馏塔是一种重要的化工设备,其中乙烯精馏塔和醋酸乙烯精馏塔是常见的两种精馏塔。本文基于 MATLAB 软件,给出了针对这两种精馏塔的模拟仿真代码。

与其临渊羡鱼,不如退而结网 904

Java / Android 监控进程Crash异常

在Android/Java开发中经常遇到的就是程序突然崩溃了,这中崩溃是如何被捕获并且打印到控制面板的呢。抛出异常之后app就崩溃了,而不是整个系统崩溃是为什么?今天就从Android的角度来看看一个Android系统是如何崩溃的。 1.异常监控的注册 从Android系统启动流程知道,systemserver和每个app进程的启动都是通过Zygote进程孵化的。从Launcher桌面点击启动一个未启动过的app时,就会触发zygote孵化一个新的进程,从这里开始扯出系统对crash的处理。对于进程的创

王项雨 的博客 2209

6、探索音乐结构中的统一性:贝多芬《英雄交响曲》第一乐章的深度剖析

本文深入剖析了贝多芬《英雄交响曲》第一乐章的音乐结构,探讨其统一性的来源及表现形式。通过主题与动机、和声与调性、旋律与节奏、织体与动态等多方面的分析,揭示了这部作品在技术层面和情感表达上的卓越成就。

b9c0d的博客 518

UE4动作游戏实例RPG Action解析二:GAS系统播放武器绑定的技能,以及GE效果

官方实例激活技能通过装备系统数据激活,我先用武器数据资产直接激活官方实例蒙太奇播放是自定义的AbilityTask,我先用更简单的方法实现效果1.1.1 插件启用AbilitySystem1.1.2 PlayerCharacter绑定技能组件AbilitySystemComponent 1.1.3 技能初始化,AbilitySystemComponent->GiveAbility 把上节获取到的默认武器绑定的技能GameAbility 添加到ASC组件 1.2、创建一个GameplayAbili

yuchenwuhen的专栏 1281

Android跨进程通信,binder传输数据过大导致客户端APP,Crash,异常捕获,监听异常的数值临界值,提前Hook拦截,共享内存实现传输

反射换掉IActivityTaskManager对象的IBinder对象,IBinder有监控的transact方法。思路,先启动一个注册的代理的Activity,然后绕过manifest的检测后,把代理的Activity替换成未注册的。IActivityTaskManager$Stub是binder服务端的类,运行在system进程的。hook拦截startActivity的方法,到达一个危险的大小容易崩溃的时候,我们就上报。invoke方法中,关注transact方法获得跨进程传输的参数的大小。

weixin_46039528的博客 1703

【QT开发教程】Qt中的数据加密与解密

在现代应用程序中,数据加密与解密是保护敏感信息的重要手段。Qt本身并不直接提供数据加密与解密的支持,但可以使用第三方库,如OpenSSL或Crypto++,来实现这一功能。本文将介绍如何在Qt中使用OpenSSL进行数据加密与解密,包括基本概念、使用方法以及一些实际的示例。

科技改变人类,技术成就未来 1157

SpiderMan Crash回调功能详解:如何保存与分析异常数据

SpiderMan是一款专为Android开发者和测试人员打造的崩溃日志手机端显示工具,被开发者亲切地称为"测试妹妹的最爱,开发哥哥的小棉袄"。本文将详细介绍SpiderMan的Crash回调功能,帮助开发者轻松实现异常数据的保存与分析,提升应用稳定性。 ## 什么是SpiderMan Crash回调功能? SpiderMan的Crash回调功能是指当应用发生崩溃时,开发者可以通过注册回调接口

gitblog_00341的博客 401

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

APM系列之二-原理篇-监听整个系统的anr和crash

本篇是APM系列文章的第二篇,主要介绍如何通过一个第三方应用,去监控整个系统中的异常事件,比如Crash或者anr。

分享+记录 825

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

android 保存crash log到本地

做车机项目,有个需求是获取在客户手中的车机运行错误信息,百度了一下基本都是实现Thread.UncaughtExceptionHandler这个类,这个方法只是获取当前进程也就是当前应用的crash信息,车机那么多应用,不可能每个应用都写一遍,所以只能从系统入手了。 当应用出现crash会弹出一个弹框,frameworks/services/core/java/com/android/serve...

qq_34694875的博客 801

WINCC7.3破解文件

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

Android笔记(18)应用层Crash处理逻辑

Android发生Crash时,说明代码执行出现异常,此时的运行时环境(RunTime)将会首先感知到问题。如果此时代码设置了try-catch,将这个运行时异常RunTimeException(更加具体的可能是NPE or 数组越界等)捕获后,将不会触发运行时环境(Runtime)触发crash机制。如果一旦没有处理,将会出现Crash!!!下面我们来探讨下应用层最终崩溃的机制 RuntimeI...

u014529755的专栏 460

android 模拟crash_Android crash 收集

Android Crash在开发中,会遇到crash问题,一般来说,crash发生在java层,但是,有时候会发生在其他层面上。大致,Android Crash 大致有三类:Java uncatch exceptionANR crashNative crashJava全局异常处理通过Thread.setDefaultUncaughtExceptionHandler我们可以指定一个默认的全局异常处理...

weixin_39849254的博客 355

友盟(软件异常实时监听的好帮手:Crash)接入教程(有点基础的小白最易学的教程)

友盟(软件异常实时监听的好帮手:Crash)接入教程(小白的你快来入手吧)

qq_37567841的博客 1714

​库的使用之Android-crash

【代码】​库的使用之Android-crash。

tonyyxm2011的博客 233

一种捕获ANR和Crash相关信息的解决方案

一种捕获ANR和Crash相关信息的解决方案 在应用开发过程中,当手机出货后应用使用过程中出现的一些比较严重的问题(如crash、anr)开发人员无法得知,从而无法进行相关的维护。下面给出一种常用解决方案: 1.应用层crash捕获方案 对于crash异常比较好处理,在java语言中可以通过实现UncaughtExceptionHandler去自定义处理未被应用自身catch的excep...

koma_mj的博客 2558

Android U 常驻应用崩溃导致进入recovery过程--记录

在一小时内,persistent 发生5次crash 会进入 Recovery模式.

huibin147852369的专栏 1181

Android 7.0 ActivityManagerService(10) App的crash处理

在这篇博客中,我们来看一下AMS处理App crash时涉及到的主要流程。

橡果生长记的博客 4891

关于Android如何对应用进行Java Crash监控

问题 android开发或者运行过程中难免会出现Crash,如何持久化崩溃日志便于重新定位 知识点补充 Java中的Thread定义了一个接口UncaughtExceptionHandler用于处理未捕获的异常导致线程的终止(值得注意:已经被捕获的异常是捕获不到的)。 当应用crash的时候,就会执行UncaughtExceptionHandler.uncaughtException方法 ,该方法可以获取到异常的信息。 通过Thread.setDefaultUncaughtExceptionHandler

qq_22255311的博客 373

Android 全局异常捕获 CrashHandler

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_33753845的博客 107
上一篇: Android 启动速度优化
HUandroid
博客等级 码龄10年 35粉丝 113原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值