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 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);
        }
    }

自实现统计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”=”命令详情”

你可能感兴趣的:(逆向分析)