Android研究的一些总结

CSDN (Chinese Software Developer Network) 创立于1999年,是中国最大的IT社区和服务平台,为中国的软件开发者和IT从业者提供知识传播、职业发展、软件开发等全生命周期服务,满足他们在职业发展中学习及共享知识和信息、建立职业发展社交圈、通过软件开发实现技术商业化等刚性需求。拥有超过3000万注册会员(其中活跃会员800万)、50万注册企业及合作伙伴。
旗下拥有:全球最大中文IT技术社区:csdn、权威IT专业技术期刊:《程序员》杂志、IT人力资源服务:科锐福克斯、IT技术学习平台:乐知教育、代码托管+社交编程平台:code、移动开发工具和服务聚合平台:mobilehub、IT专属求职网站:job、中文软件外包和项目交易平台:csto、程序员深度交流社区:iteye、中国最大技术管理者平台:CTO俱乐部、云计算产业人士沙龙:云计算俱乐部、面向移动开发者的技术组织:移动开发者俱乐部、面向全国大学生的技术组织:高校俱乐部。

Activity、Fragment相关

1、Activity正常和异常情况下的生命周期

正常情况如下图:

这里写图片描述

tips:旧Activity的pause、stop完成后, 新Activity才会执行create - start……,不要在pause、stop中执行耗时操作;如果新Activity使用透明主题,旧Activity不会执行onStop方法,重新返回旧Activity时也不会执行restart、start,只会执行resume,可以理解为,透明主题的Activity等同于dialog或popuoWindow;

异常情况下:
① 内存不足导致Activity被杀死,然后再恢复;
② 配置改变导致Activity被杀死(比如屏幕方向改变),然后重新恢复;

这两种异常情况下,Activity被杀死的回调顺序是:
onPause和onSaveInstanceState(这两个方法调用没有固定顺序) - onstop - onDestroy;

Activity重新恢复的回调顺序是:
onCreate - onStart - onResume和onRestoreInstanceState(这两个方法没有固定顺序)

对于第二种异常情况,可以通过在manifest中设置Activity标签下的configChanges属性,来设置其是否重启Activity。比如设置”Android:configChanges = “orientation”后,屏幕方向改变时,不会重启Activity,而是回调Activity的onConfigurationChanged()方法。

2、Activity启动模式

Activity的启动特性有两种:一种是“是否重新创建”,一种“放在哪个任务栈”。

在Manifest中,LaunchMode属性控制Activity“是否重新创建”,TaskAffinity属性则是控制“任务栈”的,这两个属性可以同时生效。

在代码中,启动Activity时,通过intent.addFlag()来添加各种Flag,每一种Flag都包含了Activity“是否重新创建”和“放在哪个任务栈”两个属性,方便开发者设置两种属性。通过该方法设置模式会覆盖manifest中的模式。

1、LaunchMode
standard:
标准模式,这是系统默认模式。每次启动Activity都会创建一个新的实例。

singleTop:
栈顶复用模式。如果Activity已经位于栈顶,则不会调用onCreate方法重建,只会调用onNewIntent方法启动;如果Acitvity不在栈顶,则重新创建;

singleTask:
栈内复用模式。通过一些方法可以指定Activity所在的栈(比如taskAffinity属性、FLAG等),如果Activity已经存在于指定栈中,则不会重建,只会调用onNewIntent方法,并销毁该Activity上面的Activity,让其处于栈顶;如果不存在指定栈或指定栈中不存在该Activity,则在指定栈中重新创建;

singleInstance:
单例模式。如果Activity不存在(任何栈中都没有),就为该Activity创建一个栈,并创建该Activity;如果已经存在,就调用onNewIntent方法;在这个Activity中启动其它Activity,其它Acitivity不会在该栈中(具体在哪个栈取决于设置),该Acitivty是独占一个栈的。

3、Activity启动过程

1、各种重载的startActivity方法最终都会调用startActivityForResult方法,这个方法经过一些列的调用,最终会调用AppLicationThread的scheduleLaunchActivity方法;

2、AppLicationThread是ActivityThread的内部类,scheduleLaunchActivity方法很简单,就是启用Handler发送一个消息。最终Handler在handMessage方法中,会调用ActivityThread类的handleLaunchActivity方法,这个方法最终又会调用performLaunchActivity方法;

