日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

网易NAPM Andorid SDK实现原理--转

發布時間:2025/4/5 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 网易NAPM Andorid SDK实现原理--转 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

原文地址:https://neyoufan.github.io/2017/03/10/android/NAPM%20Android%20SDK/

NAPM 是網易的應用性能管理平臺,采用非侵入的方式獲取應用性能數據,可以實時展示多個維度的分析結果。本文主要給大家分享一下Android端SDK的實現原理。

前言

APM(Application Performance Management),應用性能管理,主要是為了解決應用上線之后,性能問題難以發現、難以定位的問題,通過接入APM,可以實時了解應用在運行過程中的性能表現,快速定位和修復問題。

目前國內外有不少的應用性能管理平臺,例如國外的 New Relic、AppDynamics,國內的聽云、OneAPM,國內各大公司也都有自己的性能監控體系。

我們也開發了自己的平臺?NAPM?供公司內部的產品使用,移動端目前主要采集了網絡性能、交互性能和數據(數據庫、JSON、Image)處理性能數據,網絡性能目前主要采集了Http請求過程中的一些性能指標,比如響應時間、首包時間、DNS時間等,同時再結合機型、版本、地理位置、運營商、網絡環境等多個維度,就可以使用戶方便地了解應用在各種狀態下的性能表現,從而及時發現問題,做出適當的調整,達到優化用戶體驗的目的。

下圖是NAPM平臺某個應用的多維分析展示界面

Alt pic

接下來主要給大家分享一下網易NAPM Android端SDK的實現原理。

Android APM基本原理

簡單來說,一個APM平臺的工作流程大致如下:在各端(移動端、前端、后端)采集性能數據,然后上傳到后端進行建模、存儲,由平臺進行分析、挖掘,最后通過可視化的方式展示給用戶。

移動端SDK實際上只是一個數據采集系統,負責收集并上傳終端上產生的性能數據,大致可以劃分為三個模塊,最底層是數據采集模塊,負責采集各種性能數據,采集到的數據經過簡單的處理之后存儲在內存或者數據庫中,最上層是數據的消費模塊,通常會將采集到的數據上傳到后臺,供平臺存儲、分析和展示,同時我們也支持將采集到的性能數據交給用戶處理,方便用戶挖掘有用信息。

Alt pic

這里我們使用到了數據庫,主要是因為存在一些情況,會導致采集到的數據不能實時發送至后臺

  • 當網絡狀態較差,上傳失敗
  • 當前無可用網絡連接,無法上傳
  • 當前網絡狀態不滿足上傳條件(用戶可以設置,比如僅在wifi的狀態下上傳數據)

因此我們需要將數據進行存儲,在合適的時機上傳到后臺,盡量保證數據的完整。

APM SDK的難點是數據的采集,手動埋點的方式無疑是行不通的,一方面代價太大且容易產生錯誤,另一方面對于沒有源代碼的第三方庫我們無法直接修改,因而不能滿足我們的需求。參考New Relic,我們選擇在應用構建期間通過修改字節碼的方式來進行代碼插樁。

首先我們看一下應用構建的過程:

Alt pic

可以看到,應用中所有的class文件包括引用的第三方庫中的class,都會經由dex過程,被轉化為一個或者多個dex文件,正因為所有的class文件都會在dex這一步被處理,所以我們選擇在這里進行字節碼插樁。

javaagent + Instrumentation

