如何準確測量 Android 應用中 Activity 和 Fragment 的啟動時間
在 Android 應用開發中,了解每個 Activity 和 Fragment 的啟動時間對于性能優化至關重要。本文將介紹幾種方法來準確測量 Activity 和 Fragment 的啟動時間,并提供實際操作步驟,以幫助提升應用的響應速度和用戶體驗。
1. 使用 adb shell am start -W
命令
adb shell am start -W
命令是一種簡單且直接的方法,用于測量 Activity 的啟動時間。該命令啟動指定的 Activity 并輸出相關的時間數據。以下是如何使用該命令,以及如何解決常見錯誤。
1.1 命令
adb shell am start -W -n <your.package.name>/<your.package.name.yourActivity>
1.2 輸出解釋
ThisTime
: 當前 Activity 的啟動時間。TotalTime
: 從應用啟動到當前 Activity 的總時間。WaitTime
: 系統等待時間。
1.3 啟動 Activity 并傳遞參數
使用 -e
選項傳遞參數
要傳遞鍵值對參數,可以使用 -e
選項。-e
選項用于將一個字符串鍵值對傳遞給目標 Activity
。如果有多個鍵值對需要傳遞,可以使用多個 -e
選項。
命令格式
adb shell am start -n <your.package.name>/<your.package.name.YourActivity> -e <key1> <value1> -e <key2> <value2> ...
-n <your.package.name>/<your.package.name.YourActivity>
:指定要啟動的Activity
。-e <key> <value>
:指定要傳遞的參數及其對應的值。key
是參數的名稱,value
是參數的值。
示例
應用程序包名為 com.example.app
,要啟動的 Activity
是 com.example.app.ui.MainActivity
,并且需要傳遞兩個參數:user_id
和 session_token
。可以使用以下命令:
adb shell am start -n com.example.app/com.example.app.ui.MainActivity -e user_id 12345 -e session_token abcdef123456
傳遞多種數據類型
除了使用 -e
選項傳遞字符串參數,還可以使用以下選項傳遞其他數據類型的參數:
-e
:傳遞字符串鍵值對。-en
:傳遞整數鍵值對。-ef
:傳遞浮點數鍵值對。-el
:傳遞長整型鍵值對。-eb
:傳遞布爾鍵值對。-eia
:傳遞整數數組鍵值對。-efa
:傳遞浮點數數組鍵值對。
示例
如果需要傳遞一個整數和一個布爾值參數,可以使用以下命令:
adb shell am start -n com.example.app/com.example.app.ui.MainActivity -en max_retries 5 -eb is_active true
在這個例子中,max_retries
是一個整數參數,is_active
是一個布爾參數。
解析傳遞的參數
在的 Activity
中,可以通過 Intent
對象來獲取傳遞的參數。例如:
public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Intent intent = getIntent();String userId = intent.getStringExtra("user_id");String sessionToken = intent.getStringExtra("session_token");// 使用獲取的參數}
}
對于其他數據類型的參數,可以使用對應的 Intent
方法進行獲取,例如 getIntExtra
、getBooleanExtra
等。
通過使用 -e
及相關選項,可以方便地將參數傳遞給 Activity
,在測試和調試過程中模擬各種條件。這使得驗證應用程序的不同功能變得更加高效和靈活。
1.4 常見錯誤及解決方案
1.4.1 java.lang.SecurityException: Permission Denial
錯誤信息:
java.lang.SecurityException: Permission Denial: starting Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.example.myapp/.MainActivity mCallingUid=2000 } from null (pid=29129, uid=2000) not exported from uid 10600
原因: 當嘗試啟動的 Activity 沒有配置為導出的或沒有適當的權限時,會出現此錯誤。
解決方案:
-
修改
AndroidManifest.xml
: 確保目標 Activity 已配置為exported="true"
,這樣才能允許外部調用。<activityandroid:name=".MainActivity"android:exported="true"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter> </activity>
-
檢查權限: 確保應用具有適當的權限,或嘗試使用
adb
命令以 root 權限運行。
1.4.2 java.lang.SecurityException: Permission Denial: starting Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.example.myapp/.MainActivity } from null (pid=29219, uid=2000) not exported from uid 10600
錯誤信息:
java.lang.SecurityException: Permission Denial: starting Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.example.myapp/.MainActivity mCallingUid=2000 } from null (pid=29219, uid=2000) not exported from uid 10600
原因: 該錯誤通常表示目標 Activity 沒有正確配置為導出或啟動。即使 Activity 是導出的,它可能沒有正確設置 intent-filter
或 launchMode
。
解決方案:
-
配置
intent-filter
: 確保 Activity 正確配置了intent-filter
,如果需要,它應包括MAIN
和LAUNCHER
動作和類別。<activityandroid:name=".MainActivity"android:exported="true"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter> </activity>
-
檢查啟動模式: 如果 Activity 是內部活動或不需要被外部啟動,考慮使用其他方法測試,或者設置
android:exported="true"
確保外部調用。
1.4.3 ActivityNotFoundException
錯誤信息:
android.content.ActivityNotFoundException: Unable to find explicit activity class {com.example.myapp/.MainActivity}; have you declared this activity in your AndroidManifest.xml?
原因: 該錯誤表示系統無法找到指定的 Activity。這可能是因為在 AndroidManifest.xml
中沒有正確聲明該 Activity,或包名和類名錯誤。
解決方案:
-
檢查
AndroidManifest.xml
: 確保目標 Activity 已在AndroidManifest.xml
中正確聲明。<activityandroid:name=".MainActivity"android:exported="true"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter> </activity>
-
核對包名和類名: 確保
adb
命令中的包名和類名與實際聲明的一致。
2. 手動記錄啟動時間
為了獲得更準確的數據,可以在代碼中手動記錄每個 Activity 的啟動時間。這種方法能夠提供更符合實際使用場景的數據。
2.1 代碼
在每個 Activity 的 onCreate
方法中記錄啟動時間:
public class MainActivity extends AppCompatActivity {private static final String TAG = "MainActivity";@Overrideprotected void onCreate(Bundle savedInstanceState) {long startTime = System.currentTimeMillis();super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);long elapsedTime = System.currentTimeMillis() - startTime;Log.d(TAG, "Activity startup time: " + elapsedTime + " ms");}
}
2.2 捕獲日志
使用 adb logcat
捕獲啟動時間日志:
adb logcat -s MainActivity
3. 使用 Application.ActivityLifecycleCallbacks
接口記錄activity啟動時長
為了自動記錄每個 Activity 的啟動時間,避免在每個 Activity 中重復編寫代碼,可以使用 Application.ActivityLifecycleCallbacks
接口。
3.1使用注解標記中文名
首先,我們創建一個自定義注解來標記 Activity 的中文名。
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;@Retention(RetentionPolicy.RUNTIME)
public @interface ChineseName {String value();
}
然后,在每個 Activity 類上使用這個注解。
import android.app.Activity;
import android.os.Bundle;@ChineseName("我的Activity中文名")
public class MyActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// Activity 其他代碼...}
}
3.2創建生命周期回調類
實現 Application.ActivityLifecycleCallbacks
創建一個實現 Application.ActivityLifecycleCallbacks
接口的類,以記錄每個 Activity 的啟動時間,并使用反射獲取中文名。
import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import android.util.Log;public class ActivityLifecycleHandler implements Application.ActivityLifecycleCallbacks {private static final String TAG = "ActivityLifecycleHandler";@Overridepublic void onActivityCreated(Activity activity, Bundle savedInstanceState) {long startTime = System.currentTimeMillis();activity.getWindow().getDecorView().post(() -> {long endTime = System.currentTimeMillis();long duration = endTime - startTime;String activityName = getChineseName(activity);Log.d(TAG, activityName + " creation time: " + duration + " ms");});}@Overridepublic void onActivityStarted(Activity activity) {}@Overridepublic void onActivityResumed(Activity activity) {}@Overridepublic void onActivityPaused(Activity activity) {}@Overridepublic void onActivityStopped(Activity activity) {}@Overridepublic void onActivitySaveInstanceState(Activity activity, Bundle outState) {}@Overridepublic void onActivityDestroyed(Activity activity) {}private String getChineseName(Activity activity) {ChineseName annotation = activity.getClass().getAnnotation(ChineseName.class);if (annotation != null) {return annotation.value();} else {return activity.getClass().getSimpleName();}}
}
3.3 注冊生命周期回調
在 Application
類中注冊生命周期回調:
public class MyApp extends Application {@Overridepublic void onCreate() {super.onCreate();registerActivityLifecycleCallbacks(new AppLifecycleHandler());}
}
3.4 修改 AndroidManifest.xml
確保在 AndroidManifest.xml
中指定自定義的 Application
類:
<applicationandroid:name=".MyApp"...>...
</application>
3.5持續日志輸出
使用 adb logcat
命令捕獲啟動時間的日志輸出。
adb logcat -s AppLifecycleHandler
每次切換頁面或啟動新的 Activity 時,控制臺會輸出類似如下的日志信息:
07-04 14:23:45.123 1234-1234/com.example.myapp D/AppLifecycleHandler: MainActivity startup time: 150 ms
07-04 14:23:47.567 1234-1234/com.example.myapp D/AppLifecycleHandler: SettingsActivity startup time: 180 ms
07-04 14:23:47.567 1234-1234/com.example.myapp D/AppLifecycleHandler: 我的Activity中文名 startup time: 180 ms
這些日志顯示了每個 Activity 的啟動時間。通過這些數據,可以分析和優化應用性能。
4.記錄所有 Fragment 啟動時間
同樣,可以通過覆蓋 Fragment
的生命周期方法來記錄 Fragment
的啟動時間。
4.1使用注解標記中文名
同樣地,為 Fragment
使用相同的 ChineseName
注解。
import androidx.fragment.app.Fragment;@ChineseName("我的Fragment中文名")
public class MyFragment extends Fragment {// Fragment 代碼...
}
4.2覆蓋 Fragment 生命周期方法
首先,創建一個基類 BaseFragment
,覆蓋 Fragment
的 onCreateView
方法,以記錄每個 Fragment
的啟動時間。
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;public class FragmentLifecycleHandler extends FragmentManager.FragmentLifecycleCallbacks {private static final String TAG = "FragmentLifecycleHandler";@Overridepublic void onFragmentViewCreated(@NonNull FragmentManager fm, @NonNull Fragment f, @NonNull View v, @Nullable Bundle savedInstanceState) {long startTime = System.currentTimeMillis();f.getView().post(() -> {long endTime = System.currentTimeMillis();long duration = endTime - startTime;String fragmentName = getChineseName(f);Log.d(TAG, fragmentName + " view creation time: " + duration + " ms");});}private String getChineseName(Fragment fragment) {ChineseName annotation = fragment.getClass().getAnnotation(ChineseName.class);if (annotation != null) {return annotation.value();} else {return fragment.getClass().getSimpleName();}}
}
4.3完整代碼示例
所有的 Fragment
類繼承自 BaseFragment
。
public class MyFragment extends BaseFragment {@Nullable@Overridepublic View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {return inflater.inflate(R.layout.fragment_my, container, false);}
}
4.4持續日志輸出
使用 adb logcat
命令捕獲啟動時間的日志輸出。
adb logcat -s FragmentLifecycle
每次加載 Fragment
時,控制臺會輸出類似如下的日志信息:
07-04 14:23:45.123 1234-1234/com.example.myapp D/FragmentLifecycle: MyFragment startup time: 120 ms
這些日志顯示了每個 Fragment
的啟動時間。通過這些數據,可以分析和優化應用性能。
5. 使用第三方工具
除了自定義實現外,還有許多第三方工具和庫可以幫助測量和優化應用的啟動時間。以下是一些常用的工具和庫:
5.1 阿里云移動監控(Aliyun Mobile Monitoring)
阿里云移動監控 提供了全面的性能監控解決方案,包括啟動時間、崩潰分析、用戶行為分析等功能。
使用步驟:
- 集成 SDK:
- 在阿里云控制臺創建一個項目,并下載阿里云移動監控 SDK。
- 將 SDK 集成到的 Android 應用中。具體步驟可以參考 阿里云官方文檔。
- 配置性能監控:
- 在阿里云控制臺中配置性能監控,包括啟動時間監控和用戶行為分析。
- 查看性能數據:
- 登錄阿里云控制臺,訪問 阿里云移動監控 部分,可以查看應用的啟動時間、崩潰報告以及用戶行為分析數據。
5.2 騰訊云移動分析(Tencent Cloud Mobile Analytics)
騰訊云移動分析 提供了全面的應用性能監控和用戶行為分析功能,適用于中國國內的應用開發者。
使用步驟:
- 集成 SDK:
- 在騰訊云控制臺創建一個項目,并下載騰訊云移動分析 SDK。
- 將 SDK 集成到的 Android 應用中。具體步驟可以參考 騰訊云官方文檔.
- 配置性能監控:
- 在騰訊云控制臺中配置性能監控和用戶行為分析功能。
- 查看性能數據:
- 登錄騰訊云控制臺,訪問 騰訊云移動分析 部分,可以查看應用的啟動時間、崩潰報告以及用戶行為數據。
5.3 百度移動統計(Baidu Mobile Statistics)
百度移動統計 提供了應用性能監控和用戶行為分析功能,幫助開發者了解應用的性能和用戶行為。
使用步驟:
- 集成 SDK:
- 在百度統計控制臺創建一個項目,并下載百度移動統計 SDK。
- 將 SDK 集成到的 Android 應用中。具體步驟可以參考 百度統計官方文檔.
- 配置性能監控:
- 在百度統計控制臺中配置性能監控功能和用戶行為分析。
- 查看性能數據:
- 登錄百度統計控制臺,訪問 百度移動統計 部分,可以查看應用的啟動時間、崩潰報告和用戶行為數據。
5.3 Android Profiler
Android Profiler 是 Android Studio 提供的一套工具,可以幫助實時監控應用的性能,包括啟動時間、內存使用、CPU 使用等。
使用步驟:
-
啟動 Android Studio Profiler:
- 打開 Android Studio,運行的應用。
- 選擇
View
>Tool Windows
>Profiler
,然后選擇的應用進程。
-
監控應用啟動時間:
- 在 Profiler 窗口中,選擇
CPU
視圖。 - 啟動應用時,Profiler 會記錄啟動過程中 CPU 的使用情況,可以幫助了解啟動時間及其瓶頸。
- 在 Profiler 窗口中,選擇
-
分析性能數據:
- Profiler 提供的時間線視圖和詳細的性能指標可以幫助識別啟動過程中的性能問題。
- 可以查看應用的活動生命周期、內存使用情況以及 CPU 占用情況,從而找到優化的切入點。
5.4 LeakCanary
LeakCanary 是一個開源的內存泄漏檢測庫,可以幫助檢測和修復內存泄漏,從而間接優化應用的啟動時間和整體性能。
使用步驟:
-
集成 LeakCanary:
-
在
build.gradle
文件中添加 LeakCanary 的依賴:debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'
-
-
自動檢測內存泄漏:
- LeakCanary 會自動監控應用的內存泄漏并在發現泄漏時發出警告。
-
修復內存泄漏:
- 根據 LeakCanary 提供的報告,識別并修復內存泄漏問題,從而提高應用的性能和啟動速度。
備注
Firebase Performance Monitoring
Firebase Performance Monitoring 是一個由 Google 提供的性能監控工具,它可以幫助開發者監控應用的性能,包括啟動時間、網絡請求時間等。然而,由于某些 Google 服務在中國大陸可能會受到訪問限制,Firebase Performance Monitoring 的功能和數據傳輸可能會受到影響。
官方地址:
- Firebase Performance Monitoring 官方文檔
總結
使用這些第三方工具,可以更全面地監控和優化應用的啟動時間和整體性能。三方庫 和 Android Profiler 提供了實時的性能數據和分析功能,而 LeakCanary 可以幫助發現并修復內存泄漏問題。這些工具和庫的結合使用,可以幫助提升應用的用戶體驗和性能。