3、performLaunchActivity里会做4件事:通过类加载器加载Activity并创建对象、获取AppLication对象、创建ContextImpl对象、调用Activity的attach方法将三者进行绑定。attach方法中就会完成Window的创建、绑定等操作;

4、在ActivityThread类里,就会通过调用Instrumentation的callActivityOnCreate、callActivityOnResume……等方法,这样Activity就会接收到生命周期的回调。

4、Fragment生命周期

Fragment生命周期:
1. 启动:onAttach、onCreate、onCreateView、onActivityCreated、onStart、onResume
2. 暂停、停止:onPause、onStop
3. 销毁:onPause、onStop、onDestroyView、onDestroy、onDetach

Activity 和 Fragment 生命周期的交互:
1. 因为Fragment是依附于Activity的,因此所有启动类动作都是Activity先执行,Fragment再执行,暂停、销毁类动作则是Fragment先执行
2. 执行Activity的onCreate后,执行Fragment的onAttach ~ onActivityCreated
3. 执行Activity的onStart后,执行Fragment的onStart(onResume同理)
4. 执行Fragment的onPause后,执行Activity的onPause(onStop同理)
5. 执行Fragment的onDestroyView、onDestroy、onDetach后,执行Activity的onDestroy

5、Fragment和Activity通信

1、通过接口,自定Listener;
2、Activity和拿到Fragment的实例,Fragment也可以通过getActivity方法得到Activity的实例,互相调用public方法即可;
3、通过eventBus或广播;

View、Window相关

1、Window的工作原理

Window表示一个窗口的概念,负责显示画面、UI交互等。它的唯一实现类就是PhoneWindow,它有一个内部类Callback,负责传递Window的各种通知(Activity、Dialog实现该接口,就可以接受到touch通知了)。Window会绑定一个WindowManager,这个WindowManager负责一些具体工作的实现,比如添加view。

WindowManager是一个接口,同时还继承了ViewManager接口(这个接口声明了添加、删除view等方法)。WindowManagerImpl是实现类,负责大部分具体实现,但是添加、删除view等方法是委托给WindowManagerGlobal完成,最终它会新建一个ViewImpl,把view添加到ViewImpl中去,后面的测量布局绘制流程也会有这个ViewImlp发起。

Activity的attach方法中会new出PhoneWindow对象,然后通过getSystemService得到WindowManager对象,然后将PhoneWindow和WindowManager绑定,这样,Activity就和WindowManager建立了绑定,后面的很多实现就是依托WindowManager来完成的。

Activity的setContentView就是调用PhoneWindow的setContentView方法,这里面又通过一层一层的方法调用,这里就不一一列出每个方法,只说明整体逻辑。首先new一个DecorView,这是PhoneWindow的内部类,继承自FrameLayout。然后根据Activity设置的主题加载不同的布局文件(系统中有很多主题的布局文件),然后找到id为R.id.content的控件(所有主题的布局文件中都有这个FrameLayout,id命名也是一致的),是一个FrameLayout,然后将Activity对应的layout文件添加到这个FrameLayout中。然后,在Activity的onResume方法中就会调用DecorView的setVisible让其显示,这也是为什么在onResume之后我们就能看到View了。

setContentView完成后,ViewImpl就会调用perfomeMeasure、performLayout、performDraw方法启动View的测量布局绘制流程。

2、View的测量布局绘制流程

测量流程由ViewImpl调用performMeasure方法启动,这个方法内部会调用最底层view的measure方法,该方法首先根据父控件(ViewImpl)建议的尺寸,计算出合适的尺寸,然后就调用onMeasure方法。onMeasure中首先会调用measureChildren方法遍历调用子控件的measure方法,把所有子控件的尺寸确定后,最终再确定自己的尺寸。

每个view的measure都是由父控件调用,父控件会把建议尺寸传过来,view根据父控件建议的尺寸,调用onMeasure方法,onMeasure方法中先遍历子控件的尺寸,最后再根据子控件的尺寸确定自身的尺寸。可以看出,measure方法是发起测量流程,由父控件发起,onMeasure方法就是收到测量通知,开始进行实际测量。

