本文主要描述了怎么样提高一个客户端开发排查和定位的效率,并且动手写了一个小工具的实践和思考,以及团队中其他合作者能够提高了定位问题效率,验证功能是否准确的效率。
作者/马杰 中国大学 MOOC 团队
编辑/刘振宇
中国大学 MOOC 是由网易与高教社携手推出的在线教育平台,承接教育部国家精品开放课程任务,向大众提供中国知名高校的 MOOC 课程。目前,无论是课程数量、质量还是社会影响力,中国大学 MOOC 都已成为全球领先的中文慕课平台。
在日常的 Android 开发中,我们经常会遇到以下的一些问题:测试、运营、产品同学跑过来说这个页面出了问题,赶紧看下。这时候客户端开发同学就需要赶紧定位到具体的某个页面。
据观察,大部分的情况下对于一个突发页面的问题定位,或者业务方想让开发者确认这个页面的业务逻辑的时候,客户端开发者,往往需要花费比较长的时间去给业务方答复。如果近期业务可能还能记得,但是客户端的页面比较多,想要快速定位到具体业务页面,那么就需要花更多的时间去找相关的页面。
所以本文的想法是怎么快速找到对应的页面,帮助开发快速的进入业务代码,快速的回复业务方提出的问题。
在探讨方案的时候,我们需要对比目前有哪些方案,对比之后再选择一种更加有效的方法。
在 Android 开发中解决上述提供的问题,常用的有以下 3 种方式:
// windows
adb shell dumpsys window windows | Select-String -Pattern 'mCurrentFocus|mFocusedApp|mLastOpeningApp|mObscuringWindow'
// Mac
adb shell dumpsys window windows | grep -E 'mCurrentFocus|mFocusedApp|mLastOpeningApp'
上面 3 种方式是能够解决问题,但从时间效率上分析,可以估算一下每一种方式大概需要多长时间:
第一种,按照个人经验,熟悉项目代码的同学最快也要几十秒左右,慢的话 10 几分钟;
第二种,使用 adb 能够几秒就定位到页面,但是需要记住命令,或者提前设置命令快捷方式;
第三种,如果有很多相同文案,需要多搜索几遍,时间也可能是10几秒到1分钟不等。
所有上面几种方式得出的时间效率就是几秒到几分钟不等,而且基本都是需要代码或者 adb 的开发工具,依赖于开发环境。
既然需要花的时间也不少,那么是不是应该做一个工具来提升更快的定位速度,提升定位效率呢?
其实思路很简单,就是写一个开发的SDK,用来实时关注当前的页面信息。这个页面信息主要包含如下的信息:
效果图如下:
从上面信息就能够很快的定位到当前的页面;当一个页面的深度到非常深的时候,这样的小工具就特别好用;最快速度只要几秒就能快速定位到页面,效率提升快几十倍不止,而且能够当着测试和产品的面,能够把当前关键的参数给他看,如:xxxId、埋点信息等。
上面的小工具,主要的工作是获得当前的Activity。获得当前Activity的方式主要有以下几种方式:
- 通过 RunningTaskInfo的 topActivity,该方法在后续的一些版本已经被禁用
- 手写代码管理Activity,这个方法比较粗暴,维护比较麻烦;
- 通过反射 ActivityThread获得 currentActivityThread 从 mActivities 中查询获得;
- 使用AccessibilityService 这个辅助功能,这个方法获得的信息比较少;
- 通过 ActivityLifecycleCallback 监听来获得。
经过对比,选择使用AccessibilityService和ActivityLifecycleCallback 这2种方式去尝试。以下就简单的说下这2种方法的实现,并进行二者之间的对比以及最后做出的选择。
private static Activity topActivity;
@Override
public void onActivityResumed(Activity activity) {
topActivity = activity;
}
是不是很简单?为了避免内存泄漏,可以在 onDestroy 的时候把 topActivity 设置成 null。这种方式简单快速,不需要申请权限。
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
Log.d(TAG, "onAccessibilityEvent");
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
if (event.getPackageName() != null && event.getClassName() != null) {
ComponentName componentName = new ComponentName(
event.getPackageName().toString(),
event.getClassName().toString()
);
ActivityInfo activityInfo = tryGetActivity(componentName);
boolean isActivity = activityInfo != null;
if (isActivity) {
Log.d(TAG, "CurentActivity " + componentName.flattenToShortString());
Log.d(TAG, "CurentActivity " + event.getPackageName().toString());
}
}
}
}
private ActivityInfo tryGetActivity(ComponentName componentName) {
try {
return getPackageManager().getActivityInfo(componentName, 0);
} catch (PackageManager.NameNotFoundException e) {
return null;
}
}
<service
android:name=".WindowChangeDetectingService"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService"/>
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibilityservice"/>
</service>
<?xml version="1.0" encoding="utf-8"?>
<!-- These options MUST be specified here in order for the events to be received on first
start in Android 4.1.1 -->
<accessibility-service
xmlns:tools="http://schemas.android.com/tools"
android:accessibilityEventTypes="typeWindowStateChanged"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagIncludeNotImportantViews"
android:canRetrieveWindowContent="true"
android:description="@string/accessibility_service_description"
xmlns:android="http://schemas.android.com/apk/res/android"
tools:ignore="UnusedAttribute"/>
从实现角度对比,使用 ActivityLifecycleCallback 比 AccessibilityService 更加简单。但是 AccessibilityService 有个优势就是可以不用集成到自己的 app 里面,可以独立运行,可以查看所有的当前页面是属于哪个 Activity,可以跨进程使用。使用 ActivityLifecycleCallback 必须要集成到自己的 app 中。
在实践过程中,其实我们不只是想获得当前的 Activity ,我们还想知道当前的 Activity 中有哪些当前的 fragment, 当前的 Activity 从上一个 Activity 中获得了哪些参数,当前的fragment 中有哪些参数等细节信息,那么只能集成到 app 中去的时候才会比较容易获得。所以最后选择了使用 ActivityLifecycleCallback 的方式。
一般页面上的信息开发,简单一点的就是一个 Activity 然后简单布局;复杂一点的基本都是 Activity + (ViewPager)Adatper + fragment, 有时候 fragment 里面还会有 ViewPager 装载着 fragment, 对于不熟悉代码的人找对应的业务逻辑页面和代码,还是需要花费不少时间的。所以页面信息 fragment 也很重要。
topActivity.getIntent().getExtras();// 获得 activity 的页面参数
topActivity.getSupportFragmentManager().getFragments(); // 获得 activity 一级中的 fragments
fragment.getChildFragmentManager().getFragments();// 对应 fragment 中的 fragments
fragment.getArguments() // 获得 fragment的页面参数
关于页面信息采集,在这里列举了几个使用场景,来证明效率得到了提高:
- 对于客户端开发者,能够快速的定位到当前出错的页面,特别是刚来的开发,或者不熟悉这块业务的,或者业务页面深度比较深的时候;
- 页面核心参数的确认。比如详情页面需要一些 id,这些详细参数就不需要客户端同事打断点的方式去获取,运营和测试自己可以去查看;
- 在精品课和云课堂集成的时候,能够让测试同学快速的区分哪个是精品课里面的页面,哪个是云课堂里面的页面,这样方便测试知道当前页面是属于哪个业务端的;(这个场景是网易内部融合项目)
- 页面全链路参数传递验证场景。比如:首页点击需要传递转化率的参数一直传递到下单页面,平时都是开发自己验证,有这个工具后,产品也能在提测后,从测试包上自己查看验证。
关于页面信息场景增强,以下几种页面信息方式,认为可以进行扩充的:
- 可以获得 RecyclerView 中的 adapter;(有很多布局逻辑,放到了 adapter 里面的 ViewHolder)
- webview 当前信息的监控;(前端同事调试)
- 网络看板的监控;(当前页面的网络请求信息)
- 不连接电脑 Logcat 日志查看看板;(不用连接电脑,获得 adb 信息)
- 参考线,界面元素位置,对应元素的颜色。(UI 走查的验证)
以上的几点,主要是按照自身 app 的情况去判断是否需要实现,判断哪些实现性价比比较高。目前已经实现的,基本都是个人认为性价比是比较高的东西。
当我们遇见一个问题的时候,首先思考这个问题是不是自己比较难受的点,然后观察其他人是否有类似的情况。这个问题常用的解决方案什么?有没有工具方法去替代?如果没有,可不可以用比较低的成本去制造一个工具?最后来提升自己的效率,这个工具如果能够帮助其他人,那么能效就更加好了。
-END-
复制代码 代码如下: !DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional...
解决方法如下: 第一种 使用iframe,但是目前使用iframe的人已经越来越少了,而...
content属性一般用于::before、::after伪元素中,用于呈现伪元素的内容。平时con...
注释1:上图整个大背景是这个网页的全部尺寸,中间的小框才是浏览器中的可见尺寸...
简介: 企业上云多账号架构中,如何做到从上到下管理的同时,处理好员工的权限边...
1.HTML5的内容类型 内容类型 描述 内嵌 向文档中添加其他类型的内容,例如audio...
data URI scheme 允许我们使用内联(inline-code)的方式在网页中包含数据,可以...
John Au-Yeung 来源:medium 译者:前端小智 有梦想,有干货,微信搜索 【大迁世...
Redis 官方在 2020 年 5 月正式推出 6.0 版本,提供很多振奋人心的新特性,所以...
先点赞再看,养成好习惯 前言 这两天在另一个社区看到了一个关于 Tomcat 的提问...