【LiteApp系列】埋點的設計
前言
埋點是線上專案採集資料的一種重要方式,這些可能是對error資料的採集,也可能是對使用者偏好的採集。其實質其實就是在程式碼的相應位置新增埋點,執行相應埋點程式碼後能夠將資訊記錄到對應檔案中,然後在一定時機下上傳到伺服器。
關鍵問題
埋點要解決的幾個關鍵問題如下:
1、由於全域性都可能使用到埋點,因此一般埋點為單例模式。
2、放置埋點不可避免在多個執行緒的情況,因此要使用handler來實現,埋點要有自己的執行緒並且有Looper迴圈。
3、部分埋點邏輯可能要由業務中去實現,因此要由相應介面。
程式碼
1、定義了logTypeFilter變數,來保證只有已經定義的埋點型別才會被記錄。未知的(沒有定義的)埋點型別會被過濾。
2、Record內部類是此處定義的埋點結構。
3、此處record(String logType, String logContent, Exception exception)是提供給全域性使用的設定埋點的方法。
/**
*
* Copyright 2018 iQIYI.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.iqiyi.halberd.liteapp.utils;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.util.Log;
/**
* Created by eggizhang@qiyi.com on 17-10-27.
* Using this utils for performance recording and process recording of lite app
*/
public class LogUtils {
public static final String TAG = LogUtils.class.getName();
public static final String LOG_MINI_PROGRAM_ACTIVITY_LIFE_CYCLE = "log_type_app_activity_life_cycle";
public static final String LOG_MINI_PROGRAM_PAGE_LIFE_CYCLE = "log_type_app_page_life_cycle";
public static final String LOG_MINI_PROGRAM_FRAGMENT_LIFE_CYCLE = "log_type_app_fragment_life_cycle";
public static final String LOG_MINI_PROGRAM_CACHE = "log_lite_app_cache";
public static final String LOG_MINI_PROGRAM_EVENT = "log_lite_app_event";
public static final String LOG_MINI_PROGRAM_ERROR = "log_lite_app_error";
public static final String ACTION_START=" action start";
public static final String ACTION_STOP=" action stop";
public static final String LIFE_OBJECT_CREATE = " object create";
public static final String LIFE_CREATE = " onCreate";
public static final String LIFE_START = " onStart";
public static final String LIFE_RESUME = " onResume";
public static final String LIFE_PAUSE = " onPause";
public static final String LIFE_STOP = " onStop";
public static final String LIFE_DESTROY = " onDestroy";
public static final String LIFE_CREATE_VIEW = " onCreateView";
public static final String LIFE_DESTROY_VIEW = " onDestroyView";
public static final String LIFE_ACTIVITY = " liteAppActivity";
public static final String LIFE_FRAGMENT = " liteAppFragment";
public static final String LIFE_PAGE = " liteAppPage";
public static final String LIFE_WEB_VIEW = " halWebView";
public static final String PAGE_CONTEXT_CREATE = " page context create";
public static final String PAGE_CONTEXT_DISPOSE = " page context dispose";
public static final String PAGE_THREAD_CREATE = " page thread create";
public static final String PAGE_THREAD_STOP = " page thread stop";
public static final String FRAGMENT_PUSH = " fragment push";
public static final String FRAGMENT_POP = " fragment pop";
public static final String CACHE_PAGE = " page cache";
public static final String CACHE_FILE = " file cache";
public static final String CACHE_IMAGE = " image cache";
public static final String CACHE_MEMORY_HIT = " memory hit";
public static final String CACHE_DISK_HIT = " disk hit";
public static final String CACHE_MEMORY_CREATE = " memory create";
public static final String CACHE_DISK_CREATE = " disk create";
public static final String CACHE_NETWORK = " network access";
public static final String CACHE_CHECK_UPDATE = "check update";
public static final String COMMON_FAIL = "failed";
public static final String COMMON_SUCCESS = "success";
public static final String EVENT_TYPE = " event type is ";
private final static String[] logTypeFilter = new String[]{
LOG_MINI_PROGRAM_ACTIVITY_LIFE_CYCLE,
LOG_MINI_PROGRAM_PAGE_LIFE_CYCLE,
LOG_MINI_PROGRAM_FRAGMENT_LIFE_CYCLE,
LOG_MINI_PROGRAM_CACHE,
LOG_MINI_PROGRAM_EVENT,
LOG_MINI_PROGRAM_ERROR
};
private static LogUtils instance = null;
private String applicationFilesDir=null;
private Thread logThread = null;
private String manufacture = null;
private String model = null;
private String liteAppID = null;
private boolean isNormalSize = true;
private int messageQueueSize=0;
private static final int QUEUE_MAX_SIZE = 100;
private static final int QUEUE_NORMAL_SIZE = 5;
private Handler handler;
private static final int LOG_RECORD=0;
private static final int REMOVE_EXTRA_FILES=1;
private LogProvider logProvider;
public static void setLogProvider(LogProvider logProvider) {
getInstance().logProvider = logProvider;
}
public interface LogProvider {
void doRecord(Record record, Exception e);
void clearLogFile(String applicationFileDir);
}
private LogUtils() {
}
public static LogUtils getInstance() {
if (instance == null) {
synchronized (LogUtils.class) {
if (instance == null) {
instance = new LogUtils();
}
}
}
return instance;
}
public void init(Context context) {
if (logThread == null) {
applicationFilesDir=context.getFilesDir().getPath();
manufacture = android.os.Build.MANUFACTURER;
model = android.os.Build.MODEL;
startThreadLoop();
}
}
public String getApplicationFilesDir() {
return applicationFilesDir;
}
public void setLiteAppIdIfNull(String liteAppID) {
if (this.liteAppID == null) {
this.liteAppID = liteAppID;
}
}
private void startThreadLoop() {
logThread = new Thread(new Runnable() {
@SuppressLint("HandlerLeak")
@Override
public void run() {
Looper.prepare();
handler=new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
try{
switch (msg.what){
case LOG_RECORD:
consume((Record) msg.obj);
break;
case REMOVE_EXTRA_FILES:
if(logProvider!=null)
logProvider.clearLogFile(applicationFilesDir);
break;
}
} catch (Exception e) {
Log.e(LOG_MINI_PROGRAM_ERROR, "logFile name unknown error.", e);
}
}
};
Message msg=handler.obtainMessage();
msg.what=REMOVE_EXTRA_FILES;
handler.sendMessage(msg);
Looper.loop();
}
});
logThread.start();
}
private void consume(Record record) {
//TODO 這裡留上傳介面
if (record.exception == null) {
Log.v("lite-app-log", record.toString());
} else {
Log.e("lite-app-error", record.toString(), record.exception);
}
if(logProvider!=null)
logProvider.doRecord(record, record.exception);
messageQueueSize--;
}
public class Record {
private String logType;
private long time;
private int pid;
private long tid;
private String content;
private Exception exception = null;
public String getContent() {
return content;
}
public long getTime() {
return time;
}
@Override
public String toString() {
if (messageQueueSize > QUEUE_MAX_SIZE) {
isNormalSize = false;
} else if (messageQueueSize < QUEUE_NORMAL_SIZE) {
isNormalSize = true;
}
if (isNormalSize) {
return "time:" + time + "," +
"content:" + content + "," +
"manufacture:" + manufacture + "," +
"model:" + model + "," +
"pid:" + pid + "," +
"tid:" + tid + "," +
"queueing:" + messageQueueSize + "," +
"logType:" + logType + "," +
"liteAppID:" + liteAppID + "," +
".";
} else {
return "too muck log" + logType;
}
}
}
public static void log(String logType, String logContent) {
if(getInstance().logThread!=null&&getInstance().handler!=null)
getInstance().record(logType, logContent, null);
}
public static void logError(String logType, String logContent, Exception exception) {
if(getInstance().logThread!=null&&getInstance().handler!=null)
getInstance().record(logType, logContent, exception);
}
private void record(String logType, String logContent, Exception exception) {
for (String aLogTypeFilter : logTypeFilter) {
if (aLogTypeFilter.equals(logType)) {
Record newRecord = new Record();
newRecord.time = System.currentTimeMillis();
newRecord.pid = Process.myPid();
newRecord.tid = Thread.currentThread().getId();
newRecord.content = logContent;
newRecord.logType = logType;
if (exception != null && logType.equals(LOG_MINI_PROGRAM_ERROR)) {
newRecord.exception = exception;
}
Message msg=handler.obtainMessage();
msg.what=LOG_RECORD;
msg.obj=newRecord;
handler.sendMessage(msg);
messageQueueSize++;
return;
}
}
}
}
更多內容可以檢視github官方開源專案:
https://github.com/iqiyi/LiteApp
相關文章
- 埋點計算定位
- 前端監控和前端埋點方案設計前端
- 面向切面程式設計AspectJ在Android埋點的實踐程式設計Android
- 【LiteApp系列】何為愛奇藝小程式?APP
- 前端埋點統計方案思考前端
- 埋點
- 揭秘!如何用Flutter設計一個100%準確的埋點框架?Flutter框架
- 無埋點統計SDK實踐
- 揭祕!如何用Flutter設計一個100%準確的埋點框架?Flutter框架
- 揭祕!一個高準確率的Flutter埋點框架如何設計Flutter框架
- 【LiteApp系列】愛奇藝小程式架構淺析APP架構
- 不可缺少的程式埋點
- 小程式從手動埋點到自動埋點
- 埋點表相關
- 智慧小程式檔案館——埋點統計
- 懶人做開發系列:利用Object-C特性埋點Object
- 前端埋點資料採集(一)採集系統架構設計前端架構
- 資料埋點測試的那點事
- 前端埋點方案分析前端
- “用資料說話,從埋點開始”-帶你理解前端的三種埋點前端
- 元件中路由和埋點元件路由
- js無侵入埋點方案JS
- JetCache埋點的騷操作,不服不行啊
- 小程式自動埋點教程
- vue宣告式埋點實踐Vue
- MTFlexbox自動化埋點探索Flex
- 全自動埋點 diff 工具
- uniapp增加自定義埋點功能APP
- SpringBoot Actuator — 埋點和監控Spring Boot
- Android埋點技術概覽Android
- Android全量埋點實踐Android
- 通用的底層埋點都是怎麼做的?
- 玩法設計弄潮兒:盤點《暗黑血統》系列
- iOS資料埋點統計方案選型(附Demo):執行時Method Swizzling機制與AOP程式設計(面向切面程式設計)iOS程式設計
- Html網頁標籤曝光埋點HTML網頁
- Flutter頁面曝光事件埋點框架Flutter事件框架
- 如何使用Android視覺化埋點Android視覺化
- 極光筆記 | 埋點體系建設與實施方法論筆記