一个能让你了解所有函数调用顺序的Android库

背景

当项目代码量很大的时候,或者你作为一名新人要快速掌握代码的时候,给函数打上log,来了解代码执行逻辑,这种方式会显然成本太大,要改动项目编译运行,NO!太耗时;或者你想debug的方式来给你想关注的几个函数,来了解代码执行逻辑,NO!因为你肯定会漏掉函数;也许你可以固执的给你写的项目打满log说这样也行,但是你要知道你方法所调用的jdk的函数或者第三方aar或者jar再或者android sdk中的函数调用顺序你怎么办,还能打log吗?显然不行吧,来~这个项目给让可以让你以包名为过滤点过滤你想要知道所有函数调用顺序。

效果奉上

动作简介:首先点击MainActivity的自定义MyTextView然后进入SecondActivity再点击textview之后finish跳转回MainActivity

下面是库处理过所得到的函数调用顺序order.txt文件(我这里屏蔽了jdk函数,第三方库函数,以及android sdk中函数,换句话说我就保留了我自己包名中的函数顺序)

832 ent     67593 .....com.zjw.appmethodorder.MainActivity.onClick (Landroid/view/View;)VMainActivity.java
832 ent     99956 ..........com.zjw.appmethodorder.MainActivity.onPause ()VMainActivity.java
832 ent     99970 ...........com.zjw.appmethodorder.BaseActivity.onPause ()VBaseActivity.java
832 ent    100472 ............com.zjw.appmethodorder.BaseActivity.baseOnPause ()VBaseActivity.java
832 ent    128540 ........com.zjw.appmethodorder.SecondActivity. ()VSecondActivity.java 832 ent    128562 .........com.zjw.appmethodorder.BaseActivity. ()VBaseActivity.java 832 ent    213911 ........com.zjw.appmethodorder.SecondActivity.onCreate (Landroid/os/Bundle;)VSecondActivity.java 832 ent    213928 .........com.zjw.appmethodorder.BaseActivity.onCreate (Landroid/os/Bundle;)VBaseActivity.java 832 ent    258414 ..........com.zjw.appmethodorder.BaseActivity.baseOnCreate ()VBaseActivity.java 832 ent   1440503 .........com.zjw.appmethodorder.SecondActivity.onResume ()VSecondActivity.java 832 ent   1440563 ..........com.zjw.appmethodorder.BaseActivity.onResume ()VBaseActivity.java 832 ent   1445675 ...........com.zjw.appmethodorder.BaseActivity.baseOnResume ()VBaseActivity.java 832 ent   2954291 .................com.zjw.appmethodorder.MyTextView.onWindowVisibilityChanged (I)VMyTextView.java 832 ent   3065664 ........com.zjw.appmethodorder.MainActivity.onStop ()VMainActivity.java 832 ent   3065701 .........com.zjw.appmethodorder.BaseActivity.onStop ()VBaseActivity.java 832 ent   3069155 ..........com.zjw.appmethodorder.BaseActivity.baseOnStop ()VBaseActivity.java 832 ent   3139519 .......com.zjw.appmethodorder.SecondActivity.click (Landroid/view/View;)VSecondActivity.java 832 ent   3146300 ........com.zjw.appmethodorder.SecondActivity.finish ()VSecondActivity.java 832 ent   3183478 ..........com.zjw.appmethodorder.SecondActivity.onPause ()VSecondActivity.java 832 ent   3183498 ...........com.zjw.appmethodorder.BaseActivity.onPause ()VBaseActivity.java 832 ent   3183843 ............com.zjw.appmethodorder.BaseActivity.baseOnPause ()VBaseActivity.java 832 ent   3209420 ........com.zjw.appmethodorder.MainActivity. ()VMainActivity.java 832 ent   3209438 .........com.zjw.appmethodorder.BaseActivity. ()VBaseActivity.java 832 ent   3283359 ........com.zjw.appmethodorder.MainActivity.onCreate (Landroid/os/Bundle;)VMainActivity.java 832 ent   3283378 .........com.zjw.appmethodorder.BaseActivity.onCreate (Landroid/os/Bundle;)VBaseActivity.java 832 ent   3330938 ..........com.zjw.appmethodorder.BaseActivity.baseOnCreate ()VBaseActivity.java 832 ent   4363295 .....................com.zjw.appmethodorder.MyTextView. (Landroid/content/Context;Landroid/util/AttributeSet;)VMyTextView.java 832 ent   4436094 ..................com.zjw.appmethodorder.MyTextView.onFinishInflate ()VMyTextView.java 832 ent   4449689 .........com.zjw.appmethodorder.MainActivity.initView ()VMainActivity.java 832 ent   4539427 .........com.zjw.appmethodorder.MainActivity.onResume ()VMainActivity.java 832 ent   4539467 ..........com.zjw.appmethodorder.BaseActivity.onResume ()VBaseActivity.java 832 ent   4543597 ...........com.zjw.appmethodorder.BaseActivity.baseOnResume ()VBaseActivity.java 832 ent   4917854 .................com.zjw.appmethodorder.MyTextView.onAttachedToWindow ()VMyTextView.java 832 ent   4918658 .................com.zjw.appmethodorder.MyTextView.onWindowVisibilityChanged (I)VMyTextView.java 832 ent   5090653 ...................................com.zjw.appmethodorder.MyTextView.onMeasure (II)VMyTextView.java 832 ent   5355203 ..................................com.zjw.appmethodorder.MyTextView.onMeasure (II)VMyTextView.java 832 ent   5456681 .......................................com.zjw.appmethodorder.MyTextView.onSizeChanged (IIII)VMyTextView.java 832 ent   5467577 ....................................com.zjw.appmethodorder.MyTextView.onLayout (ZIIII)VMyTextView.java 832 ent   5876623 ...........................................com.zjw.appmethodorder.MyTextView.onDraw (Landroid/graphics/Canvas;)VMyTextView.java 832 ent   6121967 ........com.zjw.appmethodorder.SecondActivity.onStop ()VSecondActivity.java 832 ent   6121986 .........com.zjw.appmethodorder.BaseActivity.onStop ()VBaseActivity.java 832 ent   6123689 ..........com.zjw.appmethodorder.BaseActivity.baseOnStop ()VBaseActivity.java 832 ent   6127522 ........com.zjw.appmethodorder.SecondActivity.onDestroy ()VSecondActivity.java 832 ent   6127679 .........com.zjw.appmethodorder.BaseActivity.onDestroy ()VBaseActivity.java 832 ent   6133301 ..........com.zjw.appmethodorder.BaseActivity.baseOnDestroy ()VBaseActivity.java 