布局流程由ViewImpl调用performLayout方法启动,该方法会调用最底层view的layout方法,并传入left、top、right、bottom座标值,确定view的位置。layout方法最后又会调用onLayout方法,onLayout方法的作用是遍历子控件,确定每个子控件的left、top、right、bottom位置,然后调用子控件的layout方法。onLayout由每个控件自己重写(因为每个控件的布局规则不一样)。

同样,父控件根据自身的布局规则和子控件的尺寸,确定每个子控件的位置,然后就调用子控件的layout方法发起布局流程。layout方法确定了自己在父控件中的位置,而onLayout方法则是完成自身内部的布局(子控件的布局)。

绘制流程由ViewImpl调用performDraw方法启动,该方法会调用最底层view的draw方法。draw方法做一些初始化操作,接着就会调用onDraw方法绘制自身,自身绘制完成后,就调用dispathDraw方法,遍历子控件,调用子控件的draw方法,启动子控件的绘制流程。

可以看出如下规则:
1、measure()、layout()、draw()是启动流程、初始化数据,每个控件都是一样的,所以在基类View中,代码共用;

2、onMeasure()、onLayout()、onDraw()是具体的测量、布局、绘制工作,每个控件不一样,所以各个控件自己实现。凡是onXXX开头的方法就是接收到通知,开始自身的绘制工作,而自己的子控件当然也是属于自己的,所以还需要遍历子控件,启动子控件的绘制流程;

4、绘制子控件的过程中,由于每个控件measureChildren()、dispatchDraw()的功能差不多,所以该实现在ViewGroup中,所有子类共用。但是每个控件的布局规则肯定不一样,所以没有layoutChildren方法,而是自己在onLayout中实现。

3、事件分发

在WindowManagerGlobal添加view的过程中,ViewRootImpl会想WMS注册WindowInputEventReceiver,用于接收点击事件。系统接收到点击事件后,通知ViewRootImpl。当接收到点击事件后,通过一系列的操作,最终会调用DecorView的dispatchTouchEvent方法,该方法中,会调用Activity的dispatchTouchEvent方法。同时,它集成自FrameLayout,自然也会启动View的时间分发流程。

这里面有三个重要的方法,dispatchTouchEvent方法是负责第一时间接收事件的,onInterceptTouchEvent方法是判断是否拦截事件的,最后由onTouchEvent方法来处理事件。其中,由于View没有子控件了,所以View是没有onInterceptTouchEvent方法的(该方法位于ViewGroup中),所以,View的时间流程少了一个环节,其它都是一样的。

分发流程如下图:

这里写图片描述

有几个注意的点:
1、如果onInterceptTouchEvent返回true,确定消费,那后面就不会再回调onInterceptTouchEvent方法了,直接给onTouchEvent,如图虚线①那样。如果点击处没有子view,也是这样;
2、如果子控件的onTouchEvent在action_down时返回false,这个false会传给dispatchTouchEvent,通过dispatchTouchEvent又传给了父控件的onInterceptTouchEvent,所以后面的move、up等事件,在onInterceptTouchEvent方法中也会返回true,让父控件消费;
3、如果子控件的onTouchEvent在action_down时返回true,在move、up事件时返回false,是不会传给父控件的,直接抛弃掉;

Android通信、消息机制、线程相关

1、线程的基本知识

1、 线程的状态
创建:new出线程对象,就处于创建状态;
就绪:执行start()方法,就处于就绪状态。处于就绪状态只是表示它准备好了,至于何时开始运行,取决于JVM的调度。已经处于就绪状态的线程,调用start()方法会报错;
运行:当CPU调度到此线程,开始执行里面的run()方法,就处于运行状态;
阻塞:正在运行的时候被暂停,比如sleep、wait等方法,都会导致阻塞。阻塞结束后会自动回到就绪状态,等待CPU调度重新进入运行状态;
死亡:run()方法执行完毕,或调用了stop()方法,就死亡了,无法通过start()再次就绪;

2、线程的sleep()
Thread的静态方法sleep(long millis),让此线进入阻塞状态long毫秒,阻塞结束后重新等待CPU的调度;

3、join()方法
在线程A中,调用线程B的join()方法,即b.join(),那么A将会等待B执行完了才会继续向下执行;

