一、测试用例设计方法
白盒测试:逻辑覆盖、循环覆盖、基本路径覆盖
黑盒测试:边界值分析法、等价类划分、错误猜测法、因果图法、状态图法、测试大纲法、随机测试、场景法
二、软件测试分为几个阶段 各阶段的测试策略和要求(基础)
试过程会依次经历:单元测试、集成测试、系统测试、验收测试四个主要阶段
单元测试:是针对软件设计的最小单位(对于功能测试就是模块)
集成测试:是将模块按照设计要求组装起来进行测试,主要目的是发现与接口有关的问题。
系统测试:是在集成测试通过后进行的,目的是充分运行系统,验证各子系统是否都能正常工作并完成设计的要求。
验收测试:以需求阶段的《需求规格说明书》为验收标准,测试时模拟实际用户的运行环境
三、你在测试中发现了一个bug,但是开发认为这不是一个bug,你应该怎样解决(坑爹的沟通能力)
1、将问题提交到缺陷管理库里面进行备案。
2、要获取判断的依据和标准:
3、根据需求说明书、产品说明、设计文档等,确认实际结果是否与计划有不一致的地方,提供缺陷是否确认的直接依据;
4、如果没有文档依据,可以根据类似软件的一般特性来说明是否存在不一致的地方,来确认是否是缺陷;
5、与设计人员、开发人员和客户代表等相关人员探讨,确认是否是缺陷;
四、http与https区别(加密传输)
http协议和https协议的区别:传输信息安全性不同、连接方式不同、端口不同、证书zhuan申请方式不同、传输信息安全性不同
1、http协议:是超文本传输协议,信息是明文传输。如果攻击者截取了Web浏览器和网站服务器之间的传输报文,就可以直接读懂其中的信息;
2、https协议:是具有安全性的ssl加密传输协议,为浏览器和服务器之间的通信加密,确保数据传输的安全。
二、连接方式不同
1、http协议:http的连接很简单,是无状态的;
2、https协议:是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议。
三、端口不同
http协议:使用的端口是80; https协议:使用的端口是443。
四、证书申请方式不同
http协议:免费申请 https协议:需要到ca申请证书,一般免费证书很少,需要交费
五、什么是负载测试
负载测试主要是考察软件系统在既定负载下的性能表现。就是站在用户的角度去观察一定条件下软件系统的性能表现。期望结果是用户的性能指标需求得到满足。性能指标一般体现为响应时间、并发量等。
六、python多线程
python提供了两个模块来实现多线程thread 和threading ,thread 有一些缺点,在threading 得到了弥补,为了不浪费你和时间,所以我们直接学习threading 就可以了。
import threading
首先导入threading 模块,这是使用多线程的前提。
threads = []
t1 = threading.Thread(target=music,args=(u'爱情买卖',))
threads.append(t1)
创建了threads数组,创建线程t1,使用threading.Thread()方法,在这个方法中调用music方法target=music,args方法对music进行传参。 把创建好的线程t1装到threads数组中。
接着以同样的方式创建线程t2,并把t2也装到threads数组。
for t in threads:
t.setDaemon(True)
t.start()
最后通过for循环遍历数组。(数组被装载了t1和t2两个线程)
setDaemon()
setDaemon(True)将线程声明为守护线程,必须在start() 方法调用之前设置,如果不设置为守护线程程序会被无限挂起。子线程启动后,父线程也继续执行下去,当父线程执行完最后一条语句print "all over %s" %ctime()后,没有等待子线程,直接就退出了,同时子线程也一同结束。
start()
开始线程活动。
从执行结果来看,子线程(muisc 、move )和主线程(print "all over %s" %ctime())都是同一时间启动,但由于主线程执行完结束,所以导致子线程也终止。
们只对上面的程序加了个join()方法,用于等待线程终止。join()的作用是,在子线程完成运行之前,这个子线程的父线程将一直被阻塞。
注意: join()方法的位置是在for循环外的,也就是说必须等待for循环里的两个进程都结束后,才去执行主进程。
从执行结果可看到,music 和move 是同时启动的。
开始时间4分11秒,直到调用主进程为4分22秒,总耗时为10秒。从单线程时减少了2秒,我们可以把music的sleep()的时间调整为4秒。
七、数据库中where与having的区别
从整体声明角度分析:
“where”是一个约束声明,在查询数据库结果返回之前对数据库的查询条件做一个约束,即返回结果之前起作用,“where”后面不能跟聚合函数;
“having”是一个过滤声明,在查询数据库结果返回之后进行过滤,即返回结果之后起作用,“having”后面可以加聚合函数;
聚合函数:是对一组值进行计算,返回单一的值,例如:count(),sum(),max(),min()
从使用角度分析:
“where” select student_id,student_name from student where student_sorce>80;
"having" select sum(sorce) from student group by student_sex having student_id<10;
八、java中String、StringBuffer和StringBuilder的区别
java中用于处理字符串常用的有三个类:
1、java.lang.String
2、java.lang.StringBuffer
3、java.lang.StrungBuilder
三者共同之处:都是final类,不允许被继承,主要是从性能和安全性上考虑的,因为这几个类都是经常被使用着,且考虑到防止其中的参数被参数修改影响到其他的应用。
StringBuffer是线程安全,可以不需要额外的同步用于多线程中;
StringBuilder是非同步,运行于多线程中就需要使用着单独同步处理,但是速度就比StringBuffer快多了;
StringBuffer与StringBuilder两者共同之处:可以通过append、indert进行字符串的操作。
String实现了三个接口:Serializable、Comparable
StringBuilder只实现了两个接口Serializable、CharSequence,相比之下String的实例可以通过compareTo方法进行比较,其他两个不可以。
这三个类之间的区别主要是在两个方面,即运行速度和线程安全这两方面。
1、首先说运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > String
String最慢的原因:String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。以下面一段代码为例:
运行这段代码会发现先输出“abc”,然后又输出“abcde”,好像是str这个对象被更改了,其实,这只是一种假象罢了,JVM对于这几行代码是这样处理的,首先创建一个String对象str,并把“abc”赋值给str,然后在第三行中,其实JVM又创建了一个新的对象也名为str,然后再把原来的str的值和“de”加起来再赋值给新的str,而原来的str就会被JVM的垃圾回收机制(GC)给回收掉了,所以,str实际上并没有被更改,也就是前面说的String对象一旦创建之后就不可更改了。所以,Java中对String对象进行的操作实际上是一个不断创建新的对象并且将旧的对象回收的一个过程,所以执行速度很慢。而StringBuilder和StringBuffer的对象是变量,对变量进行操作就是直接对该对象进行更改,而不进行创建和回收的操作,所以速度要比String快很多。另外,有时候我们会这样对字符串进行赋值
1 String str="abc"+"de";
2 StringBuilder stringBuilder=newStringBuilder().append("abc").append("de");
3 System.out.println(str);
4 System.out.println(stringBuilder.toString());
这样输出结果也是“abcde”和“abcde”,但是String的速度却比StringBuilder的反应速度要快很多,这是因为第1行中的操作和String str="abcde";是完全一样的,所以会很快,而如果写成下面这种形式
1 String str1="abc";
2 String str2="de";
3 String str=str1+str2;
那么JVM就会像上面说的那样,不断的创建、回收对象来进行这个操作了。速度就会很慢。
public static void main(String[] args) {
long a=new Date().getTime();
String cc="";
int n=10000;
for (int i = 0; i < n; i++) {
cc+="."+i;
}
System.out.println("String使用的时间"+(System.currentTimeMillis()-a)/1000.0+"s");
long s1=System.currentTimeMillis();
StringBuilder sb=new StringBuilder();
for (int i = 0; i < n; i++) {
sb.append("."+i);
}
System.out.println("StringBuilder使用的时间"+(System.currentTimeMillis()-s1)/1000.0+"s");
long s2=System.currentTimeMillis();
StringBuffer sbf=new StringBuffer();
for (int i = 0; i < n; i++) {
sbf.append("."+i);
}
System.out.println("StringBuffer使用的时间"+(System.currentTimeMillis()-s2)/1000.0+"s");
}
2. 再来说线程安全
在线程安全上,StringBuilder是线程不安全的,而StringBuffer是线程安全的
如果一个StringBuffer对象在字符串缓冲区被多个线程使用时,StringBuffer中很多方法可以带有synchronized关键字,所以可以保证线程是安全的,但StringBuilder的方法则没有该关键字,所以不能保证线程安全,有可能会出现一些错误的操作。所以如果要进行的操作是多线程的,那么就要使用StringBuffer,但是在单线程的情况下,还是建议使用速度比较快的StringBuilder。
(一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞)
3. 总结一下
String:适用于少量的字符串操作的情况
StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况
StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况
九、java的三大特性和他们之间的区别
十、hashmap和map的关系
十一、数组和链表的区别
十二、垃圾回收机制算法和原理
十三、tcp,udp的区别
十四、jmeter性能测试
十五、seleuinm原理和缺点
十六、什么是非对称加密和对称加密
十七、python装饰器起什么作用,原理是什么
十八、linux常用的指令
十九、手写一个排序算法
二十、重载和重写的区别
二十一、GC机制、Java引用类型
二十二、RecyclerView和ListView的区别
一、 缓存机制对比
1. 层级不同:
RecyclerView比ListView多两级缓存,支持多个离ItemView缓存,支持开发者自定义缓存处理逻辑,支持所有RecyclerView共用同一个RecyclerViewPool(缓存池)。
具体来说:
ListView(两级缓存):
RecyclerView(四级缓存):
ListView和RecyclerView缓存机制基本一致:
1). mActiveViews和mAttachedScrap功能相似,意义在于快速重用屏幕上可见的列表项ItemView,而不需要重新createView和bindView;
2). mScrapView和mCachedViews + mReyclerViewPool功能相似,意义在于缓存离开屏幕的ItemView,目的是让即将进入屏幕的ItemView重用.
3). RecyclerView的优势在于a.mCacheViews的使用,可以做到屏幕外的列表项ItemView进入屏幕内时也无须bindView快速重用;b.mRecyclerPool可以供多个RecyclerView共同使用,在特定场景下,如viewpaper+多个列表页下有优势.客观来说,RecyclerView在特定场景下对ListView的缓存机制做了补强和完善。
2. 缓存不同:
1). RecyclerView缓存RecyclerView.ViewHolder,抽象可理解为:
View + ViewHolder(避免每次createView时调用findViewById) + flag(标识状态);
RecyclerView中mCacheViews(屏幕外)获取缓存时,是通过匹配pos获取目标位置的缓存,这样做的好处是,当数据源数据不变的情况下,无须重新bindView:
2). ListView缓存View。而同样是离屏缓存,ListView从mScrapViews根据pos获取相应的缓存,但是并没有直接使用,而是重新getView(即必定会重新bindView)。
二、 局部刷新
RecyclerView更大的亮点在于提供了局部刷新的接口,通过局部刷新,就能避免调用许多无用的bindView。ListView和RecyclerView最大的区别在于数据源改变时的缓存的处理逻辑,ListView是"一锅端",将所有的mActiveViews都移入了二级缓存mScrapViews,而RecyclerView则是更加灵活地对每个View修改标志位,区分是否重新bindView。
ListView获取缓存的流程:
RecyclerView获取缓存的流程:
结合RecyclerView的缓存机制,看看局部刷新是如何实现的:
以RecyclerView中notifyItemRemoved(1)为例,最终会调用requestLayout(),使整个RecyclerView重新绘制,过程为:
onMeasure()–>onLayout()–>onDraw()
其中,onLayout()为重点,分为三步:
dispathLayoutStep1():记录RecyclerView刷新前列表项ItemView的各种信息,如Top,Left,Bottom,Right,用于动画的相关计算;
dispathLayoutStep2():真正测量布局大小,位置,核心函数为layoutChildren();
dispathLayoutStep3():计算布局前后各个ItemView的状态,如Remove,Add,Move,Update等,如有必要执行相应的动画.
其中,layoutChildren()流程图:
当调用notifyItemRemoved时,会对屏幕内ItemView做预处理,修改ItemView相应的pos以及flag(流程图中红色部分):
当调用fill()中RecyclerView.getViewForPosition(pos)时,RecyclerView通过对pos和flag的预处理,使得bindview只调用一次.
作者:卫宫士郎
链接:https://www.jianshu.com/p/257c279a3493
来源:
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
二十三、Android/ 布局优化
二十四、Activity的四种启动模式
Activity四种启动模式分别是standard,singleTop,singleTask,singltInstance.在执行各个模式时候的页面中执行的方法各不相同,我们常常只是口头叙述一下各种模式是怎么回事,但是真的被问到执行的方法,可能说的不是很清楚,这里通过打印log的方法记录一下。
standard模式
从MainActivity进入StandardActivity,执行的方法分别是:onCreate,onStart,onResume,onAttchedToWindow.
从StandardActivity进入StandardTestActivity后执行方法:onPause,onStop.可以看到,当StandardTestActivity执行完onResume后,StandardActivity的onStop方法才执行。
返回的时候可以看到从StandardTestActivity到StandardActivity,StandardActivity执行了onStart,onResume方法,而当StandardActivity的onResume方法执行后,StandardTestActivity执行了onStop,onDestroy,onDetachedFromWindow方法
再次点击返回从StandardActivity返回到MainActivity页面,StandardActivity也依次执行了onPause,onStop,onDestroy,onDetachedFromWindow方法
singleTop模式
如图所示,这里分两种情况来看SingleTopActivity方法的执行。
singleTop—>singleTop
singleTop—>standard—>singleTop
singleTop–>singleTop
可以从图中看到singleTop->singleTop不再执行依次onCreate,onStart,onResume,而是执行了onNewIntent,onResume.
singleTop–>standard–>singleTop
可以从图中看到singleTop-->standard-->singleTop中,第二个singleTop依次执行了onCreate,onStart,onResume方法
singleTask模式
如图所示,同样分两种情况来看SingleTaskActivity方法的执行。
singleTask—>singleTask
singleTask—>standard—>singleTask
singleTask–>singleTask
可以从图中看到,singleTask->singleTask和singleTop->singleTop类似,也是执行了onNewIntent,onResume方法
singleTask–>standard–>singleTask
可以从图中看到singleTask-->standard-->singleTask ,进入第二个singleTask的时候,StandardActivity执行的方法相当于finish的操作,也就是被移除了,singleTask则执行了onNewIntent,onStart,onResume方法。
返回的时候直接执行了singleTask的onPause,onStop,onDestroy,onDetachedFromWindow方法
singleInstance模式
如图所示,同样分两种情况来看SingleInstanceActivity方法的执行。
singleInstance–>singleInstance
singleInstance–>standard–>singleInstance
singleInstance–>singleInstance
可以从图中看到,singleInstance->singleInstance同样是执行了onNewIntent,onResume,前一个执行了onPause方法
singleInstance–>standard–>singleInstance
可以从图中看到,前面的一切正常,进入第二个SingleInstanceActivity的时候,执行了onNewIntent,onStart,onResume方法,这个和singleTask是一样的,但是不同的是SingleInstanceActivity和StandardActivity不在同一个任务栈中。
从下图可以看到,SingleInstanceActivity点击返回到了StandardActivity中,再点击返回,则不再会回到SingleInstanceActivity。
二十五、Handler机制,loop方法为何不会造成ANR
二十六、Android之View的绘制流程解析
一:绘制大致流程
一个APP由许多Activity组成,每个Activity都对应一个界面呈现给用户,而每一个界面都是由各种View和ViewGroup组成,总体结构是一个树形结构,如图所示:
在上图中,DecorView是所有视图的根视图,也就是最顶层布局,它是一个ViewGroup。每个Activity的View绘制流程都是先从DecorView的绘制开始,然后依次递归绘制它的子View和子ViewGroup,子ViewGroup再递归绘制它包含的子View,直到所有的View都绘制完成。
二:View绘制涉及到的类和方法
View绘制的入口是ViewRootImpl类的performTraversals()方法。在performTraversals()方法中有3个重要的步骤方法:
(1)performMeasure():该方法主要用于测量所有View和ViewGroup的大小(宽和高)
(2)performLayout():该方法用于确定所有View和ViewGroup在父布局的位置
(3)performDraw():该方法进行具体的绘制操作
三:performMeasure测量
1.测量涉及的方法
performMeasure()主要就是用来测量View和ViewGroup的宽和高,涉及到的方法有:
(1)View:Measure()—>onMeasure()—>setMeasureDimension()—>getDefaultSize()。
View的测量流程:View的测量是从Measure()方法开始的,在Measure()里面没有具体的操作,而是直接调用onMeasure()方法。在onMeasure()方法里调用了两个getDefaultSize()方法来分别测量View的宽和高的值。最后调用setMeasureDimension()方法将View的宽和高测量值保存下来。
(2)ViewGroup:Measure()—>onMeasure()—>measureChildren()—>measureChild()/measureChildWithMargins()—>Measure()—>onMeasure()—>setMeasureDimension()—>getDefaultSize()。
ViewGroup除了需要测量自己的宽高大小,还需要测量它包含的子View或子ViewGroup的大小,所以它涉及到的方法除了测量View需要的方法之外,还有measureChildren()方法,在measureChildren()方法内通过循环调用measureChild()方法,在measureChild方法中又调用子View或子ViewGroup的Measure()方法。
总结:在performMeasure()方法中首先调用的是顶级视图DecorView的Measure()方法,在Measure()方法中调用onMeasure()方法进行DecorView的宽高大小测量。由于DecorView是一个ViewGroup,所以在onMeasure()方法内又调用了measureChildren()方法来测量它的子View和子ViewGroup宽高大小,这样一步步往下递归遍历,最后测量出所有的View和ViewGroup的大小。
2.MeasureSpec
上面描述了测量View和ViewGroup大小的大致流程,但是在具体测量的时候还涉及到了一个MeasureSpec类,这个类是View类的内部类,主要内容是一个int型变量,int是32位的,其中高2位表示模式(Mode),低30位表示大小(Size)。每一个View和ViewGroup都有自己的MeasureSpec,具体来说是有2个MeasureSpec:widthMeasureSpec和heightMeasureSpec。这两个MeasureSpec从measure()方法开始作为参数,一直传递到getDefaultSize()方法,最后在getDefaultSize()方法里面参考这2个MeasureSpec来确定具体大小。
MeasureSpec高2位表示的模式有3种:UNSPECIFIED(不指定的)、EXACTLY(确定的)、AT_MOST(至多的)。
MeasureSpec到底有什么用?
子View的大小通常是受限于父布局的,举个例子,假如某个子View大小设为match_parent,那么该子View的大小就依赖于父布局的大小,父布局如果大小为200*200dp,子View就为200*200dp。
子View的MeasureSpec是由自身的LayoutParams和父布局的MeasureSpec共同确定的。在上面例子中父布局大小为200*200dp,于是父布局的widthMeasureSpec和widthMeasureSpec模式Mode为EXACTLY(确定的),大小Size为200。又由于子View的LayoutParams指定为match_parent,于是子View的widthMeasureSpec和widthMeasureSpec模式Mode也为EXACTLY(确定的),大小Size也为200。
上面子View的MeasureSpec确定过程发生在measureChild()方法中,确定好子View的MeasureSpec后就将其作为参数传入到measure()方法,最后在getDefaultSize()方法里根据子View的两个MeasureSpec来确定子View的宽高。
父MeasureSpec和子MeasureSpec的关系图如下,上面例子对应下图的第二行第一列。
上图总结:
1.当子View的LayoutParams指定为具体的尺寸如200*200dp,那么无论父布局的MeasureSpec模式和大小是什么,子View的模式都为EXACTLY(确定的),大小就是LayoutParams指定的具体值200,而不依赖于父布局MeasureSpec指定的大小。
2.当子View的LayoutParams指定为match_parent时,子View的MeasureSpec就和父布局的MeasureSpec相同。
2.当子View的LayoutParams指定为wrap_content时,分两种情况:当父布局MeasureSpec模式为UNSPECIFIED(不指定的),子View的MeasureSpec模式也是UNSPECIFIED(不指定的);当父布局MeasureSpec模式为EXACTLY(确定的)和AT_MOST(至多的),子View的MeasureSpec模式都是AT_MOST(至多的)。无论Mode是哪一种,子View的MeasureSpec的大小(Size)都是父布局的MeasureSpec中指定的大小,表示子View大小不应该超过父布局大小。
只要知道了父布局的MeasureSpec和子View的LayoutParams,那么子View的MeasureSpec也就确定了,从而能够进一步确定子View的大小。
ps:由于DecorView是顶级布局,所以它的MeasureSpec是通过窗口大小和自身的LayoutParams确定的。
四:performLayout布局
在performTraversals()方法中,首先调用performMeasure()方法来测量所有View和ViewGroup的宽高大小。之后就是调用performLayout()方法确定每个View和ViewGroup在其父布局中的位置。
布局涉及的方法
(1)View:layout()—>setFrame()
View布局流程:View通过调用layout()方法来确定它在父布局的位置,具体是在layout()方法内调用setFrame()方法,将该View的上下左右四个点位置作为参数传入,从而确定了View在父布局的位置。
ViewGroup:layout()—>setFrame()—>onLayout()
ViewGroup布局流程:ViewGroup同样先调用layout()方法,通过layout()方法内的setFrame()方法设置自己相对于父布局的位置。但是由于ViewGroup包含子View或子ViewGroup,所以需要重写onLayout()方法,在onLayout()方法中遍历自己的子View或子ViewGroup,分别调用它们的layout()方法来完成子View或子ViewGroup的布局。
总结:在performLayout()方法中首先调用的是顶级布局DecorView的Layout()方法,由于DecorView是一个ViewGroup,所以它调用的是父类View的layout()方法(原因在下面PS中介绍),并且在layout()方法中确定了自己的位置之后再重写onLayout()方法来遍历子View和子ViewGroup,通过调用View和ViewGroup的layout()方法来确定它们在父布局DecorView中的位置,这样一步步往下递归遍历,直到所有的View都确定了在父布局的位置为止。
PS:
1.ViewGroup的layout()方法是final修饰的,不能被重写;View的layout()方法是可以被重写的。
2.在ViewGroup中layout()方法中最终调用的是View的layout()方法。
2.View的onLayout()方法是一个空方法,这是因为View没有子View或子ViewGroup,不需要onLayout()方法。而ViewGroup的onLayout()方法是一个抽象方法,即ViewGroup的子类必须重写这个方法,因为ViewGroup包含子View或子ViewGroup,需要在onLayout()方法中遍历View或子ViewGroup并调用它们的layout()方法,从而确定它们在父布局中的位置。
五:performDraw绘制
在performTraversals()方法中,首先调用performMeasure()方法来测量所有View和ViewGroup的宽高大小。之后就是调用performLayout()方法确定每个View和ViewGroup在其父布局中的位置。最后就是调用performDraw进行具体的绘制。
绘制涉及的方法:
View:draw()—>drawBackground()—>onDraw()—>onDrawScrollBars()。
View绘制流程:首先调用的是draw()方法,在该方法内依次调用drawBackground()方法来绘制View背景,调用onDraw()方法绘制View内容,最后调用onDrawScrollBars()方法绘制装饰。
ViewGroup:draw()—>drawBackground()—>onDraw()—>dispatchDraw()—>onDrawScrollBars()。
ViewGroup绘制流程:在ViewGroup的draw()方法中,在调用drawBackground()方法来绘制View背景、调用onDraw()方法绘制View内容之后还需要调用dispatchDraw()方法来绘制子View和子ViewGroup,具体是在dispatchDraw()方法内循环遍历子View和子ViewGroup,并且调用它们的draw()方法进行绘制。
总结:在performDraw()方法中首先调用顶级布局DecorView的draw()方法绘制自身,由于DecorView是一个ViewGroup,所以在绘制完自己之后还要调用dispatchDraw()方法来遍历绘制它的子View和子ViewGroup。这样一步步往下递归遍历绘制,直到所有View和ViewGroup都绘制完成为止。
PS:在View类中的onDraw()方法为空方法,具体的绘制逻辑需要在子类中的onDraw()方法中实现。
二十七、equals和==区别
二十八、Array和Linked区别
二十九、线程池
三十、进程间通信方式
三十一、Android中PX、DP、SP的区别
px : 其实就是像素单位,比如我们通常说的手机分辨列表800*400都是px的单位
sp : 同dp相似,还会根据用户的字体大小偏好来缩放
dp : 虚拟像素,在不同的像素密度的设备上会自动适配
dip: 同dp
三十二、Android之View的绘制流程解析
https://blog.csdn.net/yuncaidaishu/article/details/100018089
三十三、Android持久化存储的几种方式
https://blog.csdn.net/qq_35700731/article/details/78781540
三十四、JMeter性能测试
https://blog.csdn.net/u012111923/article/details/80705141
三十五、