本文介紹AliPlayerKit日誌系統LogHub的核心概念、功能特性及使用方法。
概念介紹
什麼是 LogHub?
LogHub 是 PlayerKit 架構中所有日誌輸出的統一入口,是一個靜態工具類,提供統一的日誌輸出功能。
它封裝了 Android 原生 Log API,在保留原有功能的基礎上,增加了以下核心能力:
能力 | 說明 |
日誌開關 | 控制日誌輸出是否啟用。 |
控制台開關 | 控制日誌是否輸出到 Logcat。 |
層級過濾 | 只輸出指定層級及以上的日誌。 |
監聽器機制 | 支援外部監聽日誌輸出,實現自訂處理(如寫入檔案)。 |
什麼是日誌監聽器?
日誌監聽器 (LogHubListener) 是用於接收 LogHub 輸出日誌的回調介面。通過實現該介面,可以自訂日誌的處理方式,例如:
將日誌寫入檔案,便於問題排查時提供給支援人員團隊
將日誌上傳到遠程伺服器,實現遠程日誌收集
在 UI 上展示日誌,用於調試面板
功能特性
解決問題
日誌分散,難以統一管理和過濾。
發布版本需要關閉調試日誌,缺乏統一開關。
問題排查時缺乏記錄檔支援。
播放器 SDK 日誌與業務日誌分離,難以關聯分析。
核心價值
使用方式 | 說明 | 優勢 |
基礎使用 | 直接使用 LogHub 輸出日誌。 | 無需配置,開箱即用。 |
層級控制 | 設定記錄層級過濾。 | 開發調試詳細,生產環境精簡。 |
監聽器擴充 | 註冊監聽器處理日誌。 | 日誌持久化、遠程上報等。 |
架構優勢:
統一入口:所有日誌通過 LogHub 統一輸出,便於管理和控制。
靈活控制:支援全域開關、控制台開關、層級過濾三重控制。
可擴充:通過監聽器機制支援自訂Tlog,如檔案寫入、遠程上報。
安全執行緒:監聽器列表使用 CopyOnWriteArrayList,支援多安全執行緒訪問。
核心能力
能力 | 說明 |
多層級日誌 | 支援 VERBOSE、DEBUG、INFO、WARN、ERROR 五個層級。 |
異常日誌 | 支援攜帶 Throwable 異常資訊輸出。 |
長日誌分段 | 自動分段處理超長日誌,避免 Logcat 截斷。 |
效能日誌 | 提供效能時間日誌輸出,便於效能分析。 |
SDK 日誌整合 | 通過 IPlayerLogger 介面統一管理播放器 SDK 日誌。 |
內建群組件詳解
記錄層級
層級 | 值 | 說明 | 適用情境 |
VERBOSE | 0 | 最詳細資訊層級,輸出所有調試資訊。 | 開發調試階段。 |
DEBUG | 1 | 調試資訊層級。 | 調試特定功能。 |
INFO | 2 | 資訊層級,輸出一般資訊。 | 預設層級,生產環境推薦。 |
WARN | 3 | 警告層級,輸出潛在問題提示。 | 關注潛在風險。 |
ERROR | 4 | 錯誤層級,輸出錯誤資訊。 | 關注嚴重問題。 |
NONE | 100 | 禁用所有日誌。 | 關閉日誌輸出。 |
核心類
類 | 說明 |
| 日誌中心工具類,統一日誌輸出入口。 |
| 記錄層級註解定義。 |
| 日誌資訊資料類,封裝單條日誌完整資訊。 |
| 日誌監聽器介面,用於接收日誌回調。 |
| 播放器全域日誌介面,封裝 SDK 日誌功能。 |
| 預設播放器日誌實現。 |
| 播放器日誌回調介面。 |
基礎使用
日誌系統提供三種使用原則,開發人員可根據需求選擇合適的方式。
策略 | 說明 | 適用情境 |
直接輸出日誌 | 最簡單的使用方式,直接調用 LogHub 方法。 | 開發調試、臨時日誌。 |
配置記錄層級 | 根據環境配置不同記錄層級。 | 區分開發/生產環境。 |
註冊監聽器 | 通過監聽器自訂Tlog。 | 日誌持久化、遠程上報。 |
策略一:直接輸出日誌
最簡單的使用方式,直接調用 LogHub 的靜態方法輸出日誌:
// 輸出 INFO 層級日誌
LogHub.i(this, "playVideo", "開始播放視頻");
// 輸出 WARN 層級日誌
LogHub.w(this, "onBuffering", "緩衝中,當前進度: ", progress);
// 輸出 ERROR 層級日誌
LogHub.e(this, "onError", "播放失敗,錯誤碼: ", errorCode);
// 輸出帶異常的 ERROR 日誌
LogHub.e(this, "onException", throwable, "發生異常");
// 輸出效能時間日誌
LogHub.t("loadVideoSource", costTime);日誌格式說明:
[類名] 方法名: 訊息內容Logcat 過濾建議:
package:mine tag:AliPlayerKit策略二:配置記錄層級
根據運行環境配置不同的記錄層級:
// 開發環境:輸出所有日誌
if (BuildConfig.DEBUG) {
LogHub.setEnableLog(true);
LogHub.setEnableConsoleLog(true);
LogHub.setLogLevel(LogLevel.VERBOSE);
}
// 生產環境:只輸出 INFO 及以上層級
else {
LogHub.setEnableLog(true);
LogHub.setEnableConsoleLog(false); // 關閉控制台輸出
LogHub.setLogLevel(LogLevel.INFO);
}
// 完全關閉日誌(敏感情境)
LogHub.setEnableLog(false);策略三:註冊監聽器
通過監聽器實現日誌自訂處理:
// 建立監聽器
LogHubListener listener = logInfo -> {
// 處理日誌資訊
String formattedLog = logInfo.getFormattedMessage();
// 寫入檔案、上傳伺服器等
saveToFile(formattedLog);
};
// 註冊監聽器
LogHub.addListener(listener);
// 移除監聽器(不再需要時)
LogHub.removeListener(listener); 進階使用
如何?日誌持久化?
通過 LogHubListener 介面實現記錄檔寫入,便於問題排查。
建立日誌監聽器。
public class FileLogListener implements LogHubListener { private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); private final File mLogFile; public FileLogListener(Context context) { // 記錄檔路徑 File logDir = new File(context.getExternalFilesDir(null), "logs"); if (!logDir.exists()) { logDir.mkdirs(); } mLogFile = new File(logDir, "player_" + getDateStr() + ".log"); } @Overridepublic void onLog(@NonNull LogInfo logInfo) { // 非同步寫入檔案,避免阻塞調用線程 mExecutor.execute(() -> { try { String log = logInfo.getFormattedMessage() + "\n"; FileOutputStream fos = new FileOutputStream(mLogFile, true); fos.write(log.getBytes(StandardCharsets.UTF_8)); fos.close(); } catch (IOException e) { // 忽略寫入異常 } }); } }在Application中註冊。
public class MyApplication extends Application { private FileLogListener mFileLogListener; @Overridepublic void onCreate() { super.onCreate(); // 建立並註冊檔案日誌監聽器 mFileLogListener = new FileLogListener(this); LogHub.addListener(mFileLogListener); } }擷取記錄檔。
// 記錄檔路徑 File logDir = new File(getExternalFilesDir(null), "logs"); File[] logFiles = logDir.listFiles(); // 提供給支援人員團隊進行分析
樣本參考:playerkit-examples/example-log-system/FileLogListener.java。
如何整合播放器 SDK 日誌?
通過IPlayerLogger介面統一管理播放器 SDK 的日誌輸出。
建立播放器日誌執行個體。
// 在 Application 中初始化 IPlayerLogger playerLogger = new DefaultPlayerLogger(context); // 啟用 SDK 控制台日誌(調試時開啟) playerLogger.enableConsoleLog(BuildConfig.DEBUG); // 設定 SDK 記錄層級 playerLogger.setLogLevel(LogLevel.INFO);設定 SDK 日誌回調。
playerLogger.setLogCallback((level, message) -> { // 處理 SDK 日誌,如寫入檔案、上報等 LogHub.log(level, "PlayerSDK", message); });
如何在 UI 上展示日誌?
通過監聽器將日誌即時展示在介面上。
public class LogPanelActivity extends AppCompatActivity {
private TextView mTvLogOutput;
private LogHubListener mLogListener;
@Overrideprotected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_log_panel);
mTvLogOutput = findViewById(R.id.tv_log_output);
// 建立 UI 日誌監聽器
mLogListener = logInfo -> runOnUiThread(() -> {
mTvLogOutput.append(logInfo.getFormattedMessage() + "\n");
});
// 註冊監聽器
LogHub.addListener(mLogListener);
}
@Overrideprotected void onDestroy() {
super.onDestroy();
// 移除監聽器,避免記憶體流失
LogHub.removeListener(mLogListener);
}
}最佳實務
記錄層級選擇
情境 | 推薦層級 | 說明 |
詳細調試資訊 | VERBOSE | 開發階段追蹤流程。 |
調試關鍵節點 | DEBUG | 記錄關鍵狀態變化。 |
重要商務資訊 | INFO | 生產環境保留的重要訊息。 |
潛在問題警告 | WARN | 可恢複的異常、效能警告。 |
嚴重錯誤 | ERROR | 導致功能失敗的錯誤。 |
生產環境配置
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
if (BuildConfig.DEBUG) {
// 開發環境:詳細日誌 + 控制台輸出
LogHub.setEnableLog(true);
LogHub.setEnableConsoleLog(true);
LogHub.setLogLevel(LogLevel.VERBOSE);
} else {
// 生產環境:精簡日誌 + 關閉控制台 + 檔案持久化
LogHub.setEnableLog(true);
LogHub.setEnableConsoleLog(false); // 關閉 Logcat 輸出
LogHub.setLogLevel(LogLevel.INFO); // 只保留 INFO 及以上
LogHub.addListener(new FileLogListener(this));
}
}
} 注意事項
事項 | 說明 |
避免敏感資訊 | 日誌中不要輸出使用者隱私、密鑰等敏感資訊。 |
非同步寫入檔案 | 監聽器中寫檔案應使用非同步方式,避免阻塞。 |
監聽器異常處理 | 監聽器異常不應影響正常日誌輸出。 |
及時移除監聽器 | 頁面銷毀時移除監聽器,避免記憶體流失。 |
全域監聽器位置 | 檔案日誌監聽器建議放在 Application 中,確保全生命週期收集。 |
樣本參考
專案提供了完整的樣本,位於 playerkit-examples/example-log-system。
樣本功能
功能 | 說明 |
日誌輸出示範 | 示範各層級日誌輸出。 |
UI 日誌展示 | 在介面上即時展示日誌。 |
檔案日誌監聽器 | 示範如何?日誌持久化。 |
運行樣本
在 Demo App 中選擇 Log System 樣本查看效果。
API 參考
核心介面
介面/類 | 說明 |
| 日誌中心,統一日誌輸出入口。 |
| 日誌監聽器介面,接收日誌回調。 |
| 日誌資訊資料類。 |
| 播放器全域日誌介面。 |
LogHub 主要方法
方法 | 說明 |
| 開啟/關閉日誌輸出(總開關)。 |
| 開啟/關閉控制台日誌輸出。 |
| 設定記錄層級過濾。 |
| 添加日誌監聽器。 |
| 移除日誌監聽器。 |
| 輸出各層級日誌。 |
| 輸出效能時間日誌。 |
LogInfo 主要方法
方法 | 說明 |
| 擷取記錄層級。 |
| 擷取日誌標籤。 |
| 擷取日誌訊息。 |
| 擷取異常資訊。 |
| 擷取線程名稱。 |
| 擷取時間戳記(毫秒)。 |
| 擷取格式化的日誌字串。 |
技術原理
長日誌分段機制
Logcat 單條日誌有長度限制(約 4000 字元),LogHub 通過分段機制處理超長日誌:
private static final int BUFFER_SIZE = 3000;
// 分段輸出到 Logcatwhile (startIndex < length) {
int endIndex = Math.min(length, startIndex + BUFFER_SIZE);
String sub = s.substring(startIndex, endIndex);
// 輸出分段日誌...
startIndex = endIndex;
}最佳化點:對於監聽器,發送完整的日誌內容;對於 Logcat,進行分段輸出。
安全執行緒機制
監聽器列表使用 CopyOnWriteArrayList,確保多安全執行緒:
private static final List<LogHubListener> sListeners = new CopyOnWriteArrayList<>();特點:
讀操作無鎖,效能高。
寫操作複製新數組,不影響進行中的讀操作。
適合讀多寫少的監聽器註冊/移除情境。
常見問題
如何在 Logcat 中過濾 PlayerKit 日誌?
使用以下過濾條件:
package:mine tag:AliPlayerKit或者按層級過濾:
package:mine tag:AliPlayerKit level:info 生產環境記錄檔在哪裡?
預設路徑(如果使用 FileLogListener 樣本):
/sdcard/Android/data/[包名]/files/logs/監聽器回調在哪個線程?
監聽器回調在日誌輸出的調用線程執行。如果需要在 UI 線程處理,請使用 runOnUiThread() 切換線程。
高頻錯誤錯例
以下是客戶回函中最常導致問題的情況,請務必避免:
錯例 1:監聽器中阻塞操作導致卡頓
錯誤碼:
@Overridepublic void onLog(@NonNull LogInfo logInfo) {
// 在主線程同步寫檔案,會阻塞 UItry {
FileOutputStream fos = new FileOutputStream(mLogFile, true);
fos.write(logInfo.getFormattedMessage().getBytes());
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
正確代碼:
private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
@Overridepublic void onLog(@NonNull LogInfo logInfo) {
// 非同步寫入檔案
mExecutor.execute(() -> {
try {
// 檔案寫入邏輯...
} catch (IOException e) {
// 忽略
}
});
}
問題原因:如果日誌輸出在主線程,同步檔案寫入會阻塞 UI,導致卡頓甚至 ANR。
錯例 2:忘記移除監聽器導致記憶體流失
錯誤碼:
@Overrideprotected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 註冊了監聽器但從未移除
LogHub.addListener(logInfo -> {
mTvLogOutput.append(logInfo.getMessage());
});
}
正確代碼:
private LogHubListener mLogListener;
@Overrideprotected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mLogListener = logInfo -> runOnUiThread(() -> {
mTvLogOutput.append(logInfo.getMessage());
});
LogHub.addListener(mLogListener);
}
@Overrideprotected void onDestroy() {
super.onDestroy();
// 及時移除監聽器if (mLogListener != null) {
LogHub.removeListener(mLogListener);
}
}
問題原因:監聽器持有 Activity 引用,Activity 銷毀後無法釋放,導致記憶體流失。
錯例 3:監聽器異常影響其他監聽器
錯誤碼:
@Overridepublic void onLog(@NonNull LogInfo logInfo) {
// 未處理異常,如果此處拋出異常會影響其他監聽器
String data = logInfo.getMessage();
int result = 1 / 0; // 拋出異常
}
正確代碼:
@Overridepublic void onLog(@NonNull LogInfo logInfo) {
try {
// 在監聽器內部處理異常
processLog(logInfo);
} catch (Exception e) {
// 監聽器自身的異常處理,不影響日誌系統
android.util.Log.e("FileLogListener", "Error processing log", e);
}
}問題原因:雖然 LogHub 內部已捕獲監聽器異常,但監聽器本身應該處理可能的異常,保證自身穩定性。
如何調試?
檢查日誌開關狀態:
boolean enabled = LogHub.isLogEnabled(); boolean consoleEnabled = LogHub.isConsoleLogEnabled(); int level = LogHub.getLogLevel();查看 Logcat 輸出:
使用
tag:AliPlayerKit過濾查看日誌輸出。檢查監聽器數量:
日誌系統的監聽器數量可通過反射查看(調試用),確保監聽器正確註冊和移除。