此篇博文是总结别人的面试题,一般讲面经的人都只是有题目,或是总结的也不是很全,想着自己把别人面试中遇到的问题进行总结,算给自己日后找工作做好准备。以后会不断更新,争取来年跳槽找个好工作。
一般APP可能存在以下问题:启动慢、页面跳转慢、事件响应慢、滑动和动画卡顿、展现内容慢等
Android性能优化可以从几下几方面进行:
1) 利用Hierarchy Viewer工具查看布局,针对红色圆圈的布局要查看是否太复杂,减少UI重复绘制时间;
2) 防止过度绘制,减少布局层次;
3) 使用merge\viewStub\include等优化布局;
4) 对于首次启动黑屏问题,可以设计一个.9图片替换掉,间接减少用户等待时间。
使用Analyze APK功能查看apk中各个模块占用的空间来查看要优化的部分
1) 使用Lint检查,去除无用资源、无用的变量、无用的类等
2) 图片优化,使用TinyPng或是webp格式的图片(webp格式图片仅限4.0+设备),有alpha值的jpg图片,经过webp转换后,无法在4.0, 4.1的系统上运行,只有4.2.1+以上的机型,才能解析无损或有透明度调的webp图片。对于适配要求不高的图片,可以只提供一套大图
3) 打包时开启shrinkResources去除依赖库中的无用资源
4) 代码混淆Proguard,主要是对Java代码进行压缩、混淆、优化、预编译等操作,达到删除冗余、增加安全防护、减少代码大小;
5) 可以在gradle配置resConfig选择打包的语方,去掉不需要支持的语言;
defaultConfig {
resConfigs "zh"
}
删除无用的so库,可在defaultConfig中配置
defaultConfig {
ndk {
abiFilters 'armeabi', 'armeabi-v7a'
}
}
6) 使用微信资源压缩打包AndResGuard,主要是对res目录下的文件夹和文件名称替换,修改resources.arsc里对应的R与资源映射关系,混淆资源ID的长度和资源文件名,同时利用7zip深度压缩,原理https://mp.weixin.qq.com/s?__biz=MzAwNDY1ODY2OQ==&mid=208135658&idx=1&sn=ac9bd6b4927e9e82f9fa14e396183a8f#rd【安装包立减1M–微信Android资源混淆打包工具】
7) 避免重复库,使用更小的库,或是只用第三库中部分代码,可以只把部分代码copy出来
8) 使用shape来代替形状态规则,色彩简单的图片
9) 尝试插件化加载某些业务模块
1) 使用LeakCanary检测内存泄漏;
2) 减少对象的创建,尤其在onDraw函数中,字符串拼接使用StringBuilder或StringBuffer
3) 避免加载过大的图片,压缩或使用对象池;设置图片的编码格式RGB565,可以比RGB888的编码格式每像素少占两个字节
对于知道显示尺寸的图片,可以先只读取图片大小
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
然后计算缩放比例
Bitmap bitmap = BitmapFactory.decodeFile(imageFile, opts);
opts.inSampleSize = 4 ;
再读取图片,用完后要对bitmap进行recycle
bitmap.recycle();
bitmap = null;
4) 使用ArrayMap、SparseArray代替HashMap, 为避免自动装箱,可使用SparseBoolMap
可以通过Android Studio中Monitor工具查看Network Monitor,查看某一时刻流量使用情况,也可以使用抓包工具查看网络请求。具体优化点如下:
可以将一些实时性不大的数据缓存起来,让应用启动一次只请求一次;还可以打包网络请求,将需要请求的接口放到一起请求;能调一次API接口请求到的数据,就不调用两次。
服务端只返回客户端需要的数据,而不是把所有数据全返回,在请求图片时,可以传分辨率给服务器,按需获取图片。
压缩传输的数据量(但要注意大于1KB才进行压缩,否则会得不偿失)。
JSON是XML格式,且是以字符存在的,在数据量还可是进行压缩,而ProtoBuffer是二进制格式的,在表示大数据时,空间比JSON小很多。
在2G或是3G网络下,用户可能对图片并不感兴趣,针对这种情况可不自动加载图片或是加载低分辨率的图片以节省流量,还需要设置一个开关让用户可以选择是否显示极速模式。
在网络情况好的时候打包请求。
在一个页面没有请求完网络数据,跳转到另一页面之前,把之前的网络请求都取消掉,不再等待,也不再接收数据。
1) 应用启动过程:Applciation构造器方法->attachBaseContext() -> onCreate() -> Activity构造方法 -> onCreate() -> 配置主题中背景等属性 -> onStart() -> onResume() -> 测量、布局、绘制显示在界面上。减少在Application和首页Activity的初始化函数中做耗时操作。
可以采用懒加载机制,一些可以在首页渲染完再加载的操作延迟加载
getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
myHandler.post(mLoadingRunnable);
}
});
getWindow获取的是一个PhoneWindow对象,其初始化时间为这个Activity attach时候,DecorView是PhoneWindow的一个内部类,它是在父类的onCreate主法被初始化的。这里之所以有两个post,能使它在第二个performTraversals之后执行,而不是在第一帧绘制完成后才去执行。
2) 设置闪屏页的主题,窗口背景为一张图片, @drawable/splash,而不用给闪屏页添加布局,让其一进来就显示图片,防止页面启动黑屏。
android5.0+设备,允许使用adb dump出电量使用统计信息,再使用Google提供电池历史数据分析工具Battery Historian
1) 优化网络,因为网络请求会使手机电量也随着耗用;
2) 慎用WakeLock,它会使CPU保持运转,用完需要及时释放,屏幕一直亮着,从而增大耗电;
3) 监听手机充电状态,只要设备连接或断开电源,BatteryManager就会广播相应的操作,这时就可以注册receiver来监听,此时可以用来同步用户数据,或是Log上传;
4) GPS定位使用完了要及时关闭,减少更新频率;
1) 压力测试,采用Monkey测试应用;
2) 测试不同的android版本,不同厂商机型,同一厂商不同型号android机器;
3) 在无网络、弱网络、4G、wifi等网络情况下测试手机,有无SD卡,充电等状态测试应用;
4) 对于一些边界条件进行测试;
在getView()方法中,如果有缓存加载布局,就直接使用convertView对象,从而避免滑动listview时调用getView()方法每次都是加载布局
优化getView()方法中每次调用findViewByID()方法获取控件,用于对控件的实例进行缓存
设置OnScrollListener监听,在onScrollStateChanged里,滑动时不展示图片,或是只展示内存中图片
半透明需要大量乘法计算,滑动时不停重绘会造成大量的计算,大比较差的机器上会比较卡
listView控件中设置onClickListener时,可以让ViewHolder实现OnClickListener,在ViewHolder中设置一个position
1S 60帧是人眼和人脑协作的极限,超过60fps人眼和人脑无法感知,12fps类似于手快速释书的操作,24fps是人眼感知连续性运动,通常电影就是这个频率。低于30fps无法顺畅表达绚丽的画面。1000/60约等于16ms,Android系统每隔16ms就会发出VSYNC(Vertical Synchronization的缩写)信号,触发对UI进行渲染,如果CPU和GPU处理时间因为各种原因都大于一个VSync的间隔,会发现第二个VSync还在处理前一个区域的绘制,从而导致丢帧情况。等价于同一张图看了32ms而不是16ms,用户就能感觉出卡顿来。
其中CPU和GPU负责的工作分别是:
CPU:负责Measure, Layout, Record, Execute的计算操作
GPU: 负责Rasterization(栅格化)操作,栅格化是绘制Button, Shape, Path, String, Bitmap等组件最基本的操作,它把组件拆分到不同的像素上进行显示。
XML布局文件在CPU中先转换为多边形或纹理,再传递给GPU进行栅格化。
要想改变绘制性能,主要从以下两方面:
CPU方面:减少不必要的布局和无效布局
GPU方面:过度绘制(overdraw)
过度绘制(overdraw): 是指屏幕上某个像素在同一帧的时间内被绘制多次,可通过开发者选项中的->调试GPU过度绘制->显示GPU过度绘制来查看每一像素绘制次数,减少红色区域,让屏幕看到更多的蓝色区域。
OverDraw的处理方案:
1) 去掉window默认背景,当我们自定义布局时一般会添加一张背景图或设置背景色,这样DecorView的background对我们来说是无用的,可在theme中添加android:window=”@null”
2) 在给某个view或viewGroup设置背景时,先思考下是否真的有必要,或分段设置在子view上。
3) 使用canvas.clipRect帮助系统识别那些可见区域,只有在这个区域内的才会被绘制,其他区域会被忽略
4) 使用ViewStub延迟加载布局,在需要加载布局时通过viewStub.inflate()来显示布局。
5) 使用merge标签,使用merge标签需要注意两点,子视图不需要指定任何针对父布局的布局属性(没有background和padding属性)。父布局仅仅是一个容器,子视图只需要直接添加父视图上用于显示就行。另一点,若在LinearLayout中嵌套一个布局,而这个布局的概节点也是LinearLayout,这样多一层没有用的嵌套,可以使用merge
merge只能作为XML布局的根标签使用,当inflate布局时,必须指定一个父ViewGroup,且必须设定attachToRoot为true
6) 巧用Hierarchy Viewer查看布局层次,减少不必要的层次,所有子View都有3个圆圈(红、黄、绿),红圆圈表示这个view相对于其他view,操作运行慢
7) 使用StrickMode严苛模式检测耗时与内存问题
8) 能用一个控件实现的布局,就别用两个,可以用SpannableStringBuilder组装不同颜色文字,条目之间隔可以使用Space控间,textView+ImageView可以用textView的drawableLeft等进行设置。
流畅度检测之前一直用FPS(手机屏幕显示的内容是通过SurfaceFLing类,把当前系统里所有进程需要显示的信息合成一帧,再交至屏幕上显示),FPS就是1S内SurfaceFLinger提交到屏幕的帧数。用FPS来评测一个应用是否卡顿存在两个问题:
为此我们可以用Choreographer帧率检测,Choreographer是一个消息处理器,根据sync信号来计算frame,而计算frame方式就是处理三种回调,包括事件回调、动画回调、绘制回调。
Choreographer中有个回调接口FrameCallback,当新的一帧被绘制时会调用doFrame方法,我们只要自定义实现FrameCallback接口,重写doFrame方法,计算每一帧的开始渲染时间,在下一帧被处理时,计算时间差就可计算该帧渲染时间,从而判断是否出现掉帧。在需要检测的Activity中调用 SMFrameCallback.getInstance().start()即可。
具体实现方法参考:http://www.jianshu.com/p/d126640eccb1【卡顿分析,正确评测流畅度】
当一个点击事件发生后,系统要将这个事件传递给一个具体的view去处理,主要在Activity, ViewGroup, View间进行传递, 先从根往上传,如果中间被某个View消费掉,事件停止传递。如查事件没有被消费掉,又会从叶子往根节点传。
1) dispatchTouchEvent: 分发点击事件,当点击事件能传递给当前view,该方法就会被调用
针对不同对象,该方法返回参数的差别如下:
返回值:
2) onInterceptTouchEvent: 判断是否拦截事件,ViewGroup中存在。在dispatchTouchEvent内部调用
3) onTouchEvent: 处理点击事件, 在dispatchTouchEvent内部调用
onTouch和onTouchEvent都是在view的dispatchTouchEvent中被调用,但onTouch优先于onTouchEvent执行,如果onTouch方法返回true将事件消费掉,onTouchEvent将不会被执行,
onTouch能够执行的两个条件:onTouchListener的值不得为空,当前点击的控件必须是enable
在HTML5中定义一个JavaScript的方法:
在Android中调用该方法:
bindingView.loadWv.getSettings().setJavaScriptEnabled(true);//bindingView.loadWv是WebView控件
bindingView.loadWv.loadUrl("file:///android_asset/104.html");//假设加载的是静态页面,放到main下的assets文件夹下,和res, java平齐
//调用JavaScript中的changeColor方法如下
String color = "#00ee00";
bindingView.loadWv.loadUrl("javascript: changeColor('" + color + "');");
先在html中添加跳转的文本
"baobao.gotoAnyWhere('gotoPersonCenter')">gotoPersonCenter
在Android中添加要被调用的方法
class JSInteface {
@JavascriptInterface
public void gotoAnyWhere(String url) {
if (!TextUtils.isEmpty(url)) {
startActivity(new Intent(TestHtmlActivity.this, UserInfoActivity.class));
}
}
}
//还需要添加下面一句,注册baobao和JSInteface的对应关系
bindingView.loadWv.addJavascriptInterface(new JSInteface(), "baobao");
单例模式,主要需要掌握
需要调用单例对象时才进行对象创建,线程不安全,线程不安全是因为当两个线程同时调用获取单例方法时,都是空的,此时两个线程可能都会调用new方法创建此类实例,导致有多个类实例被创建;
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
锁在方法上,每次获取类实例都要进行同步处理,其他线程都需要等待,所以效率低;
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
线程不安全是因为JVM会进行指令重排,在运行instance = new Singleton();时,并非原子性,需要进行以下三步:
a) 在堆上分配内存空间;
b) 对类中对象进行初始化工作;
c) 返回对象引用到栈中;
b步和c步的顺序是不一定的,如果线程A在进行实例化对象时,调用顺序是a\c\b,而线程B在A线程运行到c步时,还没运行到b步时,进行调用,发现instance!=null,而此时的instance是未经过初始化的,从而可能导致调用该对象出错。
而自JDK1.5以后用volatile关键字修饰的变量,会保证所有线程看到的变量是一致的,且会禁止JVM对指令进行重排序,可以解决此问题。
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
类对象在类加载时就已经初始化,即使类对象此时不需要,也一直占用内存,线程安全
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
public enum Singleton {
INSTANCE;
}
线程安全,只在调用getInstance方法时才加载,实例化,且只加载一次
public class Singleton {
public static Singleton getInstance() {
return Holder.instance;
}
private static class Holder {
private static Singleton instance = new Singleton();
}
private Singleton() {}
}
volatile:保证变量可见,即表示线程本地内存无效,当一个线程修改共享变量后会立即被更新到主内存中,当其他线程读取变量时,会直接从主存中读取。
Java中保证volatile修饰的变量,所有线程看的值都是一样的,如果某个线程对volatile修饰的共享变量进行更新,其他线程可以立马看到这个更新。并且它能够防止指令重排序。
ThreadLocal:每个线程都会持有一个该threadlocal变量的副本,即通过ThreadLocal修饰的变量变成了只属于当前线程的局部变量,当不同的线程访问同一个ThreadLocal变量,得到的值可能是不同的,它有两个重要的方法,一个是get,一个是set,对应读和写。
静态内部类实现单例的原理:由于内部类只会被加载一次,也就是只会执行一次初始化语句,所以保证了实例的唯一。
所有的类都是在使用它的时候被加载的,包括内部类。所以内部类不会随着外部类的加载而加载,只有在使用它的时候才会被加载。
Binder是Android系统进程间通信方式之一,它使用Client-Server通信方式:一个进程作为Server提供诸如视频、音频解码服务,多个进程作为Client向Server发起服务请求,获得所需服务。
Binder是一个实体位于Server对象中,该对象提供一套方法用以实现对服务的请求,可以看成Server提供的实现某个特定服务的访问接入点;Client通过这个接入点向Server发送请求来使用服务。
Binder对象是一个可以跨进程引用的对象,它的实体位于Server进程中,引用却遍步于系统各个进程之中。
Binder框架定义了四个角色:Client, Server, ServiceManager, Binder驱动
1) Binder驱动:负责进程间Binder通信的建立,Binder在进程中的传递,Binder引用计数管理,数据包在进程间的传递和交互等一系列底层支持;
2) ServiceManager:将字符形式的Binder转化成Client中对该Binder的引用,使Client能通过Binder名字获取对Server中Binder实体的引用。
Server创建Binder实体,取一个字符形式名字AAA,将这个Binder和名字以数据包的形式通过Binder驱动发送给SeviceManager,通知ServiceManager注册一个叫AAA的Binder,它位于Server中。驱动为这个穿过进程边界的Binder创建位于内核中的实体节点及ServiceManager对实体的引用,打包传回给ServiceManager,ServiceManager收到数据包后,从中取出名字和引用填入一张查找表。
3) Client获取实名Binder的引用
Server向ServiceManager注册了Binder的实体及其名字后,Client就可通过名字获得该Binder的引用。
在 manifest中, , 和中可以通过设置:android:process ,指明当前组件在哪个进程中运行。
一个项目中有几个进程,默认的application会启动几次,即oncreate会被调用多次。
局部变量的基本类型和引用类型存储在栈中,引用的对象实体存储在堆中。因为它们属于方法当中的变量,生命周期会随着方法一起结束。
String str="hello";
SoftReference soft=new SoftReference(str);
WeakReference soft=new WeakReference(str);
ReferenceQueue super String> q=new ReferenceQueue();
//现在我们由上面的强引用创建一个虚引用,所以现在str有两个引用指向它
PhantomReference p=new PhantomReference(str, q);
Java堆内存中存放着几乎所有对象的实例,垃圾回收器在对堆对象进行回收前,会先确定哪些对象可以回收,哪些对象正在被使用。主要有以下两种方法:
(1)引用计数法:给对象添加一个引用计数器,有一个地方引用,计数器+1, 引用失效,计数器-1, 当计数器为0时代表对象不再被使用;
优点:执行效率高; 缺点:很难解决对象间相互循环引用的问题;
(2)可达性分析算法:将“GC Roots”对象作为起点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连,GC Roots到这个对象不可达,证明此对象不可用。
Java中可作为GC Roots的对象包括以下几种:
1) 虚拟机栈中引用的对象;
2) 方法区中类静态属性引用的对象;
3) 方法区中常量引用的对象;
4) 本地方法栈中JNI引用的对象;
在可达性分析算法中不可达对象,它们也不是立马被回收,而要经过两次标记过程:若发现没有与GC Roots相连接的引用链,将会被第一次标记并进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法,若对象没有覆盖finalize()方法或finalize()方法已被虚拟机调用过,则虚拟机将回收它。
若对象判定为有必要执行finalize()方法,将会被放置在F-Queue队列中,此时GC将对F-Queue中对象进行第二次标记,若对象在finalize()方法中被引用,将从“即将回收”集合中移除。
根据调用是否属于同进程而返回不同的实例对象。
均是IBinder的子类,IBinder定义了binder IPC的通信协议
1) Bn-代表Native(也叫BbBinder):真正Server端的binder操作及状态的维护就是通过继承自BbBinder来实现的,是server端用于接收消息的通道BbBinder是serice作为binder的本质所在。
2) Bp代表Proxy:是client端创建的用于消息发送的代理。
1) 首先在定义屏幕尺寸上,一般选用dp,文字上选用sp;
2) UI切图时,根据不同的分辨率,切h/xh/xxh/xxxh等不同大小的图;图片做成.9.png;对于特殊屏幕切图,可新建相应的分辨率的文件夹进行存放,如drawable-hdpi-854*480存放854*480 hdpi大小的图片;
3) 尽量按比例来设置屏幕权重,如用Linearlayout布局,设置weight; 使用wrap_content、match_parent、weight
4) 同时针对一些特殊屏幕适配可以在每个分辨率下面建一套layout,比如layout-480x800,layout-1280x720,1080x1080等。这种方法的缺点就是产品需要改点需求的时候,每一个layout文件都需要去修改,增加了适配的工作量。
5) 使用布局别名:如适配手机的单面板(默认)布局:res/layout/main.xml; 适配尺寸>7寸平板的双面板布局:res/layout/main_twopanes.xml
可以在res下面建立相应国家的values-##(-r**)来提供对应的字符串资源,中间的##代表语言编号,后面的代表区域编号,如果某种语言不需要提供区域,后面的-r则不需要,如果需要适配英文,则命名values-en,若需要适配美国英文:values-en-rUS、英国英文:values-en-rGB ,简体中文:values-zh-rCN, 繁体中文:values-zh-rTW,中文(香港):values-zh-rHK,再在相应的文件夹下写上对应的strings.xml文件。具体对应关系可查看:http://blog.csdn.net/u011002668/article/details/50442210
如果需要根据android系统版本来设置不同的高度或是样式,可能过添加values-v11, values-v14, values-v19等不同文件夹,专门给相应的版本适配dimens,styles等。
values-v11代表在API 11+的设备上,用该目录下的styles.xml代替res/values/styles.xml
values-v14代表在API 14+的设备上,用该目录下的styles.xml代替res/values/styles.xml
values-v19代表在API 19 +的设备上,用该目录下的styles.xml代替res/values/styles.xml
其中API 11+代表android 3.0 +, API 14+代表android 4.0+, API 19+代表android 4.4 +
举个例子:对于不同的android API版本的手机,可能在设置高度或是样式上会要求不一样,比如在设置沉浸式状态栏时,在4.4之前状态栏一直是黑色的,4.4中可设置windowTranslucentStatus给状态栏着色,5.0引入了Material Design风格,可直接在主题中设置状态栏的颜色。如果设置沉浸式状态栏,标题栏还保持原来的高度,会使标题栏会和状态栏重叠。对于标题栏的高度,4.4以上版本,需要加上状态栏的高度,而4.4以下版本则保持原标题栏高度即可,可以添加多个values版本,对不同的版本,设置不同的dimens.xml。
有些手机厂商自改手机系统,可能导致程序需要一些特殊配置,如锤子手机、三星手机、小米手机等,导致消息推送或是一些其他原因不适配,则需要获取当前手机型号版本,进行特殊适配。
hashmap实际上是一个数据+链表的数据结构,hashmap底层是一个数组结构,数组中每一项又是一个链表。
hashmap通过hash算法计算出key对应的hash码,再通过这个hash码与HashMap中的Entry数组长度来计算得到对应在数组中位置值i,HashMap在初始化的时候默认会把Entry数组的长度设置为16或者设置为大于或等于指定大小的2的N次幂。indexFor的方法很简单 return hash&length-1, length-1 用二进制表示 一定是 011111…., 任何数比 length 小在按位与运算中都会得到这个数本身,这样不同的key算出的index相同的概率就非常小,数据在数组上分布也比较均匀。
hashmap会根据hash值得到这个元素在数组中的位置(即下标),如果该数组位置上已经存放有其他元素,会对比key是否相同,若相同,则会覆盖旧值。若不相同,该位置上的元素将以链表形式存放,新加入的放在链头,HashMap会把i处对应的Entry链表元素整体向后移动一位,把旧的Entry取出来放到新的Entry的next属性上, 然后把新的Entry放在旧的Entry的位置上, 这样就解决了hashcode的冲突问题。
如果Entry不存在,直接调用addEntry方法。
首先计算key的hashcode,找到数组中对应位置的某一元素,再通过key的equals方法在对应位置的链表中找到需要的元素。
算可以重写
静态方法不依赖类就可以访问,不用通过new一个对象实例再调用其方法,而重定是依赖对象的,static修饰的方法是不依赖类的。
但子类可以覆盖父类的方法,前提是必须保证子类权限大于等于父类,父类的方法为静态,子类在覆盖时也必须是静态。覆盖时必须保证返回值、函数名、参数列表完全相同才可以,只有方法实现可以不同,由子类去实现自己特有的一些功能。
TCP连接三次握手:
客户端向服务器发送一个报文,服务器向客户端发回确认报文,客户端再发送一次加以确认;
1) 建立连接时,客户端发送SYN=1报文到服务器,并置发送序号为X,进入SYN_SEND状态,等待服务器确认;(SYN建立联机)
2) 服务器收到syn包,必须确认客户的SYN,同时自己也发送一个SYN包,即SYN+ACK报文,此时服务器进入SYN_RECV状态;(ACK确认)
3) 客户端收到服务器的SYN+ACK包,并向服务器发送确认包ACK报文,此时发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
四次挥手
连接的释放需要发送4个包,称为四次挥手,客户端和服务器都可以主动发起挥手操作。
1) 客户端发送一个FIN(结束报文),用来关闭客户端到服务器的数据传送;
2) 服务器收到客户端发送的FIN,向它发回一个ACK,确认序号+1,和SYN一样,一个FIN将占用一个序号;
3) 服务器关闭与客户端的连接,发送一个FIN给客户端;
4) 客户端发回ACK报文确认,并将确认序号+1;
因为服务端的LISTEN状态下的SOCKET当收到SYN报文连接请求后,它可以把SYN和ACK放在一个报文中发送,但关闭连接,当收到对方FIN报文通知时,它仅代表对方没有数据传送了,但接收方未必所有数据都发送给对方了,所以接收方未必会立马关闭SOCKET,可能还需要发送一些数据给对方后,再发送FIN报文给对方表示接收方同意关闭连接,所以ACK报文和FIN报文多数情况下是分开发送的。
1) 一个程序至少有一个进程,一个进程至少有一个线程;
2) 线程的划分尺度比进程小,使得多线程程序的并发性高;
3) 进程在执行过程中拥有独立的内存空间,而多个线程共享内存,从而极大地提高程序的运行效率;
4) 线程不能独立运行,必须依存在应用程序中,由就算程序提供多个线程执行;
5) 进程是操作系统分配资源的单位,线程是进程的一个实体,是CPU调度和分派的基本单位。
异步请求:发送方发出数据后,不等接收方发回响应,继续接着发送下一个数据包的通讯方式,可实现多任务并发执行;
同步在一定程度上可以看做是单线程,这个线程请求一个方法后就等待给他回复,否则他不往下执行;
异步在一定程度上可以看做是多线程,请求一个方法后,不管是否返回结果,继续执行其他方法。
Android中资源可以分成两类:Assets和Res
Assets: 放在工程根目录Assets子目录下,这些文件最终会原封不动地打包进APK中,访问这些文件需要通过指定文件名来访问;
Res: 存放.png, raw, xml等资源,编译打包时,会把资源文件打包成二进制文件,对所有资源都赋予一个资源ID常量,并生成一个资源索引表resources.arsc.
resources.arsc: 记录应用程序所有资源目录信息,包括资源名称、类型、值、ID及配置的维度信息。
资源ID最终格式是0xPPTTNNNN,是一个无符号4字节整数,最高字节PP代表PackageId(相当于一个命名空间,限定资源来源), 次高字节TT代表TypeId(资源类型), 最低两字节代表Entry ID(每一个资源在其所属的资源类型中出现的次序)
Android资源管理框架是由AssetManager和Resources两个类来完成的,Resources可根据ID来查找资源,AssetManager根据文件名来查找资源,一个资源ID对应一个文件,Resource类先根据ID来找到资源的文件名称,再将该文件名称交给AssetManager类来打开对应的文件,而AssetManager最终调用了JNI的方法来获取资源。
首先获取ResTable, 再从resTable当中去找到相应的资源,ResTable实际就是apk解压出来的resources.arsc.