摘要:一埋點架構(gòu)設(shè)計埋點的核心邏輯抽象將生產(chǎn)的用戶數(shù)據(jù)組織發(fā)送給服務(wù)器。普通埋點定義頁面進入,頁面離開,頁面元素點擊,頁面元素曝光。無埋點進入退出都使用,如何區(qū)分增加了一個字段,用表示頁面進入退出。如何修改字節(jié)碼庫基礎(chǔ)使用。
一、埋點架構(gòu)設(shè)計
埋點的核心邏輯抽象:將“APP生產(chǎn)”的“用戶數(shù)據(jù)”組織“發(fā)送給服務(wù)器”。
1.Producer是APP,生產(chǎn)各種用戶數(shù)據(jù)。
2.Consumer是埋點系統(tǒng)的數(shù)據(jù)上傳模塊,把各種用戶數(shù)據(jù)上傳給服務(wù)器。
3.MetaData是對用戶數(shù)據(jù)的抽象。
4.Queue是存儲用戶數(shù)據(jù)的隊列。
將抽象邏輯分解為各個子模塊并拼裝,形成最終的架構(gòu)。
1.MetaData模塊:將用戶行為抽象為數(shù)據(jù)描述。
2.Producers模塊:生產(chǎn)用戶數(shù)據(jù),將用戶行為轉(zhuǎn)化為數(shù)據(jù)集。
3.Consumers模塊:消費用戶數(shù)據(jù),將用戶數(shù)據(jù)上傳給服務(wù)器。
4.Storage模塊:存儲用戶數(shù)據(jù),將用戶數(shù)據(jù)暫存在文件中。
5.Broker模塊:管理用戶數(shù)據(jù)。
關(guān)鍵是特征值的提取。
設(shè)備基礎(chǔ)屬性:
設(shè)備id:udid=12345678910
APP基礎(chǔ)屬性:
版本:v=8.5.0
渠道:c=xiaomi
用戶基礎(chǔ)屬性:
用戶id:ucid=12345678910
頁面元素描述:
屬于哪個APP:pid=bigc_app_xinfang
屬于哪個頁面:key=newhouse/homeindex
屬于哪個頁面元素:為頁面元素定義唯一的code
頁面與頁面關(guān)聯(lián)邏輯:
從哪個頁面進入當前頁面:f=newhouse/homeindex
頁面停留時間:stt=1000
用戶行為描述:
用戶基礎(chǔ)行為描述:evt=xxx,如APP啟動/退出,頁面進入/離開/滑動,頁面元素點擊/曝光,push到達/點擊
用戶擴展行為描述:action=json,如action={"project_name":"thyhwabktj", "xinfangapp_click":"10020"}
舉例:
{v=1.1.6, ts=1527067845806, ucid=null, ssid=b032222c-7105-4119-9bfe-a1aec5ba9285, pid=bigc_app_xinfang, key=newhouse/project, action={"sample_mark":"","project_name":"thyhwabktj"}, longitude=0.0, latitude=0.0, cid=110000, f=newhouse/homeindex?project_name=thyhwabktj&city_id=110000, stt=685, evt=2}
LJ由于歷史原因,有兩套MetaData
無埋點evt定義:app啟動/退出=5,頁面進入/退出=6,頁面滑動=8,頁面元素點擊=7,push到達/點擊=9。
普通埋點evt定義:頁面進入=1,3,頁面離開=2,頁面元素點擊=10186,頁面元素曝光=11316。
Q:無埋點進入/退出都使用6,如何區(qū)分?
A:增加了一個status字段,用status=0/1表示頁面進入/退出。
建議:統(tǒng)一dig埋點和無埋點的evt定義
1.內(nèi)存緩存(List):每生產(chǎn)一條數(shù)據(jù)都會先進入內(nèi)存緩存
2.數(shù)據(jù)庫存儲(DataBase):提供對數(shù)據(jù)庫的操作接口
1.接收生產(chǎn)者的數(shù)據(jù),并寫入數(shù)據(jù)庫(對外提供put方法)
Q:如何控制數(shù)據(jù)由內(nèi)存寫入數(shù)據(jù)庫時機?
1.1.內(nèi)存數(shù)據(jù)超過一定數(shù)量(如20條)時,缺點是應(yīng)用在后臺被殺,最多可能丟失20條數(shù)據(jù)
1.2.生命周期onPause時,缺點是寫操作相對比較頻繁
1.3.利用定時器,缺點是后臺定時任務(wù)可能不執(zhí)行,導(dǎo)致丟數(shù)據(jù)
LJ現(xiàn)行方案:1.1+1.3
建議使用:1.1+1.2
Q:多進程寫數(shù)據(jù)庫怎么辦?
A:多進程向sqlite插入數(shù)據(jù)不會有問題,只是插入順序是亂序的;如果要保證插入順序也一致,可以考慮啟動一個獨立進程操作sqlite,其它進程與sqlite所在進程進行通信。
2.讀取數(shù)據(jù)庫中的數(shù)據(jù),并供給消費者消費(對外提供aquire/release方法)
Q:如何控制數(shù)據(jù)提供給消費者消費的時機?
A:生命周期onPause時,缺點是消費相對比較頻繁
申請(aquire)數(shù)據(jù),消費(upload)數(shù)據(jù),釋放(release)數(shù)據(jù)
建議:LJ目前的代碼可以參考此設(shè)計進行代碼優(yōu)化
普通埋點數(shù)據(jù)生產(chǎn)
Producers模塊對外提供各種封裝好的add方法供開發(fā)者調(diào)用,如addClickEvent(), addPageEnterEvent(), addPageLeaveEvent()等。
無埋點數(shù)據(jù)生產(chǎn)
Producers模塊自動生產(chǎn)各種埋點數(shù)據(jù),如app啟動/退出,頁面進入/退出/滑動,頁面元素點擊,push到達/點擊等。
由于歷史原因,LJ總共有2個埋點庫:
dig庫:LJ-APP&BK-APP均在使用,用于對無埋點無法處理的特殊數(shù)據(jù)進行補充
無埋點庫:LJ-APP用的老版本(pid無法自定義,主工程和插件共用主工程的pid),BK-APP用的新版本(pid可以自定義,主工程和插件工程可以分別定義自己的pid)
建議:兩庫合并
無埋點的核心是,如何通過代碼自動搜集想要的信息:
1.設(shè)備、APP、用戶等基礎(chǔ)屬性,直接通過api獲取
2.Activity進入/離開等生命周期相關(guān)屬性,直接通過LifeCycleCallback監(jiān)聽獲取
3.Activity的唯一標記如pageId等屬性,直接通過注解獲取
4.UI元素點擊/滑動等行為屬性,需要通過hook代碼才能實現(xiàn)
如何確定UI元素的唯一性:
方案1:為需要統(tǒng)計的元素定義唯一的code,寫入contentDescription,然后讀取這個屬性
方案2:利用ViewTree中的ViewPath唯一確定一個UI元素
核心代碼:
public static ViewPath getPath(View view) { do { //1.構(gòu)造ViewPath中于view對應(yīng)的節(jié)點:ViewType[index] ViewType = view.getClass().getSimpleName(); index = view在兄弟節(jié)點中的index; ViewPath節(jié)點 = ViewType[index]; } while ((view = view.getParent()) instanceof View);//2.將view指向上一級的節(jié)點 }
結(jié)果示例:
DecorView/LinearLayout[0]/FrameLayout[0]/ActionBarOverlayLayout[0]/ContentFrameLayout[0]/FrameLayout[0]/LinearLayout[0]/ViewPager[0]/ButtonFragment[0]/AppCompatButton[0]
ViewPath可讀性問題:
可以建立一個Mapping文件,將ViewPath和描述Describe對應(yīng)起來,具體實現(xiàn):
寫一個工具,當我們在手機上點擊一個按鈕的時候彈出彈窗,輸入Describe描述文字,最終生成一個ViewPath<->Describe的Mappting文件。
1.元數(shù)據(jù)metadata與注解annotation
Java中總共有4種類型:類Class、接口Interface、枚舉Enum、元數(shù)據(jù)@interface(就是注解)。
元數(shù)據(jù):是添加到包、類、方法、屬性上的額外信息,對其進行描述,如@Override。
元注解:是最基本的注解:@Target、@Retention、@Documented、@Inherited
@Target取值:PACKAGE、TYPE、FIELD、METHOD
@Retention取值:SOURCE、CLASS、RUNTIME
注解的作用:編譯時可獲取到注解信息動態(tài)生成代碼,運行時科獲取到注解信息做特殊處理。
2.運行時注解
在運行時通過反射對注解進行處理,比較消耗資源,性能較差。
定義:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Inherited public @interface MethodInfo { ? ????String author() default "xiaoming"; ? ????String date(); ? ????int version() default 1; }
使用:
public class App { ? ????@MethodInfo( ????????author = “xiaoming@gmail.com”, ????????date = "2018/05/10", ????????version = 2) ????public String getAppName() { ????????return "trinea"; ????} }
解析:
Class cls = Class.forName("com.lianjia.test.annotation.App"); for (Method method : cls.getMethods()) { MethodInfo methodInfo = method.getAnnotation(MethodInfo.class); System.out.println(“method author: ” + methodInfo.author()); }
3.編譯時注解
在編譯時通過Java Annotation Process技術(shù)對注解進行處理,因為不使用反射,所以性能較好
模擬ButterKnife定義:
@Retention(CLASS) @Target(FIELD) public @interface InjectView { ??int value(); }
模擬ButterKnife調(diào)用:
@InjectView(R.id.user) EditText username;
模擬ButterKnife處理:
@SupportedAnnotationTypes({"com.lianjia.InjectView "}) @SupportedSourceVersion(SourceVersion.RELEASE_7) public class MyProcessor extends AbstractProcessor { @Override public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) { for (TypeElement typeElement : annotations) { // 遍歷annotations獲取annotation類型 for (Element element : roundEnv.getElementsAnnotatedWith(typeElement)) { // 使用roundEnv.getElementsAnnotatedWith獲取所有被某一類型注解標注的元素,依次遍歷 // 在元素上調(diào)用接口獲取注解值 int annoValue = element.getAnnotation(TestAnnotation.class).value(); String annoWhat = element.getAnnotation(TestAnnotation.class).what(); System.out.println("value = " + annoValue); System.out.println("what = " + annoWhat); // 向當前環(huán)境輸出warning信息 processingEnv.getMessager().printMessage(Kind.WARNING, "value = " + annoValue + ", what = " + annoWhat, element); } } return false; } }十、Hook代碼實現(xiàn)搜集用戶點擊數(shù)據(jù)
1.Android編譯運行全流程
2.在onClick(View view)方法中加入埋點邏輯
2.1.編寫埋點邏輯:由埋點sdk(LianjiaAnalyticsSdk)完成
2.2.將埋點邏輯插入onClick(View view)方法中:由埋點插件(LianjiaAnalyticsPlugin)完成
3.埋點插件編寫
1.插件編寫流程?plugin編寫。
2.如何侵入編譯流程?transform庫基礎(chǔ)使用。
4.如何修改字節(jié)碼?Javassist庫基礎(chǔ)使用。
4.核心代碼
編寫埋點邏輯:
public class AnalyticsEventsBridge { /** * Hook onClick(View view)方法,并調(diào)用此方法 */ public static void onViewClick(@Nullable View view) { // 獲取view的唯一標記等相關(guān)信息 // 生成一條埋點日志并寫入 } }
編寫插件:
1.插件項目目錄結(jié)構(gòu)
2.build.gradle修改
apply plugin: "groovy" dependencies { compile gradleApi() compile "com.android.tools.build:gradle:2.3.3" compile "org.javassist:javassist:3.21.0-GA" } apply from: "./gradle-mvn-push.gradle" apply plugin: "maven-publish" publishing { publications { mavenJava(MavenPublication) { groupId PROJ_GROUP artifactId PROJ_ARTIFACTID version PROJ_VERSION from components.java } } }
3.插件執(zhí)行入口,相當于Main函數(shù)
class AnalyticsPlugin implements Plugin{ @Override void apply(Project project) { InjectAndJarMergingTransform transform = new InjectAndJarMergingTransform() android.registerTransform(transform) } }
transform侵入編譯流程:
public class InjectAndJarMergingTransform extends Transform { @Override public void transform(@NonNull TransformInvocation invocation) throws TransformException, IOException { println("LianjiaJarMergingTransform, begin"); //這里可以獲取到文件的輸入/輸出信息,并對其做相應(yīng)的更改,核心抽象為1個方法 processClass(inputStream, outputStream); println("LianjiaJarMergingTransform, end"); } }
Javassist修改字節(jié)碼:
private void processClass(InputStream inputStream, OutputStream outputStream) throws IOException { final ClassPool classPool = AndroidClassPool.getClassPool() final CtClass clazz = classPool.makeClass(inputStream) final CtMethod ctMethod try { ctMethod = ctClass.getMethod(targetMethodName, targetMethodDescriptor); } catch (NotFoundException e) { xxxxxxxx } //通過過濾器,找到android.view.View$OnClickListener的onClick(View view)方法,略 //Hook調(diào)用AnalyticsEventsBridge.onViewClick(view)方法 ctMethod.insertBefore("""com.lianjia.sdk.analytics.gradle.AnalyticsEventsBridge.onViewClick($1);""") //其中$0=this, $1=$args[0]表示方法的第一個參數(shù) }
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/69519.html
摘要:全稱應(yīng)用性能管理監(jiān)控后面我會通過一系列的文章來介紹的原理框架設(shè)計與實現(xiàn)等等。在應(yīng)用構(gòu)建期間,通過修改字節(jié)碼的方式來進行字節(jié)碼插樁就是實現(xiàn)自動化的方案之一。 showImg(https://segmentfault.com/img/bVbbRX6?w=1995&h=1273); 歡迎關(guān)注微信公眾號:BaronTalk,獲取更多精彩好文! 一. 前言 性能問題是導(dǎo)致 App 用戶流失的罪魁...
閱讀 1011·2021-11-22 13:52
閱讀 1447·2021-11-19 09:40
閱讀 3166·2021-11-16 11:44
閱讀 1274·2021-11-15 11:39
閱讀 3902·2021-10-08 10:04
閱讀 5362·2021-09-22 14:57
閱讀 3101·2021-09-10 10:50
閱讀 3184·2021-08-17 10:13