初始化主要做了以下几件事:
读取配置信息appId、channeId,既可通过代码设置appId、channelId,也可以在manifest中设置;如果两个都设置的话,优先以AndroidManifest.xml为主;
Hook系统函数,监听Activity生命周期;
Android 4.0及以后版本,只需要向mActivityLifecycleCallbacks 添加一个callback即可
public class Application extends ContextWrapper implements ComponentCallbacks2 {
private ArrayList mComponentCallbacks =
new ArrayList();
private ArrayList mActivityLifecycleCallbacks =
new ArrayList();
private ArrayList 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 gDefault = new Singleton() {
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);
}
}
读写分离,读和写都在独立的进程;
数据库加密存储,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”=”命令详情” |