4、线程让步yield()方法
Thread的静态方法yield(),让此线程放弃执行机会,进入就绪状态,让CPU重新调度线程。CPU重新调度线程时,只会调度优先级大于等于此线程的线程,如果没有,就重新调度此线程;

5、线程同步
可以使用对象obj或类.class来作为锁,锁定一块代码或一个方法。锁的意思就是,obj作为锁的这段期间,不能再被当作锁。这样,其它线程想要执行该方法、或其它使用obj作为锁的方法时,需要获取obj作为锁,而在上一个锁释放钱,obj是不能再作为锁的,所以这个方法无法立即执行。通过“锁的唯一性”保证了被锁的代码不会同时执行。

死锁,就是线程1使用了a作为锁,线程2使用了b作为锁。然后线程1需要执行某段代码,该代码需要b作为锁,同时,线程2需要执行某段代码需要a作为锁。导致两个线程都无法执行下去,形成死锁。

6、线程间通信
为什么需要线程间通信?当两个线程要同时对同一个对象进行操作时,才需要协调运行,需要协调运行的方法/代码块就是需要加锁同步的,很显然,这个对象就是锁。

线程间通信用到的方法就是Object类的三个方法wait()、notify()、notifyall(),这三个方法的调用者是锁(也就是那个被当作锁的对象)。线程1使用“锁”调用wait()方法会使当前线程等待,同时锁被释放;线程2使用“锁”调用notify()方法会唤醒线程1。

7、线程池
查看博客

8、Callable和Futrue
Callable接口只声明了一个call方法,主要用于把Callable对象提交到线程池任务中,然后线程池中的新线程会调用Callable对象的call方法。

Callable和Futrue都有一个泛型的参数,配合使用时,这两个泛型参数类型必须一致。Callable泛型参数用于声明任务返回的结果,Futrue的泛型参数就是用于接收这个结果,所以类型肯定是要一致的。

2、对Handler、Looper、MessageQueue的理解

一个线程唯一对应一个Looper和MessageQueue,它是如何实现做到的呢?Looper通过prepare方法实例化、并创建一个MessageQueue,然后以该线程为key,Looper为value,存入静态变量ThreadLocal中。ThreadLocal是一个负责存储数据的类,其原理是通过一个数组,存储key-value数据,在这里,key就是线程,value是Looper。由于是static修饰的,所以全局共用。在同一个线程内重复调用Looper.prepare方法时,会判断TreadLocal中是否已经存过当前线程的key-value,如果已经存过,就会报错,通过这种方式,保证了“一个线程唯一对应一个Looper和MessageQueue”。

Handler的构造方法中,会获取到当前线程的Looper,MessageQueue是Looper的成员变量,自然Handler也就获取到了当前线程对应的MessageQueue。

Handler发送消息就是调用MessageQueue的插入方法,把message插入到消息队列中。至于插入的具体逻辑,要先简单介绍一下Message对象,Message有两个核心成员变量,一个是Handler,一个是next。每条消息都绑定着一个Handler,这样Looper才知道把这条消息交给哪个Handler处理;每个消息都有一个next,这个是用来标识自己的“下一个”,这样,多个消息就形成了首位项链的链表,MessageQueue就是管理着这个消息链表。插入消息的过程,就是把这个消息插入到链表中的过程,具体的插入逻辑会根据当前消息的when参数,遍历所有消息,按照时间首尾相连。

这样,插入消息就分析完成了,接下来就分析如何处理这些消息。

通过调用Looper.loop方法,该线程的Looper就会进入一个无限循环,调用MessageQueue的next()方法读取消息。这里分三种情况,如果成功取到消息,就交给对应的Handler处理;如果next()返回的消息为null,Looper就退出死循环,只有人为设置退出,这里才会返回null;没有消息的时候并不会返回null,而是阻塞在next方法中,知道有新消息为止。

这里有两个阻塞需要解释下,第一个阻塞就是MessageQueue中的next方法,这里的阻塞使用的是Linux管道流机制,只是让next方法沉睡,设定一个唤醒时间,不影响后面的执行,具体原理好像是本地c++代码写的。第二个阻塞是Looper在for循环中调用MessageQueue的next方法,next一直没有返回,这里是真阻塞,就是后面的代码都无法执行。但是为什么不会报ANR呢?因为Android系统就是由消息驱动的,比如接收到用户点击,就发一条消息通知View进行处理,Android系统内部无时不刻都在进行消息处理,ANR的真正含义是:处理某个消息超过了约定时间,或下一条消息到了时间迟迟无法处理。

