初始化分析
初始化主要做了以下幾件事:
讀取配置信息appId、channeId,既可通過代碼設置appId、channelId,也可以在manifest中設置;如果兩個都設置的話,優先以AndroidManifest.xml爲主;
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”=”命令詳情”