本文主要描述了怎麼樣提升一個客戶端開發排查和定位的效率,而且動手寫了一個小工具的實踐和思考,以及團隊中其餘合做者可以提升了定位問題效率,驗證功能是否準確的效率。前端
做者/馬傑 中國大學 MOOC 團隊android
編輯/劉振宇web
中國大學 MOOC 是由網易與高教社攜手推出的在線教育平臺,承接教育部國家精品開放課程任務,向大衆提供中國知名高校的 MOOC 課程。目前,不管是課程數量、質量仍是社會影響力,中國大學 MOOC 都已成爲全球領先的中文慕課平臺。shell
在平常的 Android 開發中,咱們常常會遇到如下的一些問題:測試、運營、產品同窗跑過來講這個頁面出了問題,趕忙看下。這時候客戶端開發同窗就須要趕忙定位到具體的某個頁面。windows
據觀察,大部分的狀況下對於一個突發頁面的問題定位,或者業務方想讓開發者確認這個頁面的業務邏輯的時候,客戶端開發者,每每須要花費比較長的時間去給業務方答覆。若是近期業務可能還能記得,可是客戶端的頁面比較多,想要快速定位到具體業務頁面,那麼就須要花更多的時間去找相關的頁面。網絡
因此本文的想法是怎麼快速找到對應的頁面,幫助開發快速的進入業務代碼,快速的回覆業務方提出的問題。app
在探討方案的時候,咱們須要對比目前有哪些方案,對比以後再選擇一種更加有效的方法。ide
在 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-