3、对HandlerThread的理解

HandlerThread集成Thread,在run方法中完成了Looper的初始化,并调用了loop()方法开启循环。跟普通Thread不同的是,这个run方法会在loop()方法中死循环,所以这个线程的run方法不会执行完成,除非认为停止,该线程不会销毁。

HandlerThread创建、启动完成后,需要创建一个Handler实例与之关联,并调用Handler的post(Runnable r)方法发送消息。

因为是通过Handler h = new Handler(HandlerThread.getLooper())创建Handler,这个Handler发送的消息也是由HandlerThread线程里的Looper处理。

post(Runnable r)方法会通过obtain方法获取一个msg,把Runnable放入msg中,然后把这个msg插入消息队列,最终HandlerThread的Looper会获取到这个消息,调用Handler的dispatchHandMessage()方法来处理msg。该方法中会做判断,如果msg中的callable字段为空,就执行handMessage方法(我们自己实现的);如果不为空,就调用该callable的run方法(注意,不是start)。

可以看出,最终handMessage方法是在HandlerThread线程中执行的,所以可以执行耗时操作。

4、对ThreadLocal的理解

ThreadLocal有一个内部类ThreadLocalMap,ThreadLocalMap又有一个内部类Entry。

Thread持有一个ThreadLocalMap对象,ThreadLocalMap又持有一个Entyr[]数组,这个Entyry是存储数据的,等于是每个Thread都对应着一个Entyr[]数组来存储数据。

存数据是通过ThreadLocalMap的set(ThreadLocal local,Object value)方法,等于说每个value都绑定了一个ThreadLocal,这样一来,每个数据都绑定了一个Thread和一个ThreadLocal。

这样设计可能有两个用处,一个是“对于多线程,数据按Thread分类存取,维护更加方便”,一个是“对於单线程,数据又按ThreadLocal分类存取,维护方便”。可以认为ThreadLocal是一个维护数据的工具类、管理类。

5、对AsynTask的理解

AsynTask类中有两个static修饰的线程池,一个负责存储、封装runnable,一个负责执行runnable。由于是静态的,也就是说该app里所有AsynTask共用这两个线程池。

创建AsynTask对象时,构造方法中就会创建一个WorkerRunnable对象(实现了Callable接口),把doInbackground()方法中的耗时逻辑封装到call()方法中(最终在线程池中会调用该call方法)。同事创建一个FutureTask对象,与WorkerRunnable绑定,用于接收WorkRunnable的执行结果。

调用AsyncTask的excute方法后,首先会回调onPreExecute()方法,这个方法是由我们自己实现的,做一些初始化操作。然后把执行参数封装到WorkerRunnable中,把FutureTask插入存储线程池。

存储线程池采用数组队列ArrayQueue存储Runnable,插入的时候,并不是直接把传进来的Runnable/WorkerRunnable参数存入队列,而是新建一个Runnable对象,对原始的参数进行一定的封装,加入一些执行逻辑(比如执行完了需要调用一个方法执行下一个),然后再把新建的Runnable添加到队列中。

添加到完成后就会开始执行:取出“当前”任务,使用“执行线程池”的execute()方法来执行这个任务,也就是说,这个“存储线程池”只是负责封装runnable,加一些控制逻辑,并存储。真正执行是交给另外一个“执行线程池”来负责,一个任务执行完成后,会调用scheduleNext()方法,这个方法就是“存储线程池”封装的时候加的,这个方法的作用就是从“存储线程池”中取出下一个任务,调用“执行线程池”来执行。

执行线程池执行任务时,是new出n个线程(取决于线程池设置),启动这些线程,调用任务的run()方法,而不是start()。

前面已经说过,doInbackground方法被封装到WorkRunnable的call方法中,在call方法中,执行完成后,就会调用AysncTask类的postResult方法通知结果,这个方法里就是使用Handler来通知调用处。

Java语法相关

1、java是值传递还是引用传递

答案是值传递。对于基本类型,传递的是值本身,对于对象,传递的是地址的值。都是值,所以是值传递。

