{"id":33210046,"url":"https://github.com/zkwlx/DroidTelescope","last_synced_at":"2025-11-21T05:02:06.683Z","repository":{"id":84035565,"uuid":"85302329","full_name":"zkwlx/DroidTelescope","owner":"zkwlx","description":"DroidTelescope（DT），Android端App性能监控框架","archived":false,"fork":false,"pushed_at":"2022-04-21T02:39:54.000Z","size":14635,"stargazers_count":262,"open_issues_count":1,"forks_count":46,"subscribers_count":14,"default_branch":"master","last_synced_at":"2024-07-22T23:48:13.298Z","etag":null,"topics":["android","monitor","performance","performance-analysis","performance-monitoring","systrace","telescope","trace"],"latest_commit_sha":null,"homepage":"","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zkwlx.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2017-03-17T10:59:46.000Z","updated_at":"2023-12-22T10:07:55.000Z","dependencies_parsed_at":"2023-03-06T05:45:30.795Z","dependency_job_id":null,"html_url":"https://github.com/zkwlx/DroidTelescope","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/zkwlx/DroidTelescope","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zkwlx%2FDroidTelescope","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zkwlx%2FDroidTelescope/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zkwlx%2FDroidTelescope/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zkwlx%2FDroidTelescope/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zkwlx","download_url":"https://codeload.github.com/zkwlx/DroidTelescope/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zkwlx%2FDroidTelescope/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":285560043,"owners_count":27192467,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-21T02:00:06.175Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","monitor","performance","performance-analysis","performance-monitoring","systrace","telescope","trace"],"created_at":"2025-11-16T11:00:26.438Z","updated_at":"2025-11-21T05:02:06.671Z","avatar_url":"https://github.com/zkwlx.png","language":"HTML","funding_links":[],"categories":["Articles"],"sub_categories":["Network Profiling Tools"],"readme":"\u003cimg src=\"https://github.com/zkwlx/DroidTelescope/blob/master/wiki/logo.png\" width=\"50px\" /\u003e DroidTelescope（DT）\n=======\n这是一套Android端线上应用性能监控框架，目前支持卡顿监控、内存泄露监控；后续还会增加更多监控对象。此项目参考自开源项目[BlockCanaryEx](https://github.com/seiginonakama/BlockCanaryEx)。\n\n## 框架简介\n* 支持方法耗时追踪，提供 startXX 和 endXX 接口，并支持框架自研和 Android SysTrace 两种实现\n* 框架支持卡顿监控，当发生卡顿时会记录所有方法的调用耗时和调用栈\n* 框架支持内存泄露监控，当发生内存泄露时会记录用户的交互行为和页面的创建关系（为了提高性能，内存泄露并不会dump内存）\n* 框架支持用户交互行为的监控，为其他监控提供支持，比如内存泄露（交互监控还未开发完全）\n* 框架会在编译时进行代码注入，所以对apk的性能会有一点影响，具体影响范围会在下面介绍。\n\n## 架构图\n\u003cbr\u003e![](https://github.com/zkwlx/DroidTelescope/blob/master/wiki/DroidTelescope%E6%9E%B6%E6%9E%84%E5%9B%BE.png \"整体架构\")\n\n## 使用效果\n### 方法调用追踪（新功能）\n可以通过接口 DroidTelescope.startMethodTracing 和 DroidTelescope.stopMethodTracing 追踪方法的调用栈的耗时，\n例如想要监控 App 的启动耗时方法，可以在 App 中加入如下代码：\n```java\npublic class MyApplication extends Application {\n    ...\n    @Override\n    protected void attachBaseContext(Context base) {\n        super.attachBaseContext(base);\n        DroidTelescope.install();\n        DroidTelescope.startMethodTracing();\n    }\n    ...\n}\n \npublic class MainActivity extends Activity {\n    ...\n    @Override\n    public void onWindowFocusChanged(boolean hasFocus) {\n        super.onWindowFocusChanged(hasFocus);\n        String path = DroidTelescope.stopMethodTracing(this.getApplicationContext());\n        // 打印日志路径。使用 SysTrace 时框架不会主动生成日志文件，由 Android 提供的 systrace.py 工具生成\n        if (!TextUtils.isEmpty(path)) {\n            Log.i(\"zkw\", \"加载完成:::\u003e\" + path);\n        } else {\n            Log.i(\"zkw\", \"Path is null, maybe use Systrace.\");\n        }\n    }\n    ...\n}\n```\n### 使用框架自研模块追踪效果\n之后按如下几部打开报告文件：\n* 生成的日志文件 trace_html_report.zip 从设备中取出并解压\n* 用浏览器打开 trace_html_report/index.html 文件\n\n报告的效果和分析方法如下：\n\n\u003cbr\u003e![](https://github.com/zkwlx/DroidTelescope/blob/master/wiki/TracesDemo.png)\n简单说明下两个时长的意义：\n* 时钟时长，表示方法执行所消耗的时钟时间，即使方法没有占用 cpu，仅等待另一线程的完成，时长也会被记录。\n* Cpu 时长，表示方法执行所消耗的 cpu 时间，当方法没有占用 cpu 时，时间不会被记录。\n\n相比 Google 官方提供的检测方法，DroidTelescope 框架有如下优点：\n* 报告规模、方法数量可控，不会被大量系统方法干扰；\n* 性能损耗较小，可针对某些需求应用到线上；\n* 高度可定制，可以根据 App 特点定制框架（例如想监控 Fragment 的创建耗时）；\n\n### 使用 SysTrace 追踪效果\n详细使用方式请参考[官方文档](https://developer.android.com/studio/command-line/systrace)，框架中使用 SysTrace 方式如下：\n```java\n    public void init() {\n        DroidTelescope.install(new MyConfig());\n    }\n    ...\n    class MyConfig extends Config {\n        @Override\n        public boolean useSysTrace() {\n            return true;\n        }\n    }\n    ...\n```\n使用 SysTrace 后，DT 框架会在每个方法的开始和结束调用 Trace.beginSection(方法签名) 和 Trace.endSection()，并维护调用栈关系。\n在开始追踪之前，运行命令：\n```shell\nsystrace.py -o output.html --app=app进程名 app\n```\n结束后用浏览器打开 output.html，效果如下图所示。\n\u003cbr\u003e![](https://github.com/zkwlx/DroidTelescope/blob/master/wiki/systrace_demo.png)\n\n### 卡顿监控\n当发生卡顿时，框架会记录相关方法的调用时间和调用栈，并生成BlockInfo对象，使用框架提供的ConvertUtils工具将BlockInfo对象转换成JSON格式的日志，如下例子，每个字段的意义请看注释：\n```js\n{\n    \"loop_wall_clock_time\":319,//表示一次loop所消耗的时钟时长，单位是ms毫秒\n    \"loop_cpu_time\":47,//表示一次loop所消耗的cpu时长，单位是ms毫秒\n    \"invoke_trace_array\":[//表示一次loop记录的耗时方法调用关系\n        {\n            \"method_signature\":\"plugin.gradle.my.SecondActivity.onCreate(android.os.Bundle)\",\n            \"thread_id\":1,\n            \"wall_clock_time\":31.06,\n            \"cpu_time\":20\n        },\n        {\n            \"method_signature\":\"plugin.gradle.my.BlankFragment.onCreateView(android.view.LayoutInflater,android.view.ViewGroup,android.os.Bundle)\",\n            \"thread_id\":1,\n            \"wall_clock_time\":5.74,\n            \"cpu_time\":5\n        },\n        {\n            \"method_signature\":\"plugin.gradle.my.SecondActivity.onResume()\",//方法的签名\n            \"thread_id\":1,//方法调用时所在线程id\n            \"wall_clock_time\":257.77,//方法调用消耗的时钟时长，单位是ms毫秒\n            \"cpu_time\":8,//方法调用消耗的cpu时长，单位是ms毫秒\n            \"invoke_trace\":[//记录在当前方法中调用的其他子方法\n                {\n                    \"method_signature\":\"plugin.gradle.my.dummy.DummyContent.\u003cclinit\u003e()\",\n                    \"thread_id\":1,\n                    \"wall_clock_time\":6.62,\n                    \"cpu_time\":6\n                },\n                {\n                    \"method_signature\":\"plugin.gradle.my.dummy.DummyContent.sleep()\",\n                    \"thread_id\":1,\n                    \"wall_clock_time\":250.18,\n                    \"cpu_time\":0\n                }\n            ]\n        }\n    ]\n}\n```\n框架不会记录所有方法，只有当方法耗时超过阈值时（可以配置）记录，日志中所有的time单位都是ms毫秒。\n触发这次卡顿的源码结构是这样的：\n```java\npublic class SecondActivity extends AppCompatActivity {\n\n    @Override\n    protected void onCreate(@Nullable Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.second_layout);\n\n        FragmentManager fm = getSupportFragmentManager();\n        FragmentTransaction tx = fm.beginTransaction();\n        BlankFragment blankFragment = new BlankFragment();\n        tx.add(R.id.id_content, blankFragment, \"ONE\");\n        tx.commit();\n\n        blankFragment.onLowMemory();\n    }\n\n    @Override\n    protected void onResume() {\n        super.onResume();\n        DummyContent d = new DummyContent();\n        d.sleep();\n    }\n    \n}\n```\n```java\npublic class DummyContent {\n    public void sleep() {\n        try {\n            Thread.sleep(250);\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n        }\n    }\n}\n```\n### 内存泄露监控\n下面模拟一个内存泄露的环境：MainActivity启动SecondActivity，SecondActivity添加一个BlankFragment，BlankFragment会导致泄露，泄露代码如下：\n```java\npublic class BlankFragment extends Fragment {\n\n    public static List\u003cActivity\u003e ins = new ArrayList\u003c\u003e();\n\n    @Override\n    public void onAttach(Activity activity) {\n        super.onAttach(activity);\n        ins.add(activity);\n    }\n}\n```\n当发生内存泄露时（目前只支持监控Activity和Fragment的引用泄露），会创建LeakInfo对象，使用框架提供的ConvertUtils工具将LeakInfo对象转换成Json格式，如下例子，每个字段的意义请看注释：\n```js\n{\n    \"garbage_reference_list\":[//怀疑是泄露对象的列表\n        {\n            //泄露对象的id，通过Object.toString()生成\n            \"objectId\":\"BlankFragment{1324e62}\",\n            //泄露对象的调用链，只记录Activity、Fragment之间的调用关系\n            \"object_create_chain\":\"plugin.gradle.my.MainActivity@2695ae7-\u003eplugin.gradle.my.SecondActivity@6cce780-\u003eBlankFragment{1324e62 #0 id=0x7f0b006f ONE}\"\n        },\n        {\n            \"objectId\":\"plugin.gradle.my.SecondActivity@6cce780\",\n            \"object_create_chain\":\"plugin.gradle.my.MainActivity@2695ae7-\u003eplugin.gradle.my.SecondActivity@6cce780\"\n        }\n    ]\n}\n```\n## 使用方法\n（不知为何bintray一直不通过我的包，所以jcenter上还没有插件包，可以先使用本地编译，见谅）\n\u003cbr\u003e框架会在编译期间注入代码，首先配置代码注入的插件，将repo目录复制到你自己项目的根目录，在项目app的build.gradle文件中加入如下代码：\n```groovy\nbuildscript {\n    repositories {\n        maven {\n            url uri('../repo')\n        }\n        jcenter()\n    }\n    dependencies {\n        classpath 'andr.perf.monitor:TelescopeInjector:0.9.0'\n    }\n}\napply plugin: 'telescope.injector'\n```\n然后项目添加对DroidTelescope库的依赖，可以直接使用项目目录下的DroidTelescope_v0.8.0_xxxxxx.jar包，\n添加完后，大致是这个样子：\n\u003cbr\u003e![](https://raw.githubusercontent.com/zkwlx/DroidTelescope/master/wiki/demo.png \"项目配置图例\")\n\n然后再代码中配置监控框架，建议在自定义的Application.onCreate中配置，示例如下：\n```java\npublic class MyApplication extends Application {\n\n    // 需要自定义配置项时，重写Config的相应方法\n    private Config config = new AndrPerfMonitorConfig();\n\n    // 设置监听器，当发生卡顿或者内存泄露时回调\n    private DroidTelescope.BlockListener blockListener = new MyBlockListener();\n    private DroidTelescope.LeakListener leakListener = new MyLeakListener();\n\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        // 设置自定义配置\n        DroidTelescope.install(config);\n        // 设置监听器，当发生卡顿或者内存泄露时回调\n        DroidTelescope.setBlockListener(blockListener);\n        DroidTelescope.setLeakListener(leakListener);\n    }\n\n    //自定义配置类，本例没有自定义配置，所以没有重写任何方法\n    private static class AndrPerfMonitorConfig extends Config {\n        \n    }\n\n    //卡顿监听器，当发生卡顿时，使用框架提供的转换工具类将BlockInfo转换成Json，并保存到文件\n    private static class MyBlockListener implements DroidTelescope.BlockListener {\n        @Override\n        public void onBlock(BlockInfo blockInfo) {\n            JSONObject blockInfoJson = null;\n            //使用框架提供的转换工具，将BlockInfo对象转换成Json格式\n            try {\n                blockInfoJson = ConvertUtils.convertBlockInfoToJson(blockInfo);\n            } catch (JSONException e) {\n                e.printStackTrace();\n            }\n            //可以将json数据上传服务器，或者保存到本地\n            if (blockInfoJson != null) {\n                FileUtils fileUtils = new FileUtils();\n                String blockJson = blockInfoJson.toString();\n                fileUtils.write2SDFromInput(\"\", \"block.txt\", blockJson);\n            }\n        }\n    }\n\n    //泄露监听器，当发生内存泄露时，使用框架提供的转换工具类将LeakInfo转换成Json，并保存到文件\n    private static class MyLeakListener implements DroidTelescope.LeakListener {\n        @Override\n        public void onLeak(LeakInfo leakInfo) {\n            JSONObject leakInfoJson = null;\n            //使用框架提供的转换工具，将LeakInfo对象转换成Json格式\n            try {\n                leakInfoJson = ConvertUtils.convertLeakInfoToJson(leakInfo);\n            } catch (JSONException e) {\n                e.printStackTrace();\n            }\n            //可以将json数据上传服务器，或者保存到本地\n            if (leakInfoJson != null) {\n                FileUtils fileUtils = new FileUtils();\n                String leakJson = leakInfoJson.toString();\n                fileUtils.write2SDFromInput(\"\", \"leak.txt\", leakJson);\n            }\n        }\n    }\n    \n}\n```\n## 对应用的性能影响测试\n* __包大小：__ 由于编译时会注入代码，所以会增加class文件的大小，如果按默认配置（只注入所有模块src下的代码）编出的dex平均会增加20%~40%。\n后期会增加编译开关来控制注入哪些模块。\n* __方法耗时：__ 测试方法是对一个方法循环调用2万次，对比注入前和注入后耗时。当只注入卡顿监控模块时会慢120ms；\n当只注入泄露模块时会慢170ms；当只注入用户交互模块时会慢1000ms。\n\u003cbr\u003eLooper监控的测试比较特殊，测试时每次loop生成10个耗时方法，然后触发200次loop监控，耗时180ms左右。\n* __内存消耗：__ 同样是循环2万次调用，卡顿模块消耗2.38MB，内存模块消耗510.89KB，交互模块消耗400KB（该测试和实际差别较大，仅供参考）。\n\n* * *\n\n欢迎关注我的公众号 `二叉树根子`，在这里可以看到不曾见过的 Android 底层技术。\n\n\u003cimg width=\"258\" height=\"258\" alt=\"公众号\" src=\"wiki/binary tree root.jpg\"\u003e\n\n## License\nDroidTelescope使用的GPL3.0协议，详细请参考[License](https://raw.githubusercontent.com/zkwlx/DroidTelescope/master/LICENSE)\n\n\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzkwlx%2FDroidTelescope","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzkwlx%2FDroidTelescope","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzkwlx%2FDroidTelescope/lists"}