OK!发现是不是很炫酷啊,下面来介绍该库的原理(求star!!!)

原理

本库其实并没有什么黑科技,本库也没有java代码,核心就是2个build.gradle中的task。首先,原理就是基于android sdk中提供的工具----traceview,和dmtracedump。traceview会生成.trace文件,该文件记录了函数调用顺序,函数耗时,函数调用次数等等有用的信息。而dmtracedump 工具就是基于trace文件生成报告的工具,具体用法不细说。

dmtracedump 工具大家一般用的多的选项就是生成html报告,或者生成调用顺序图片(看起来很不直观)。首先说说为什么要用traceview,和dmtracedump来作为得到函数调用顺序的,因为这个工具既然能知道cpu执行时间和调用次数以及函数调用树(看出函数调用顺序很费劲)比如android studio是这样呈现.trace文件的解析视图的

或者这样的

是网上找的,侵删) 用上面这2个图发现你要清晰知道函数调用看懂了才是见鬼了。或者使用dmtracedump 工具解析生成的html长下面这样(dmtracedump 生成图片就不说了 生成出的图片也根本看不出顺序这个就略过了)

一开始我以为 Method 序列号有戏于是乎冲动的我把带序号的东西内容复制出来写了一个脚本对他们进行排序代码如下:


  1.  import java.io.BufferedReader; 
  2. import java.io.FileReader; 
  3. import java.io.IOException; 
  4. import java.util.ArrayList; 
  5. import java.util.Arrays; 
  6. import java.util.Collections; 
  7. import java.util.regex.Matcher; 
  8. import java.util.regex.Pattern; 
  9.  
  10. public class Sort implements Comparable<Sort> { 
  11.  
  12. static String uri = "D:/Application/eclipse/javaworkspace/Testc/JB/1.text"; 
  13. String str = ""; 
  14. String content = ""; 
  15.  
  16. public Sort(String str,String content) { 
  17. super(); 
  18. this.str = str; 
  19. this.content = content; 
  20.  
  21. public static void main(String[] args) throws IOException { 
  22. // TODO Auto-generated method stub 
  23.  
  24. ArrayList<Sort> list = new ArrayList<>(); 
  25.  
  26. BufferedReader in = new BufferedReader(new FileReader(uri)); 
  27. String a = ""; 
  28.  
  29. while ((a = in.readLine()) != null) { 
  30. //System.out.println(Long.valueOf(getIndexStr(a))); 
  31. if(a.contains("com.zjw.appmethodorder")){ 
  32. list.add(new Sort(getIndexStr(a),a)); 
  33. Collections.sort(list); 
  34. for (Sort sort : list) { 
  35. System.out.println(sort.content); 
  36.  
  37. public static String getIndexStr(String str) { 
  38. String regEx = "(?<=\\[)(\\S+)(?=\\])";// 匹配[]中的数字 
  39. Pattern p = Pattern.compile(regEx); 
  40. Matcher m = p.matcher(str.trim()); 
  41. while (m.find()) { 
  42. return m.group().trim(); 
  43. return ""; 
  44.  
  45. @Override 
  46. public int compareTo(Sort o) { 
  47. // TODO Auto-generated method stub 
  48. //return 0; 
  49. return (int) (Long.valueOf(str) - Long.valueOf(o.str)); 
  50.  
  51. }  

结果发现过滤后的东西序列号是按顺序的但是并不是代码执行的逻辑顺序。我擦怎么办,工具代码也写了,居然不是我想要的结果,好在花了一些时间发现dmtracedump -ho 选项,经过研究发现,这玩意输出的内容居然是按逻辑顺序从上到下的,于是基于这一点我写一个项目,其实核心就是2个task完成了了解所有函数调用顺序的目的。


  1. //核心任务:在captures文件目录下输出 基于最新.trace文件的函数调用信息的txt版本 
  2.  
  3. //说明:dmtracedump 为 android sdk自带工具,要执行dmtracedump命令则需要先添加环境变量 
  4.  
  5. task AppOutPutMethodOrder() { 
  6.  
  7.     doLast { 
  8.  
  9.         def capturesDirPath = project.getProjectDir().getParentFile().path + File.separator + "captures"; 
  10.  
  11.         def capturesDir = new File(capturesDirPath); 
  12.  
  13.         capturesDir.traverse { 
  14.  
  15.             if (it.isFile() && it.name.endsWith(".trace")) { 
  16.  
  17.                 def orderName = it.name.replace("trace", "txt") 
  18.  
  19.                 def orderFile = new File(capturesDirPath, orderName) 
  20.  
  21.                 orderFile.write("") 
  22.  
  23.                 def dmtracedumpDir = getDmtraceDumpDir(); 
  24.  
  25.                 //说明:dmtracedump 为 android sdk自带工具,要执行dmtracedump命令则需要先添加环境变量 
  26.  
  27.                 def baseComand = dmtracedumpDir + "dmtracedump  -ho " + it.absolutePath + " >> " + orderFile.absolutePath 
  28.  
  29.                 println baseComand 
  30.  
  31.                 String osNameMatch = System.getProperty("os.name").toLowerCase(); 
  32.  
  33.                 if (osNameMatch.contains("windows")) { 
  34.  
  35.                     ("cmd /c start  /b " + baseComand).execute() 
  36.  
  37.                 } else { 
  38.  
  39.                     ["bash", "-c", baseComand].execute() 
  40.  
  41.                 } 
  42.  
  43.             } 
  44.  
  45.         } 
  46.  
  47.     } 
  48.  
  49.  
  50.  
  51. /** 
  52.  
  53.  * read the sdk dir from local.properties 
  54.  
  55.  * eg : 
  56.  
  57.  *  sdk.dir = /home/env/sdk 
  58.  
  59.  *  so: 
  60.  
  61.  *   dmtracedump.dir = /home/env/sdk/platform-tools 
  62.  
  63.  * 
  64.  
  65.  * @return the dir which dmtracedump tools exists 
  66.  
  67.  */ 
  68.  
  69. def getDmtraceDumpDir() { 
  70.  
  71.     def rootDir = project.rootDir 
  72.  
  73.     def localProperties = new File(rootDir, "local.properties") 
  74.  
  75.     def sdkDir = null; 
  76.  
  77.     if (localProperties.exists()) { 
  78.  
  79.         Properties properties = new Properties() 
  80.  
  81.         localProperties.withInputStream { instr -> 
  82.  
  83.             properties.load(instr) 
  84.  
  85.         } 
  86.  
  87.         sdkDir = properties.getProperty('sdk.dir') 
  88.  
  89.     } 
  90.  
  91.     if (sdkDir == null || !(new File(sdkDir).exists())) { 
  92.  
  93.         sdkDir = android.getSdkDirectory().getAbsolutePath() 
  94.  
  95.     } 
  96.  
  97.     if (sdkDir == null || !(new File(sdkDir).exists())) { 
  98.  
  99.         sdkDir = android.plugin.getSdkFolder().getAbsolutePath() 
  100.  
  101.     } 
  102.  
  103.     def dmtraceDumpToolDir = sdkDir + File.separator + "platform-tools" + File.separator 
  104.  
  105.     if (new File(dmtraceDumpToolDir).exists()) { 
  106.  
  107.         return dmtraceDumpToolDir; 
  108.  
  109.     } 
  110.  
  111.     return "" 
  112.  
  113.  
  114.  
  115. //这里AppFilterMethodOrder 任务其实也不需要 执行找到 \captures 目录找到 base_order.txt 
  116.  
  117. //用Notepad++ 使用正则 先过滤 带 xit 的行 (我们只关注ent 行就行,ent代表进入执行函数 xit代表退出函数)再过滤掉你不关心的包名 
  118.  
  119. // Notepad++ 中过滤将会使用到的命令行如下 
  120.  
  121. //^.*xit.*$ //去除掉 含有 xit 字符串的行  然后替换为空 
  122.  
  123. // ^((?!XXX).)*$  //去除不包含XXX的行  然后替换为空 
  124.  
  125. //^\s+   //合并空行  然后替换为空 
  126.  
  127.  
  128. task AppFilterMethodOrder() { 
  129.  
  130.     doLast { 
  131.  
  132.         //TODO 替换为你想要过滤的包名 
  133.  
  134.         def filterPackageName = "com.zjw.appmethodorder" 
  135.  
  136.         if (project.hasProperty("package_name")) { 
  137.  
  138.             filterPackageName = project.getProperty("package_name") 
  139.  
  140.         } 
  141.  
  142.         //处理包名 
  143.  
  144.         def filterSignature = filterPackageName.replaceAll("[.]", "/") 
  145.  
  146.  
  147.         def capturesDirPath = project.getProjectDir().getParentFile().path + File.separator + "captures"; 
  148.  
  149.         def capturesDir = new File(capturesDirPath); 
  150.  
  151.  
  152.         capturesDir.traverse { 
  153.  
  154.             if (it.isFile() && it.name.endsWith(".txt") && !it.name.contains("--filter")) { 
  155.  
  156.                 def orderName = it.name.replace(".txt", "--filter.txt") 
  157.  
  158.                 def orderTimeName = it.name.replace(".txt", "--timefilter.txt") 
  159.  
  160.                 def orderFile = new File(capturesDirPath, orderName) 
  161.  
  162.                 orderFile.write("") 
  163.  
  164.                 def orderTimeFile = new File(capturesDirPath, orderTimeName) 
  165.  
  166.                 orderTimeFile.write("") 
  167.  
  168.                 it.eachLine { line -> 
  169.  
  170.  
  171.                     if (line.contains(" ent ") 
  172.  
  173.                         //兼容不同版本traceview 有的是方法包名有的是方法签名 
  174.  
  175.                     && (line.contains(filterPackageName) 
  176.  
  177.                     || line.contains(filterSignature)) 
  178.  
  179.                     ) { 
  180.  
  181.                         orderFile.append(line + "\n") 
  182.  
  183.                     } 
  184.  
  185.  
  186.                     //生成带xit 和 ent 的trace行 函数耗时计算方式: xit字符后 数值 减去 ent字符后的 数字 (差值就是耗时 单位:微妙) 
  187.  
  188.                     //注意:好像函数体中含Thread.sleep的计算不准确 
  189.  
  190.                     if (line.contains(filterPackageName) 
  191.  
  192.                             || line.contains(filterSignature)){ 
  193.  
  194.                         orderTimeFile.append(line + "\n") 
  195.  
  196.                     } 
  197.  
  198.  
  199.                 } 
  200.  
  201.             } 
  202.  
  203.         } 
  204.  
  205.  
  206.  
  207.     } 
  208.  

如何使用

讲了一堆原理我们来说说这个库怎么用吧。

  • 下载utils.gradle到工程根目录
  • 修改根目录下build.gradle,增加 apply from: rootProject.getRootDir().getAbsolutePath() + "/utils.gradle" 对utils.gradle的引用,具体可参考本工程根目录下的build.gradle.
  • 使用下面的详细介绍生成trace文件
  • 使用下面的命令生成堆栈文件 ./gradlew AppOutPutMethodOrder
  • 上面命令文件内容太多时,通过这个命令进行过滤包含需要过滤的字符串 ./gradlew AppFilterMethodOrder -P package_name=com.zjw.appmethodorder

注意:请先确保 anroid sdk 中的dmtracedump 工具加入在你的环境变量中(Mac同学因为task面板执行的bug 需要把gradle添加到环境变量中)

首先编译运行项目,然后点击下图的时钟(这是使用工具打trace start 和 end)进行操作,可以参考上文所说的动作简介(记住你操作想想你的生命周期函数调用顺序,待会可以和生成的captures目录下base_order.txt或者生成的order.txt中的函数顺序做做对比)然后再点一次下图那个时钟。还有一种记录trace start 和 end的方式就是在修改代码,即使用android.os.Debug.startMethodTracing();和android.os.Debug.stopMethodTracing();

以上操作完成后即会在captures目录生成

com.zjw.appmethodorder_2017.03.25_21.41.trace文件,android studio会默认打开一个可视化窗口

然后双击右侧面板的 AppOutPutMethodOrder任务 (特别注意:用Mac的同学注意了,现在已知双击执行task会输出空文件,貌似是studio的BUG,可以使用 ./gradlew AppOutPutMethodOrder执行该任务)如下图

这一步完成就将在captures目录生成和trace文件同名的txt文件(如示例com.zjw.appmethodorder_2017.03.25_21.41.trace),该文件包含所有函数执行顺序

等待任务执行完成,再双击执行AppFilterMethodOrder任务 (特别注意:用Mac的同学注意了,现在已知双击执行task会输出空文件,貌似是studio的BUG,可以使用 ./gradlew AppFilterMethodOrder -P package_name=com.zjw.appmethodorder执行该任务)如下图窗口

该任务目的就是过滤其他非相关包名,留下自己包名的函数,任务执行完成将在captures目录生成和·trace文件同名+--filter.txt文件(例如示例AppMethodOrder\captures\com.zjw.appmethodorder_2017.03.25_21.41--filter.txt) 如下图:

接下来打开trace文件同名+--filter.txt文件(例如示例AppMethodOrder\captures\com.zjw.appmethodorder_2017.03.25_21.41--filter.txt)就是上文效果中的那样啦。

关于扩展和改造

这里改成你想要过滤的包名即可。

小工具

Windows 环境下 可使用tool文件夹下的Method-trace-analysis.jar 直接导入.trace文件,一键分析

执行AppFilterMethodOrder 任务 新增后缀为--filterTime.txt 的文件,用于计算方法耗时

如上图例:MainActivity.onPause方法执行耗时为149231-148152 = 1079,最终耗时为 1079μs(微秒) 约为 1毫秒

1.生成带xit 和 ent 的trace行 函数耗时计算方式: xit字符后 数值 减去 ent字符后的 数字 (差值就是耗时 单位:微妙)

2.注意:函数体中含Thread.sleep的计算不准确

作者:zjw-swun

来源:51CTO

时间: 2024-09-19 10:11:45

一个能让你了解所有函数调用顺序的Android库的相关文章

visual studio 2010-关于VS的一个小测试程序问题,函数调用顺序

问题描述 关于VS的一个小测试程序问题,函数调用顺序 新手学C++,把C++ primer 上的两个例子用Visual Studio 2010弄到一起的时候出现了一个问题. 一共用了三个函数放在三个cpp文件,被调用的两个均用头文件进行了声明. 两个函数实现的功能很简单分别是:1.多个数相加求和(plus()) 2.输入两个数字作为上界和下界并求和.(two_num()) 问题出现在:在main函数下进行调用的时候, 先调用two_num();再调用plus()可以正常执行. #include

MFC应用程序中处理消息的顺序,创建窗口的过程关闭窗口的顺序(非模态窗口),打开模式对话框的函数调用顺序

MFC应用程序中处理消息的顺序 1.AfxWndProc()      该函数负责接收消息,找到消息所属的CWnd对象,然后调用AfxCallWndProc 2.AfxCallWndProc() 该函数负责保存消息(保存的内容主要是消息标识符和消息参数)供应用程序以后使用,                    然后调用WindowProc()函数 3.WindowProc()      该函数负责发送消息到OnWndMsg()函数,如果未被处理,则调用DefWindowProc()函数 4.O

jquery 菜单移动-这是一个菜单移动的操作,但是顺序a向上移动有错误,求指导,请问怎么控制不超出父节点

问题描述 这是一个菜单移动的操作,但是顺序a向上移动有错误,求指导,请问怎么控制不超出父节点 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 无标题文档 #wrap{margin:20px auto;width: 500px;} #wrap li{list-st

并行执行-求一个既有并行,又有顺序的调度算法

问题描述 求一个既有并行,又有顺序的调度算法 如上UML活动图左图,作业既有并行执行,又有顺序执行. 比如: 1->3->5 和2->4这两段任务可以并行执行,6必须在4,5都完成之后开始做. 1->3->5 2->4 两段任务的绝对顺序不变,但两个表可以任意归并,因为他们是并行. 输入3(做完),返回5. 输入5,系统检查4,做完则返回6,否则返回0. 请问有什么经典的调度算法吗? 解决方案 这不需要什么特定的算法的啊.线程+等待就可以解决了. 1.主线程先开三个线程

大数据-一个程序中,TCP协议用的muduo网络库,UDP协议可以用boos.asio吗?

问题描述 一个程序中,TCP协议用的muduo网络库,UDP协议可以用boos.asio吗? 通过TCP协议接收数据,解析好,在用UDP协议转发. UDP转发是后面增加的,muduo网路库不支持Udp协议,自己用socket api写,担心大数据,多并发的时候不可靠.请问大神有什么建议? 解决方案 这个当然可以,只要协议格式客户端,服务端之间处理好. 解决方案二: udp确实不可靠,要解决可以自己实现一套超时和重发机制,比如说每个数据包打上标号,发送方和接收方处理好重发,如果对数据质量要求高的话

一个只有50人的小公司以前,知道简·库姆的人很少

在扎克伯格花掉190亿美元买下一个只有50人的小公司以前,知道简·库姆的人很少.这个50人的小公司就是世界上4亿人都在用的美国版"微信"Whatsapp,而简·库姆是这个移动通讯应用软件的联合创始人和首席执行官.一个自学电脑,做过程序员,当过黑客,在雅虎苦干九年的37岁IT男,最终在自己曾排队领救济券的地方,将公司卖给自己曾投简历却被拒的Facebook,一夜之间身家涨到68亿美元,从"吊丝"到亿万富豪,简·库姆有的当然不仅仅是好运气. 条件一:励志图强的童年 移民

如果把一个自定义的数据集DATASET按字段顺序打印出来

问题描述 我想自己拼凑一个DATASET,然后打印数据集中所有记录哪位给份源码.我从没用过C#,什么都不懂.如果需要什么配置步骤请一起写出来. 解决方案 解决方案二:DataTablenewDt=newDataTable();//新建DataTablenewDt.Columns.Add("字段1");//添加字段newDt.Columns.Add("字段2");DataRowdr=newDt.NewRow();//添加一行内容dr["字段1"]=

构建 View 时可能用到的代理函数调用顺序

很多时候,要记住iPhone试图加载的life-cycle对应的代理函数是有点头疼的,特别是用Interface Builder构建界面的情况下.这给如何找到最佳的位置来添加自定义的视图绘画/初始化/配置成为了一个对于新手来说的难题.我把目前用到的可能会override的方法总结在这张图里.注意两点: 1. 这是我目前遇到的case的总结.我不100%确定适用于所有情况 2. 不是每个case都会用到这些方法的重载.我只是把它们放在了一张图里面而已

构建“.NET研究” View 时可能用到的代理函数调用顺序

很多时候,上海企业上海徐汇企业网站制作网站设计与制作要记住iPhone试图加载的life-cycle上海闵行企业网站设计与制作对应的代理函数是有点头疼的,特别是用Interface Buil上海徐汇企业网站设计与制作der构建界面的情况下.这给如何找到最佳的位置来添加自定义的视图绘画/初始化/配置成为了一个对于新手来说的难题.我把目前用到的可能会override的方法上海网站建设总结在这张图里.注意两点: 1. 这是我目前遇到的case的总结.我不100%确定适用于所有情况 2. 不是每个cas