比如,先声明 int i = 3; Object obj = new Object();

对于int y = i;是把3赋值给y,跟i其实毫无关系;
对于Object obj2 = obj;是把new出来的Object对象的地址赋值给obj2,调用obj2对Object进行改变,这个对象就是是真正改变了,obj还是指向这个Object对象。

2、final和static

对于static,一句话就可以说明:static声明的东西(成员变量、方法、代码块)都是属于类的,跟对象无关。

static修饰的成员变量,在类加载的时候就完成了内存分配。而普通成员变量,是在创建对象的时候才分配。
static修饰的方法,是属于类的,所以该方法中不能有super、this关键字,方法里也只能访问属于类的方法、变量。因为它是属于类的,所以不能是抽象的abstract。子类中也使用static修饰的同名方法,是属于子类的静态方法,不属于类的,不算重写。
static修饰的代码块,在类加载的时候初始化自动执行,如果有多个,按声明的先后顺序执行。

对于final,它的主要作用就是告诉jvm它不会改变,有助于jvm做出更好的决定,提高效率。
final修饰的变量,只能赋值一次,要么在声明时赋值,要么在构造方法中。如果编译器知道字段的值不会更改,那么它能安全地在寄存器中高速缓存该值,提高效率。对于基本类型来说,它的值就不可改变了,对于对象来说,它指向的地址不能再改变(不影响该对象本身的变化)。
final修饰的方法和类不能被重写、不能被继承。

3、jvm内存分配和垃圾回收

见博客。

4、强引用(strong)、软引用(soft)、弱引用(weak)

1、默认都是强引用,GC时,只要该对象从root可达,就不会回收;
2、软引用在普通情况下也不会回收,只有到内存不足时才会清理软引用;
3、弱引用在GC时就会被直接清理。由于GC频率并不高,所以大部分情况下,该对象是使用周期内都不会被清理的。

5、equals和hashcode

equals方法的默认实现是比较两个对象的地址,即“return obj1 == obj2”。但是大多数时候我们都是希望它比较对象内容,所以如果有需要的话,一般会重写equals方法;

java中有两种集合,一种是list,有序、可重复的;一种是set,无序、不可重复。对于不可重复的集合,新添加元素时就要判断是否相同,如果挨个使用equals方法进行比较,效率会非常低,所以有了hashcode。每个对象都有hashcode值,就像一个“索引”,插入新元素时,根据新元素的“索引”获取对应的value,如果为null,就说明没有重复,如果不为null,就说明“索引”重复了。注意,索引是有可能重复的,此时再通过equals来判断,这样效率就大大提高了。

java对于equals和hashcode有规范,如果equals相同,hashcode就必须相同,这样的数据在插入集合时效率才更高。

6、java集合、map

1、集合
java的集合基类是Collection接口,有两个子类:List接口、Set接口。List有序可重复,Set无需不可重复。

List有两个子类:ArrayList实现类、Vector实现类。ArrayList非同步,Vector是同步的。

Set有一个子类:HashSet实现类。

ArrayList内部是由数组存储,当添加元素时,会调用其grow()方法“增加数组长度”,具体方法就是新建一个长度+1的数组,把旧数组中的内容copy过来,然后再添加。Vector与ArrayList一样,只是在几乎所有方法上都加了synchronized修饰。

HashSet内部是由HashMap存储数据,利用了HashMap元素不可重复的特点。由于HashMap是非同步的,所以HashSet也是非同步的。

2、map
Map是一个接口,有一个内部类Entry接口,这个Entry可传入两个泛型参数,分别对应key-value。Map有两个实现类:HashMap和HashTable。HashMap是非同步的,HashTable是同步的,两个类的实现是一样的,只是HashTable里面几乎把所有方法都加了synchronized修饰。

HashMap添加元素时,是通过key得到对应的hashcode,然后以hashcode为key,把数据存入Entry中。

7、字符相关

字符编码相关参考博客。

StringBuffer是线程安全的。

网络

TCP/IP是个协议组,可分为四个层次:网络接口层/链路层、网络层、传输层和应用层。

