TalkingData技術實現分析

初始化分析

初始化主要做了以下幾件事:

  1. 讀取配置信息appId、channeId,既可通過代碼設置appId、channelId,也可以在manifest中設置;如果兩個都設置的話,優先以AndroidManifest.xml爲主;

  2. Hook系統函數,監聽Activity生命週期;

    Android 4.0及以後版本,只需要向mActivityLifecycleCallbacks 添加一個callback即可


public class Application extends ContextWrapper implements ComponentCallbacks2 {
    private ArrayList<ComponentCallbacks> mComponentCallbacks =
            new ArrayList<ComponentCallbacks>();
    private ArrayList<ActivityLifecycleCallbacks> mActivityLifecycleCallbacks =
            new ArrayList<ActivityLifecycleCallbacks>();
    private ArrayList<OnProvideAssistDataListener> mAssistCallbacks = null;

    /** @hide */
    public LoadedApk mLoadedApk;

    public interface ActivityLifecycleCallbacks {
        void onActivityCreated(Activity activity, Bundle savedInstanceState);
        void onActivityStarted(Activity activity);
        void onActivityResumed(Activity activity);
        void onActivityPaused(Activity activity);
        void onActivityStopped(Activity activity);
        void onActivitySaveInstanceState(Activity activity, Bundle outState);
        void onActivityDestroyed(Activity activity);
    }

}

android4.0之前,需要Hook ActivityManagerNative,這是一個很有用的類,android插件化、熱更新也會hook這個類;說到插件化,個人感覺還是360做得比較好,基本上完全解耦了,不需要任何依賴就可以加載apk,但要hook的系統類比較多,還需要對各種版本兼容,甚至不同的rom兼容,難度和工作量都是比較大的,但對我們瞭解apk的啓動與安裝有很大幫助;

public abstract class ActivityManagerNative extends Binder implements IActivityManager {
                             ......
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
            protected IActivityManager create() {
                IBinder b = ServiceManager.getService("activity");
                if (false) {
                    Log.v("ActivityManager", "default service binder = " + b);
                }
                IActivityManager am = asInterface(b);
                if (false) {
                    Log.v("ActivityManager", "default service = " + am);
                }
                return am;
            }
        };
   public interface IActivityManager extends IInterface {
                           ......
        public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
            String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags,
            ProfilerInfo profilerInfo, Bundle options) throws RemoteException; 
        public void finishSubActivity(IBinder token, String resultWho, int requestCode) 
        public void activityPaused(IBinder token) throws RemoteException;
        public void activityStopped(IBinder token, Bundle state,
            PersistableBundle persistentState, CharSequence description) throws RemoteException;
                            ......
}        

原理就是通過動態代理創建IActivityManager的代理類,再賦值給gDefault;

日誌保存

TalkingData對外提供了三個保存的接口

 1. TCAgent.onEvent(context, "event_ID", "事件標籤");

 2. Map<String, Object> map = new HashMap<String, Object>();
    map.put("遊戲類型", "益智遊戲");
    map.put("下載次數", 100000);
    map.put("price", number);
    TCAgent.onEvent(context, "event_ID", "event_LABEL", map);

 3. TCAgent.onError(context, exception);

日誌保存也使用單獨的線程,最終會保存在數據庫中,不同類型,保存在不同的表中。

private static final HandlerThread processingThread= new HandlerThread("ProcessingThread");

CREATE TABLE app_event(
           _id INTEGER PRIMARY KEY autoincrement,
           event_id TEXT,
           session_id TEXT,
           paramap BLOB);

CREATE TABLE error_report(
            _id INTEGER PRIMARY KEY autoincrement,
            error_time LONG,
            repeat INTERGER,
            shorthashcode TEXT);

CREATE TABLE activity (
            _id INTEGER PRIMARY KEY autoincrement,
            name TEXT,
            duration INTEGER,
            refer TEXT,
            realtime LONG);

CREATE TABLE session (
            _id INTEGER PRIMARY KEY autoincrement,
            session_id TEXT,
            start_time LONG,
            duration INTEGER,
            is_launch INTEGER,
            interval LONG, 
            is_connected INTEGER);

當然保存在數據庫中數據會進行加密,talkingData使用aes+base64的方式加密存儲,下面就是加密後的數據格式

occurtime = gT/eWPVCUNwdo27ijTY+DA==
event_id = T5GkG01BkfChcRqKfBoKLQ==
session_id = cGs8z2tU7emPtYnra01EbJuMZKh4RV2lbstIQU/o7oXXCQTC/sW9pdwNrUlwJymt    
event_label = l9Hm43j6KrFsMyS402s+ng==

日誌上傳

每次重新啓動或者間隔30s會觸發日誌上傳;
從數據庫中讀取日誌時,日誌會去重;
上傳的數據會進行gzip壓縮;

SELECT COUNT(_id), MAX(occurtime), event_id, event_label, paramap 
       from app_event 
       group by event_id, event_label, paramap

靈動分析功能

TalkingData還有一個很不錯的靈動分析功能,其原理主要通過下面的方法來實現對View的監聽;

public void sendAccessibilityEvent(int eventType) {
        if (mAccessibilityDelegate != null) {
            mAccessibilityDelegate.sendAccessibilityEvent(this, eventType);
        } else {
            sendAccessibilityEventInternal(eventType);
        }
    }

自實現統計sdk

  • 讀寫分離,讀和寫都在獨立的進程;

  • 數據庫加密存儲,aes+base64;

  • 日誌上傳
    1) 對日誌進行去重;
    2) 上傳時機:每次初始化後上傳日誌,日誌上傳間隔時間30s;
    3) gzip壓縮;

  • 數據格式
    上傳時,都轉爲json對象,並進行gzip壓縮;

    數據格式 說明
    event_id 事件類型
    event_lable 標籤
    map 詳情,key-value

  • map的基礎字段

    字段名 說明
    device_id 設備號
    device_cookie 安裝後生成的唯一表示,不卸載重裝不會改變
    network_type 網絡類型,3g、4g、wifi
    os 操作系統,android4.4.4
    package_name 包名,應用的唯一標識
    version 應用版本
    phone_model 手機型號
    net_operator 網絡運營商
    language 系統語言
    phone_number 手機號
    mac mac地址
    resolution 分辨率,480x800
    config_id 配置id
    create_time 日誌生成的時間
  • 幾種常見的事件類型

    event_id event_label map
    crash、xxException error “detail”=”堆棧信息”
    OpenGame、PauseGame、CreateRole、… custom “server”=”服務器”, “role_name”=”角色名”, “role_id”=”角色Id”“account”=”賬號”
    xx cmd “detail”=”命令詳情”

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章