ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Android技术精要与面试攻略:从构建排错到存储适配实战

Android技术精要与面试攻略:从构建排错到存储适配实战 前两天有个朋友发来一条报错截图android studio build 出现tag number over 30 is not supported。他说网上搜了一圈答案都是“资源混淆导致的”可照着关掉混淆还是编不过。我问他是不是用了DataBinding他愣了一下说“对这跟DataBinding有什么关系”——这其实是个很典型的现场很多Android开发的困惑不是业务代码写不出来而是工具链、系统机制、存储适配这些“底层常识”断层了。这篇不打算写成那种“从入门到放弃”的教材我按自己这些年做开发、带团队、当面试官的经验把平时最容易卡住人的环节串一条线环境与真机调试、编译构建排错、核心机制理解、分区存储适配、面试场景题回答思路最后再聊几句蓝牙、WiFi、车机这些“特殊设备”开发时容易踩的坑。标题叫“技术精要与面试全攻略”但其实核心就一句话——把Android工程师真正该具备的工程判断力讲透顺便解决几个热搜里的高频痛点。1. 先把环境搞稳Android Studio、SDK和真机调试的三道“隐形门槛”1.1 版本对应关系别迷信最新版但也别赖在旧版不走很多人一上来就问“Android Studio怎么下载、怎么安装、怎么设置中文”这类问题其实文档都有真正在团队里引发灾难的是版本组合。Gradle、AGPAndroid Gradle Plugin、JDK、SDK Platform四者之间有严格的兼容关系配错了轻则构建警告重则直接编不过。我建议新手记住一组相对稳的组合JDK 17 AGP 8.x Gradle 8.x 编译SDK 34。如果你在维护老项目AGP还在4.x甚至3.6那JDK千万别升到17老老实实留在JDK 11或8。判断依据很简单看项目的build.gradle里com.android.application版本再查官方兼容表。反正我的习惯是“能用稳定版就不追新”尤其AGP大版本刚发布的前几个小版本坑多到让人怀疑人生。还有那个常常被忽视的init.gradle它本质是Gradle的全局初始化脚本很多团队用它来做统一镜像仓库、统一插件版本、统一内存参数。比如在公司网络环境下靠init.gradle把google()和mavenCentral()替换成内部代理比在项目里逐个改repositories高效得多。它的执行优先级最高能直接修改Project的allprojects配置。如果你发现“我明明改了项目的仓库地址下载还是走公司镜像”大概率就是init.gradle在起作用。1.2 SDK下载、汉化与“SDK无法勾选”的真相Android Studio安装教程满天飞但“SDK无法勾选”这个问题值得单独说。SDK Manager里某个版本的SDK Platform显示灰色、无法勾选最常见的原因有三类该版本已经安装过但目录损坏或残留导致Studio识别异常网络代理拦截了SDK下载请求界面看似可用实际列表拉取不完整License未接受安装被拒。排查顺序建议是先到SDK安装目录看platforms文件夹里是不是已有同名版本有就删掉重来然后关掉代理或用cmd下执行sdkmanager --licenses手动接受所有协议最后检查磁盘权限Windows下别把SDK装在C:\Program Files这类受限目录Linux/macOS下注意目录属主。汉化问题反而最简单——直接在Plugins插件市场搜“Chinese (Simplified) Language Pack”安装重启即可。但说实话我建议英文不好的同学也尽量保留英文界面因为报错信息、Stack Overflow答案、官方文档全是英文界面汉化解决不了问题反而会让你在查资料时“翻译对不上号”。1.3 vivo手机无线调试从USB到“无线真机”的正确姿势“如何使用Android Studio无线连接调试vivo手机”这个热搜词说明很多人还在用USB线插拔。Android 11之后无线调试体验已经很成熟了核心流程是这样手机上打开“开发者选项”开启“USB调试”和“无线调试”点击“无线调试”进入二级页选择“使用配对码配对设备”记下IP端口和6位配对码电脑端执行adb pair 192.168.x.x:端口号输入配对码配对成功后执行adb connect 192.168.x.x:端口号注意配对端口和连接端口通常不是同一个别混了。vivo手机上最容易出问题的点有两个一是无线调试开启后如果你拔掉USB线某些系统会默认把调试权限回收需要在“无线调试”页面点“开发调试-允许”之类的授权弹窗二是部分vivo机型在“仅充电”模式下会拦截adb连接必须切到“传输文件”模式走一遍授权。真机调试的体验提升是几何级的谁用谁知道。2. 编译构建链路从“编不过”到“跑得动”的排查真相2.1 “tag number over 30 is not supported”到底是谁报的错回到开头朋友的报错。tag number over 30 is not supported不是Gradle报的也不是Java编译器报的而是AAPT2Android资源打包工具在编译资源阶段抛出的。它背后的机制是这样的AAPT2在解析XML资源时会把每个“可标记的节点”打上tag用于资源索引和增量编译。当某个文件或某个模块生成的tag数量超过30个AAPT2就会拒绝继续处理。什么场景会产生这么多tag最常见的就是DataBinding。DataBinding会在布局XML里为每个绑定表达式生成大量的标记信息如果你把一个超长的布局文件里塞了二十几个{}表达式再叠加include和自定义属性tag数量很容易突破30。另外某些代码生成插件比如ButterKnife的维护版、部分注解处理器也会在R.java生成阶段引入大量tag。完整的排查链路应该是这样的先看完整报错定位到具体模块和具体文件临时关闭DataBinding或ViewBinding重新编译看报错是否消失如果消失说明布局文件太“重”把超大布局拆成多个include或改用Merge减少单文件的绑定表达式数量如果没消失再用二分法排查注释掉一半资源文件看报错是否转移缩小范围到具体资源目录最后检查是否开了资源混淆或shrinkResources如果有先关掉再验证。这位朋友照着“关混淆”做没用就是因为他的项目根本没开混淆报错源头在DataBinding。所以排查问题最忌讳“看到关键字就抄答案”一定要顺着报错链路的源头走。2.2 移植老项目到新StudioSDK目录、构建缓存与License三座山“移植Android Studio项目”这件事看着简单实操中总在重复踩坑。把一个老项目从旧电脑拷到新电脑或者在Git上拉一个新分支打开后第一波报错基本都是SDK路径不对。新版Studio已经能自动检测local.properties里的sdk.dir但如果你把项目放到别的磁盘路径变了就得手动改。第二步是Gradle版本冲突。老项目用的Gradle版本新Studio可能不兼容这时别急着升级Gradle先在gradle-wrapper.properties里保持原版本用新Studio直接打开即可Studio会自己下载对应Gradle。第三步是License问题。新环境执行Gradle同步时如果提示SDK License not accepted不要只在IDE里点“接受”命令行跑一遍sdkmanager --licenses后输入y回车把协议全部接受这是最彻底的方案。构建缓存也是个容易出鬼的地方。如果你改了AGP版本、SDK版本某些旧缓存会导致构建结果“看似成功实际异常”。遇到这种玄学问题直接clean工程再把~/.gradle/caches/下对应模块的缓存删掉基本能解决80%的“改一行代码没反应”问题。2.3 跨端项目里的“工具链连环套”Flutter报VS toolchain的启发热搜里有一条“vs code flutter android 项目报错:unable to find suitable visual studio toolc”这虽然是Flutter的坑但思路对Android开发者同样适用。unable to find suitable Visual Studio toolchain的意思是你的电脑缺C桌面构建工具链。Flutter做Windows桌面端时需要MSVC编译器而很多Android开发只装了Android Studio和JDK没装Visual Studio Build Tools就会在构建Windows目标时卡住。解决办法不是去下载完整VS几个G很劝退装一个“Visual Studio Build Tools”并勾选“使用C的桌面开发”工作负载即可。这件事给Android工程师的启发是现在纯Java/Kotlin的Android岗位正在变少很多项目都是混合栈前端、跨端、后端服务全要碰。你接手一个Spring Boot后端项目能不能直接上手改会看pom.xml、会用mvn package、知道application.yml里改端口这些基础的“跨语言直觉”反而成了区分度。别把自己锁死在Android一个维度。3. Android核心知识体系从组件到AIDL一条主线全部打通3.1 应用启动链路面试官问“Android启动过程”时他在期待什么基础八股文背熟了没用面试官想确认的是你有没有“系统级视野”。Android启动过程的核心链路是开机引导加载Linux内核内核启动第一个用户进程initinit孵化Zygote进程Zygote预加载常用类和资源然后通过socket接收AMS请求来fork出应用进程应用进程创建ActivityThread进入Looper.loop()消息循环经过Application.onCreate和Activity生命周期回调后界面呈现。很多人会忽略“为什么用Zygote fork而不是直接new一个进程”这个问题。答案是fork能复制父进程已加载的类、资源和虚拟机堆大量减少应用启动耗时同时Zygote在开机时就预加载了Framework层常用类新应用进程不需要重复加载。你在项目里测冷启动时间数据好看不好看很大程度取决于Zygote预加载做得是否充分以及你自己Application里是否有多余初始化。3.2 Handler/Looper消息机制主线程为什么不卡“子线程Toast为什么报错”Handler机制是Android面试的“钉子户”但很多人只背了“子线程不能更新UI”这句话从来没想过“为什么不能”。完整链路Looper.loop()是个死循环不断从MessageQueue里取消息取到就交给对应Handler的handleMessage处理。MessageQueue底层用Linux的epoll机制没有消息时线程进入休眠不占用CPU因此主线程的“死循环”不会导致卡顿或ANR。你看到的“主线程不卡”本质是因为系统不断往消息队列里塞“绘制消息”你的代码也往里面塞“业务消息”大家排队执行只要单条消息执行不超时界面就流畅。那“子线程真的完全不能更新UI”吗严格来说ViewRootImpl在创建时会检查checkThread()谁创建的ViewRootImplUI操作就必须在哪个线程。如果某个View在主线程创建子线程动它就会抛CalledFromWrongThreadException。但如果你在子线程里new一个没有attach到Window的自定义View改它属性不报错因为没有ViewRootImpl检查。面试时能细分到这一层才算真理解。子线程Toast为什么经常报错因为Toast需要向系统注册最终通过INotificationManager跨进程调用Handler用的是主线程Looper。你从子线程发Toast内部会post到主线程队列如果主线程Looper没准备好比如在Application.attach之前就会崩溃。所以结论不是“子线程不能弹Toast”而是“别在Looper准备之前弹”。3.3 事件分发机制从源码源码层拆解再到滑动冲突实战事件分发是另一个面试高频我的建议是别看一堆结论直接看三个方法的源码逻辑。触摸事件从Activity.dispatchTouchEvent开始传给ViewGroup的dispatchTouchEventViewGroup先问自己onInterceptTouchEvent要不要拦截不拦截就按顺序分发给子View。子View的dispatchTouchEvent里如果onTouchEvent返回false事件会回传给父级的onTouchEvent。核心顺序dispatchTouchEvent决定事件给谁onInterceptTouchEvent决定自己是否拦截onTouchEvent决定自己是否消费。滑动冲突的三种场景外部滑动方向与内部不一致、方向一致、两层同时滑动本质是“谁该拿到事件”的问题。最简单有效的处理方式外部拦截法在父View的onInterceptTouchEvent里判断滑动方向决定是否拦截或者内部拦截法子View在onTouchEvent里调用requestDisallowInterceptTouchEvent(true)阻止父View抢事件。实际工作中协调布局CoordinatorLayout里的Banner和AppBar联动就是事件分发与嵌套滚动机制的综合应用。你写一个“协调布局Banner”的页面如果Banner轮播的ViewPager2和AppBar的scrollBehavior配合不好典型问题就是滑动时Banner被“吞掉”或者AppBar收起后不弹回。这时要检查Behavior.onNestedScroll的回调是否返回true以及nestedScrollingEnabled是否开启光懂事件分发是不够的还得懂嵌套滚动机制。3.4 AIDL编写步骤与Binder原理不只是背格式AIDLAndroid Interface Definition Language的编写步骤很多人背得滚瓜烂熟但面试官追问“Binder为什么快”就卡壳了。标准步骤其实只有四步定义.aidl接口文件加上包名确定传输的数据类型。自定义类必须实现Parcelable并在aidl文件里声明parcelable标签build后自动生成对应的Stub类在Service的onBind里返回Stub的实现类客户端通过bindService拿到代理调用接口方法。Binder的效率核心在于“一次拷贝”。传统IPC比如Socket、管道通常需要内核空间和用户空间之间两次数据拷贝。Binder通过mmap映射内核缓冲区与接收方用户空间共享同一段物理内存发送方拷贝一次数据到内核缓冲区接收方直接就能读减少了一次拷贝。同时Binder还支持为每个进程分配UID/PID系统可以基于调用方身份做权限校验。我建议面试时答AIDL不要只背定义流程一定带上“你用AIDL解决过什么实际问题”的例子比如音乐播放器的跨进程控制、推送SDK的后台进程通信。没有实战经验的建议自己写个Demo两个App一个提供远程Service另一个bind并调用方法跑通了你就理解了。3.5 性能优化不是玄学布局、内存、卡顿定位的“三板斧”性能优化是面试必问也是工作里最能拉开差距的。布局方面用Layout Inspector看层级树目标是把“视图树深度”控制在10层以内能用ConstraintLayout减少嵌套就别用多层LinearLayout自定义View的onDraw里不要new对象避免在绘制过程触发GC导致掉帧。内存方面常见的坑是Bitmap大图加载对应热搜里的“Android进度条”“android背景”这类问题很多新手喜欢直接用BitmapFactory.decodeFile加载原图一张相机照片几MB加上采样放大直接OOM。正确做法是先用inJustDecodeBounds读图片宽高再按目标View尺寸计算inSampleSize最后才真正解码。有些场景还需要BitmapRegionDecoder做长图分块加载。卡顿定位我推荐两个工具老项目用systrace新项目用Perfetto。抓一段启动过程或滑动过程的trace重点看主线程上有没有超过16ms的“长任务”以及Binder调用是否频繁。很多时候卡顿的根源不是你的代码而是系统服务响应慢比如PackageManager.getInstalledPackages在启动时被调用这种坑没工具是真的查不出来。4. FileProvider与分区存储看懂content://那一长串路径比背八股文有用4.1 contentType那串诡异路径其实是FileProvider的“授权访问地址”热搜里出现了好几条类似的报错比如content://com.tencent.wework.fileprovider/external_path/android/data/com...、content://com.baidu.searchbox.fileprovider/baiddpath/android/data/com.ba。很多人看到这种日志直接懵其实拆开来看每个部分都有含义。以content://com.tencent.wework.fileprovider/external_path/android/data/com.xxx为例content://是ContentProvider的统一Schemecom.tencent.wework.fileprovider是authority在应用Manifest里配置external_path是file_paths.xml里配置的external-path标签的name后面的android/data/com.xxx是文件相对根目录的路径。关键点来了external_path对应的是Environment.getExternalStorageDirectory()也就是/storage/emulated/0/。如果在file_paths.xml里写的是external-path nameexternal path. /那么content://xxx.fileprovider/external/Android/data/...就等于向外暴露了外部存储根目录下的Android/data/...。问题在于Android 11之后/Android/data/目录对绝大多数普通应用是不可访问的即使通过FileProvider授权接收方也不一定有权限读取。这就是为什么分享文件时对方打开会发现“文件不存在”。4.2 FileProvider配置的“正确姿势”分场景选用不同的路径根节点FileProvider的file_paths.xml提供了几个根节点各自对应不同的真实目录记住这张表配置名对应目录典型场景files-pathgetFilesDir()即/data/data/包名/files/私有文件共享最推荐cache-pathgetCacheDir()即/data/data/包名/cache/缓存文件临时共享external-pathEnvironment.getExternalStorageDirectory()即/storage/emulated/0/外部存储根目录慎用external-files-pathgetExternalFilesDir()即/storage/emulated/0/Android/data/包名/files/外部私有目录微信接收文件常用external-cache-pathgetExternalCacheDir()即/storage/emulated/0/Android/data/包名/cache/外部缓存目录实战里最稳的做法是把要分享的文件先复制到自己的getExternalFilesDir()或getCacheDir()再通过FileProvider授权给第三方App。不要试图直接分享/Android/data/其他应用/下的文件——那大概率是别人应用的私有目录你没有权限读取对方也不该被授权读。4.3 从FileUriExposedException到分区存储一次适配踩坑复盘我之前做个社交类App用户在相册选图分享到微信/QQ/企业微信/百度贴吧iOS一切正常Android这边收到一堆“无法打开文件”的反馈。排查过程是这样的先看日志发现抛的是FileUriExposedException原因是从Android 7.0开始应用之间不能再用file://共享文件必须走FileProvider。当时我很快改成了FileProvider但同事反馈“自己App内看没问题一分享到微信就空白”。继续打日志发现FileProvider生成的URI是content://.../external_path/storage/emulated/0/Android/data/包名/...微信拿不到该文件的读取权限。最终方案改成了两件事把file_paths.xml里的external-path改为external-files-path并指定path.让URI路径对应到本应用的外部私有目录分享前调用FileProvider.getUriForFile拿到content URI同时给Intent加上FLAG_GRANT_READ_URI_PERMISSION并确保manifest里FileProvider声明了grantUriPermissionstrue。这样改完之后微信、QQ、企业微信、百度、钉钉全部能正常打开。核心逻辑很简单不要试图拿别人的私有文件也不要让别人拿你的私有文件原始路径FileProvider给你一个“临时授权凭证”你要做的是在凭证有效期内把文件内容提供出去。如果你在适配Android 11/12之后还遇到媒体文件扫描不到大概率是因为强制分区存储导致Environment.getExternalStoragePublicDirectory直接操作受限。媒体类文件建议走MediaStore插入数据库再拿uri读取文档类文件建议用SAFStorage Access Framework让用户挑目录。这两种方式跨版本兼容性最好别再硬扛WRITE_EXTERNAL_STORAGE权限了。5. 面试里的场景题如何把一个普通需求答出工程深度5.1 一套通用答题框架先澄清再设计后验证作为面试官我见过太多候选人上来就答“用Glide加载图片”然后没有然后了。场景题考察的不是你知道哪个库而是工程思维。我建议按四步走需求澄清先问清楚数据量级、机型范围、网络环境、有没有弱网降级要求技术选型说明为什么选A不选B给出对比关键实现画出核心链路说清楚读写、缓存、线程调度边界与验证讲内存、电量、弱网下的降级以及用什么工具验证。比如问到“相册选图上传多张大图导致OOM怎么解决”初级候选人答“压缩图片”中级答“采样率内存缓存”但高级的回答会包含完整链路——先告知用户最多选9张按屏幕尺寸计算采样率上传队列用线程池限制并发3个上传完用LruCache做缩略图缓存列表滑动时暂停上传任务最后用Profiler验证内存波动。同样一道题差别就在这四步。5.2 “聊天列表怎么做才流畅”RecyclerView之外的事另一个面试官特别爱问的是“聊天列表卡顿怎么排查和优化”。这题表面考UI优化实际考整体工程素养。基础答案是复用RecyclerView、使用DiffUtil只刷新变化项、图片用Glide并设置缓存策略。但聊天列表的特殊性在于消息时间长、条目类型多文本/图片/语音/视频、可能还有撤回和已读未读状态。更进一步的优化包括消息数据分页加载不要一次性把一个月的历史记录塞进内存图片用ConcatAdapter分类型或继承ListItem实现多类型复用避免用getItemViewType里的if-else地狱用OnPreDrawListener做首帧优化启动时先显示“正在加载”数据到位后再渲染把inflate布局放到后台线程预创建ViewHolder借助AsyncLayoutInflater。面试官追问“如果用户手机很老怎么办”你要能接住“降低图片质量、减少过度绘制、关闭动画、限制最大加载数量”这类降级方案。5.3 “进程被杀后怎么恢复状态”把Binder与生命周期连起来高级一点的场景题喜欢考“进程被杀后的状态恢复”。这个问题背后考的是Android进程管理机制。Android把进程分成五级前台进程、可见进程、服务进程、后台进程、空进程。系统内存不足时从低到高杀。你应用在后台被杀了用户切回来系统会重建Activity通过onSaveInstanceState恢复View状态但业务数据不一定还在内存里。所以正解是关键业务数据要持久化到本地数据库或DataStoreonSaveInstanceState只存轻量级UI状态Fragment同样有自己的onSaveInstanceState如果业务需要用WorkManager做异步任务的重试而不是自己起Service硬撑。能把这个“系统杀进程—恢复—重新拉数据”的完整链路讲清楚说明你是真的理解Android应用运行机制的。最近面试还经常聊到“Agent智能体开发工程师”相关的延伸题比如“在App里集成一个端侧大模型Agent需要做哪些工程准备”。面试官想听的其实不是模型本身而是工程化能力流式输出的UI刷新、多轮对话的记忆管理、弱网环境下的超时和重试、token耗量监控。核心思路还是那套——先澄清需求边界再做技术设计最后谈降级和验证。6. 特殊设备与边缘场景蓝牙、WiFi、车机存储监听的实战细节6.1 蓝牙开发权限变化与Gatt回调的“线程陷阱”蓝牙权限从Android 12开始变化很大旧代码很容易在12及以上设备崩溃。API 31之后扫描需要BLUETOOTH_SCAN连接需要BLUETOOTH_CONNECT这两个都是运行时权限还要在Manifest里声明BLUETOOTH_SCAN、BLUETOOTH_ADVERTISE、BLUETOOTH_CONNECT并且记得在Android 13上声明neverForLocationtrue如果不涉及定位。经典蓝牙与BLE的使用差异也很大经典蓝牙要配对、连接流程复杂BLE走GATT协议适合低功耗数据交换但连接不稳定回调频繁。最容易踩的坑是把BluetoothGattCallback里收到数据后直接更新UI结果有些机型回调线程不在主线程直接崩溃或界面不刷新。正确做法是收到数据后post到主线程Handler或者用协程的withContext(Dispatchers.Main)切线程。另外Gatt连接不能频繁connect/disconnect有些设备固件会直接挂掉需要做重连退避策略。6.2 WiFi强度测试权限链比想象中长“Android wifi强度测试”这个需求做起来比想象中烦因为WiFi扫描结果不是你想拿就能拿到的。完整权限链是Android 8.0以上需要ACCESS_FINE_LOCATION或ACCESS_COARSE_LOCATIONAndroid 13细化为NEARBY_WIFI_DEVICES扫描WiFi需要同时还得打开位置服务。也就是说即使用户的App有定位权限系统定位开关没开startScan()返回的结果也可能为空或系统广播扫描完成的SCAN_RESULTS_AVAILABLE_ACTION不回调。拿到ScanResult之后每个AP的level字段就是信号强度单位dBm范围通常在-40极强到-100极弱。要做曲线展示建议每秒采样一次存到本地数据库或DataStore再通过图表库绘制。注意扫描有频率限制每30秒只能发起有限次扫描太频繁系统会忽略请求或直接回调失败。6.3 车机与存储监听Android私有目录在车载场景的“另一个坑”“android 车载监听存储空间的变化”这个热搜词很有代表性。车机应用和手机App的差异很大存储介质可能是eMMC或UFS剩余空间变化频繁系统可能长时间不休眠网络可能在车辆启动后才建立连接。监听外部存储插拔和空间变化官方API是StorageManager.registerListener可以监听ACTION_MEDIA_REMOVED、ACTION_MEDIA_MOUNTED、ACTION_MEDIA_EJECT等广播。但车机上更常见的问题是U盘播放视频文件用户拔U盘时应用还在读文件直接crash。这里涉及一个非常容易被忽视的文件路径细节——android.process.acore。这是Android系统里“联系人存储”的进程名偶尔会有第三方App把大文件放到/data/data/下乱写导致系统进程崩溃。很多车机工程师排查诡异Bug时发现存储空间被占满原因就是某个应用往getFilesDir()里塞了几十G日志。所以做车机开发一定要监听存储变化并且在写入前检查剩余空间给用户明确的“空间不足”提示。提到LVGL这类嵌入式UI框架很多Android开发者觉得八竿子打不着但我建议把它作为“交叉视野”了解下。车载仪表盘、智能家居屏幕上跑LinuxLVGL的场景越来越多面试时你随口说出LVGL的控件树和脏矩形渲染机制面试官会对你另眼相看。Android经历积累的界面架构思维、布局层次思考迁移到LVGL上并不难关键是愿不愿意跨出舒适区。安卓系统更新迭代快框架、权限、存储策略几乎每年都在变。我个人的体会是与其焦虑今天又出了什么新框架不如把基础概念吃透——事件分发、消息机制、Binder、进程生命周期这些东西十年没大变掌握了就能以不变应万变。面试背题只能帮你拿到入场券真正让你在实战和面试中站稳脚跟的是把一条条报错背后的系统链路搞清楚的能力。就像那个tag number over 30知道它为什么报错、怎么排查、怎么避免比记住二十个面试答案有用得多。最后再分享一个小习惯每次排查完一个玄学问题都花二十分钟写一段排查笔记包括现象、定位过程、根因、修复措施。坚持半年你就是团队里那个“什么都见过”的人。
返回列表