应用层:就是把用户A想要传输的数据按照协议(http、ftp等协议)封装起来,送入下一层(传输层)。另一边的用户B接收到的也是这个封装过的数据,用户B按照这个协议(http、ftp)对这个数据进行解封,就能读懂用户A想表达的意思了。为什么要封装?因为只有按照一定的规则封装,传输层、网络层才能读懂其中的意思,才能正确地传输。这个层对应的就是HTTP、FTP协议。

传输层:每台设备有很多端口号、各种服务,传输称就是解读数据包中的端口号等信息,把这些信息告诉网络层;这个层对应的有TCP、UDP协议。

网络层:网络层就是解读数据包中的ip地址、目的地等,把这些信息告诉链路层。这个层使用最广房的就是IP协议。

网络接口层/链路层:就是网卡、网线、光线等负责传输数据的硬件,通过网络层解析出来的ip地址、目的地,把数据包发送到指定地方;

因此,HTTP是应用层的协议,是从Web服务器传输超文本到本地浏览器的传送协议。

而SOCKET是为了实现以上的通信过程而建立成来的通信管道,而通信的规则采用指定的协议。用socket可以创建tcp连接,也可以创建udp连接,这意味着,用socket可以创建任何协议的连接。

Http协议定义了很多与服务器交互的方法,最基本的有4种,分别是PUT,DELETE,GET,POST,对应着对这个资源的增删改查4个操作。 GET一般用于获取/查询资源信息,而POST一般用于更新资源信息.

GET和POST的区别
1、get是把提交的数据放在url中,通过地址栏来传值;post是放在数据包的body中,通过提交表单来传值;

2、因为浏览器对URL的长度有限制,所以get方式提交的数据长度有限制,而POST方法提交的数据没有限制;

3、get提交的数据就在url地址中,很容易就泄密了,post更安全;

性能优化、apk瘦身

1、UI优化

通过include标签可以复用xml布局文件,这样程序需要加载的xml文件就变少了,自然就节省了内存。

merge标签用于一个xml文件的根标签,当这个xml文件被整体加载到某个父布局中时,会使用父布局中的XXXLayout加载merge的子控件,一般和include标签配合使用,减少布局层级。比如Activity对应的布局是竖直排列的LinearLayout,include导入的标题栏也是竖直排列的LinearLayout,如果导入进来,就有一个LinearLayout层级是多余的。所以标题栏的根标签就不要用LinearLayout,直接用merge,这样导进去的时候,就会用父布局的LinearLayout,减少了层级。

ViewStub在Activity的加载过程中是不会加载、绘制的,只有在需要的时候,开发者调用ViewStub.inflate方法进行加载。比如网络异常、无数据等UI,一般是不会展示的,即使设置为gone,仍旧会加载、绘制,只是没有显示而已。使用viewStub的话,就可以根据需要进行加载了。

另外,由于View的draw、onDraw方法调用是非常频繁的,所以自定义view时,千万不要在这两个方法里执行太复杂的操作,也尽量不要在里面new对象。

GPU过度绘制工具
在手机的“开发者选项”中,打开“显示GPU过度绘制”开关。每个区域如果只被绘制了1次,就是它原本的颜色,如果绘制了2次,就是浅蓝色,3次是绿色,4次是粉色,5次及以上就是红色了。我们的目的是尽可能减少绘制的次数,提高性能。对于父控件,不需要背景色的,就不要设置背景色了,Activity的主题中会设置背景颜色,所以我们也要慎用。

Hierarchy Viewer工具
Hierarchy Viewer在连接手机时,手机上必须启动一个叫View Server的客户端与其进行socket通信,所以我们是无法用真机来调试的,只能用模拟器调试。这里面可以显示该Activity所有View的层级关系。

Android Lint工具
这是AS自带的工具,可以检测很多“优化”方面的问题(不仅仅是UI优化),常用的有下面这些功能:
1、检查无用的资源、import、class等;
2、检查过时的API;
3、检查xml中的重命名,使用px的地方,manifest中注册的Activity不存在,添加了不必要的权限等;
4、代码中的问题,比如可以用局部变量的地方,可以用android优化过的集合parseArrayList,可以用equals代替的地方,有可能出现空指针的地方,拼写错误的地方等等。其实这些一般在代码中都会有加深提示。

发布了35 篇原创文章 · 获赞 52 · 访问量 8万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章