TalkingData技术实现分析

    xiaoxiao2022-06-23  41

    初始化分析

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

    读取配置信息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、wifios操作系统,android4.4.4package_name包名,应用的唯一标识version应用版本phone_model手机型号net_operator网络运营商language系统语言phone_number手机号macmac地址resolution分辨率,480x800config_id配置idcreate_time日志生成的时间

    几种常见的事件类型

    event_idevent_labelmapcrash、xxExceptionerror“detail”=”堆栈信息”OpenGame、PauseGame、CreateRole、…custom“server”=”服务器”, “role_name”=”角色名”, “role_id”=”角色Id”“account”=”账号”xxcmd“detail”=”命令详情”
    转载请注明原文地址: https://ju.6miu.com/read-1123171.html

    最新回复(0)