dex的過程是在dx程序中進行,而dx程序是由java實現的,這里我們使用到了javaagent技術,它可以使我們在JVM加載class文件前對字節碼作出修改,這里簡單介紹一下用法,主要分為兩步

  • 實現一個javaagent
  • 加載javaagent
  • 實現javaagent

    javaagent的形式是一個jar包,根據javaagent的不同加載方式,對它的實現也有不同的要求。

    如果javaagent是在虛擬機啟動之后加載的,我們需要在它的manifest文件中指定Agent-Class屬性,它的值是javaagent的實現類,這個實現類需要實現一個agentmain方法

    public static void agentmain(String agentArgs, Instrumentation instrumentation) {//xxx }

    agentmain會成為javaagent的入口,它會在javaagent被加載時調用。

    但是如果javaagent是在JVM啟動時通過命令行參數加載的,情況會不太一樣,需要在它的manifest文件中指定Premain-Class屬性,它的值是javaagent的實現類,這個實現類需要實現一個premain方法。

    public static void premain(String agentArgs, Instrumentation instrumentation) {//xxx }

    我們知道,一個java程序的入口是main方法,而如果javaagent是在JVM啟動時通過命令行參數加載的,虛擬機會在應用的main方法執行之前調用javaagent的premain方法,這應該也是premain方法名字的由來吧。

    如果要支持兩種加載方式,那么上述的條件需要同時滿足。并且如果通過命令行參數在JVM啟動時加載,agentmain方法不會再被調用。而在這個時候,應用中的類還沒有被加載到虛擬機,所以給我們修改字節碼帶來了便利,因為一個類被加載之后,修改它的字節碼會比較麻煩。

    我們看到premain方法的第二個參數是一個Instrumentation的實例,Instrumentation接口有一個方法

    void addTransformer(ClassFileTransformer transformer, boolean canRetransform)

    它會在虛擬機中注冊一個ClassFileTransformer,transformer會在類加載時對類進行處理,ClassFileTransformer接口只定義了一個方法

    byte[] transform(ClassLoader loader,String className,Class<?> classBeingRedefined,ProtectionDomain protectionDomain,byte[] classfileBuffer)throws IllegalClassFormatException

    而這個方法的作用就是修改一個類的字節碼,className是這個類的名稱,classfileBuffer是這個類原本的字節碼,而返回值是修改過后的字節碼,如果沒有修改,可以直接返回null。

    因此,如果我們想在程序運行前改變一個類的字節碼,可以在javaagent的premain方法中調用Instrumentation的實例的addTransformer方法,添加一個自定義的ClassFileTransformer。偽代碼如下:

    //實現一個javaagent,注冊自定義的ClassFileTransformer public class MyJavaAgent { public static void premain(String agentArgs, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException { inst.addTransformer(new MyTransformer()); } }//實現一個 ClassFileTransformer,對xxx.xxx.xxx類的字節碼進行修改 public class MyTransformer implements ClassFileTransformer {public byte[] transform(ClassLoader classLoader, String className, Class<?> clazz,ProtectionDomain protectionDomain, byte[] bytes) throws IllegalClassFormatException {if(name.equals("xxx.xxx.xxx")) {return changeByteCode(bytes);}return null;} }

    加載javaagent

    前邊已經提到了javaagent有兩種加載方式

    1) JVM啟動時通過命令行參數加載javaagent

    • manifest中需要指定Premain-Class屬性
    • 需要實現premain方法
    • premain方法會在程序的main方法之前執行
    • agentmain方式不會被調用

      通過命令行加載javaagent的形式如下:

      -javaagent:jarpath[=options]

      一個示例如下:

      java -javaagent:/path/to/myagent.jar -jar myapp.jar

    2) JVM啟動后動態加載javaagent

    • manifest中需要指定Agent-Class屬性
    • 需要實現agentmain方法
    • agentmain方法會在javaagent被加載時執行

      一般運行時加載agent的方法如下:

      String nameOfRunningVM = ManagementFactory.getRuntimeMXBean().getName(); int p = nameOfRunningVM.indexOf('@'); String pid = nameOfRunningVM.substring(0, p);String jarFilePath = "/the/path/to/the/agent/jar";try {VirtualMachine vm = VirtualMachine.attach(pid);vm.loadAgent(jarFilePath);vm.detach(); } catch (Exception e) {throw new RuntimeException(e); }

    具體使用細節可參考VirtualMachine介紹http://docs.oracle.com/javase/7/docs/jdk/api/attach/spec/com/sun/tools/attach/VirtualMachine.html

    借助javaagent,我們可以將代碼插樁的工作分為兩個步驟:首先是獲取到應用中所有的字節碼,然后是對應用的字節碼進行修改。

    獲取應用字節碼

    首先從要解決的問題出發,上邊提到我們會在dex的這一步去獲取字節碼,通過查看dx程序的代碼,我們發現,在dex的過程中所有的class文件會經由com.android.dx.command.dexer.Main的processClass()方法進行處理,processClass()的代碼如下:

    /*** Processes one classfile.** @param name {@code non-null;} name of the file, clipped such that it* <i>should</i> correspond to the name of the class it contains* @param bytes {@code non-null;} contents of the file* @return whether processing was successful*/ private boolean processClass(String name, byte[] bytes) {if (! args.coreLibrary) {checkClassName(name);}try {new DirectClassFileConsumer(name, bytes, null).call(new ClassParserTask(name, bytes).call());} catch(Exception ex) {throw new RuntimeException("Exception parsing classes", ex);}return true; }

    第一個參數是應用中一個類的名字,第二個參數就是這個類的字節碼了,應用中所有的類,都會經過這個函數進行處理。

    所以我們打算修改com.android.dx.command.dexer.Main的processClass()方法,從而獲取到應用中的字節碼,那么現在的問題就變成了如何修改com.android.dx.command.dexer.Main的processClass()方法。

    掌握了javaagent,想要修改dx程序中com.android.dx.command.dexer.Main的字節碼就變得比較容易了,我們需要實現一個javaagent,在其中注冊一個ClassFileTransformer,在ClassFileTransformer的transform()方法中對com.android.dx.command.dexer.Main的字節碼進行修改,最后在dx程序啟動時將這個javaagent加載進去就好了。

    //實現一個 ClassFileTransformer,對com.android.dx.command.dexer.Main類的字節碼進行修改 public class MainTransformer implements ClassFileTransformer {public byte[] transform(ClassLoader classLoader, String className, Class<?> clazz,ProtectionDomain protectionDomain, byte[] bytes) throws IllegalClassFormatException {if(name.equals("com/android/dx/command/dexer/Main")) {return changeMainClassByteCode(bytes);}return null;} }byte[] changeMainByteCode(byte[] bytes) {//修改Main的 processClass() 方法//返回修改后Main的字節碼 }

    如果你是通過命令行來手動構建應用的,到這里已經可以用上邊的方式獲取到應用中的字節碼了,然而大多數人在開發Android的時候,并不會通過命令行去手動構建,而是通過使用一些構建工具,來完成自動化構建,而dx程序則是由構建工具啟動的,所以我們面臨的問題就是如何將javaagent加載到dx進程。

    我們目前支持了ant構建和gradle構建,通過查看ant和gradle的代碼,我們發現最終它們都會通過java.lang.ProcessBuilder的start()方法來啟動dx進程。

    通過查看java.lang.ProcessBuilder的代碼,我們發現它有一個成員

    private List<String> command;

    它是用來保存的是啟動目標進程的命令和參數,我們需要做的就是在調用start()方法啟動dx進程時,將加載javaagent的參數(-javaagent:jarpath[=options])添加到command中。

    這里我們仍然使用javaagent來完成這個工作,我們需要實現另外一個javaagent,在其中注冊一個另一個ClassFileTransformer,在它的transform方法中對java.lang.ProcessBuilder的字節碼進行修改。

    //實現一個 ClassFileTransformer,對com.android.dx.command.dexer.Main類的字節碼進行修改 public class ProcessBuilderTransformer implements ClassFileTransformer {public byte[] transform(ClassLoader classLoader, String className, Class<?> clazz,ProtectionDomain protectionDomain, byte[] bytes) throws IllegalClassFormatException {if(name.equals("java/lang/ProcessBuilder")) {return changeProcessBuilderClassByteCode(bytes);}return null;} }byte[] changeProcessBuilderClassByteCode(byte[] bytes) {//修改ProcessBuilder的 start() 方法//返回修改后ProcessBuilder的字節碼 }

    那么最終問題就變成了如何把這個javaagent加載到ant進程和gradle進程。

    它們對應到了javaagent的兩種加載方式

    • ant構建-JVM啟動時加載

      export ANT_OPTS="-javaagent:/path/to/agent.jar"(mac os環境,windows不太一樣)

      在ant構建前進行上述配置,可以在啟動ant時加載指定的javaagent,這里使用的是在JVM啟動時通過命令行參數加載javaagent的方式。

    • gradle構建 -JVM啟動后加載

      我們會編寫一個gradle插件來完成javaagent的加載,當我們的插件被加載時,gradle進程已經運行起來了,因此只能通過動態的方式加載javaagent。

    因此,獲取字節碼的流程,大致如下圖所示:
    Alt pic

    這個過程中主要使用了兩個javaagent,一個用來修改ProcessBuilder類,另一個用來修改Main類,涉及到的進程是ant構建進程或者gradle構建進程,以及由它們啟動的dx進程。

    對于gradle構建方式,需要注意一點,gradle plugin 在2.1.0之后的版本,支持dx in-process,它使得dx的過程可以直接在當前的gradle進程中執行,而不需要額外啟動一個dx進程,從而縮短應用構建的時間。如果你在使用Android Studio構建應用的時候看到To run dex in process, the Gradle daemon needs a larger heap. It currently has 910 MB這樣的一句話,它就是指導用戶通過配置gradle daemon進程的堆大小來開啟dx in-process特性的。

    而這個新的特性,會給我們設置javaagent帶來麻煩,不啟動dx進程使得我們無法對dx進程設置javaagent,而在gradle進程中動態加載javaagent時,com.android.dx.command.dexer.Main類早已經加載過了,所以通過javaagent方式來獲取字節碼會變得十分困難。

    幸運的是,gradle plugin 在1.5.0之后,提供了一個Transform API,它允許第三方插件操作編譯后的class文件,而修改的時機正是在將這些字節碼轉換為dex文件之前,這里就不在展開講解了,感興趣的同學可以參考下這篇文章http://blog.csdn.net/sbsujjbcy/article/details/50839263。

    修改應用字節碼

    通過javaagent修改com.android.dx.command.dexer.Main和java.lang.ProcessBuilder,以及最終修改應用的字節碼進行插樁,都需要對.class文件的格式以及java虛擬機有比較深入的了解,另外需要使用字節碼操作工具來幫助我們對字節碼進行改造,這里不詳細講解,只是推薦一些有用的的字節碼操作框架和工具,后邊可能會有同事做相關的分享。

    • ASM是一個 Java 字節碼操控框架。它能被用來動態生成類或者增強既有類的功能。ASM 可以直接產生二進制 class 文件,也可以在類被加載入 Java 虛擬機之前動態改變類行為。

    • Javassist是一個開源的分析、編輯和創建Java字節碼的類庫,它提供了源碼級別的API以及字節碼級別的API,源碼級別的API,直接使用java編碼的形式,而不需要深入了解虛擬機指令,就能動態改變類的結構或者動態生成類。

    • Bytecode Outline plugin for Eclipse是一個非常有用的eclipse 插件,可以查看當前正在編輯的java文件或者class文件的字節碼。

    • 如果需要逆向APK,查看字節碼修改的效果,除了dex2jar外,再給大家推薦一個google的逆向工具enjarify。

    小結

    本文重點介紹了使用javaagent在應用打包過程中修改com.android.dx.command.dexer.Main和java.lang.ProcessBuilder的字節碼,從而獲取到應用的字節碼,進行插樁的基本原理,并沒有涉及so hook相關的原理,以后有機會的話會再做一次分享。

    轉載于:https://www.cnblogs.com/davidwang456/p/7550789.html

    總結

    以上是生活随笔為你收集整理的网易NAPM Andorid SDK实现原理--转的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 九七影院在线观看免费观看电视 | 国产麻豆剧传媒精品国产 | www.xxx国产 | 久久久久久久黄色片 | 少妇精品无码一区二区免费视频 | 野花视频在线观看免费 | 亚洲一区二区三区加勒比 | 国产伦精品一区二区三区四区视频 | 最新国产毛片 | 依依成人在线 | 色先锋av资源| 少妇在线 | 日本黄色一区 | 亚州精品毛片 | 亚洲天堂性 | 一区亚洲| 日产精品久久久久久久 | 日本草草视频 | 亚洲综合视频网站 | 亚洲乱码中文字幕 | 国产男女无套 | 欧美日本精品 | 欧美123| 国产哺乳奶水91在线播放 | 精品在线小视频 | 黄色三级免费网站 | 亚洲 欧美 日韩系列 | 亚欧美日韩 | 91国内精品 | videosex抽搐痉挛高潮 | www日本高清视频 | 欧美成人免费一级人片100 | 中文字幕35页 | 国产调教打屁股xxxx网站 | 91成年版 | 激情插插插 | 毛片毛片毛片毛片毛片毛片毛片毛片毛片 | 超碰最新网址 | 绿帽人妻精品一区二区 | 女人叫床很黄很污句子 | 亚洲一级理论片 | 九九久久精品视频 | 欧美精品久久久久久久久 | 少妇免费看 | 麻豆av免费看 | 亚洲少妇激情 | 一女双乳被两男吸视频 | 胖女人毛片 | 久久人妻无码aⅴ毛片a片app | 成人在线免费观看网站 | 日本va视频 | 日本视频一区二区 | 91精品国产91久久久久久久久久久久 | 精品午夜福利视频 | 国产精品啊啊啊 | 国产极品在线观看 | 美女爽爽爽 | 黄色在线观看网站 | 欧美天天爽 | 亚洲一区第一页 | 日韩在线观看视频一区 | 高潮流白浆在线观看 | 亚洲天堂男人天堂 | 国语对白一区二区三区 | 国产精品一区二区三 | 免费黄色小说视频 | av最新资源 | 少妇精品偷拍高潮白浆 | 激情视频免费在线观看 | 日本做爰高潮又黄又爽 | 亚洲制服av | 色夜av| 男女交性视频播放 | 91漂亮少妇露脸在线播放 | 精品国产免费视频 | 精品日韩制服无码久久久久久 | 亚洲美女综合网 | 99在线小视频 | 九九久久精品视频 | 91激情| 色牛影院| 国产精品无码一本二本三本色 | 少妇精品无码一区二区免费视频 | 日韩精品国产AV | 在线一级视频 | 国内精品在线播放 | 欧美三级黄色大片 | 重囗味sm一区二区三区 | 欧美bbw视频 | 三级五月天 | 欧美国产在线观看 | 天天看片天天射 | 黑人干亚洲人 | 欧美精品手机在线 | www.国产视频.com | 午夜成人影视 | 天堂va欧美ⅴa亚洲va一国产 | 成人免费区一区二区三区 | 激情婷婷网 |