Android学习资料

  1. Service

服务是一个后台运行的组件,执行长时间运行且不需要用户交互的任务。即使应用被销毁也依然可以工作。Service是在main Thread中执行,Service中不能执行耗时操作(网络请求,拷贝数据库,大文件)。

可以在xml中设置Service所在的进程,让Service在另外的进程中执行。

Service执行的操作最多是20s,BroadcastReceiver是10s,Activity是5s。

Activity通过bindService(Intent,ServiceConnection,flag)与Service绑定。

Activity可以通过startService和bindService启动Service。

  1. IntentService

IntentService是一个抽象类,继承自Service,内部存在一个ServiceHandler(Handler)和HandlerThread(Thread)。IntentService是处理异步请求的一个类,在IntentService中有一个工作线程(HandlerThread)来处理耗时操作,启动IntentService的方式和普通的一样,不过当执行完任务之后,IntentService会自动停止。另外可以多次启动IntentService,每一个耗时操作都会以工作队列的形式在IntentService的onHandleIntent回调中执行,并且每次执行一个工作线程。IntentService的本质是:封装了一个HandlerThread和Handler的异步框架

  1. 跨进程通信

Intent,比如拨打电话
ContentProvider数据库存储数据
Broadcast广播通信

File 文件

Socket

Pipe管道
AIDL通信,通过接口共享数据:AIDL是Android IPC机制中很重要的一部分,AIDL主要是通过Binder来实现进程通信的

什么时候使用AIDL

所以使用AIDL只有在你先允许来自不同应用的客户端跨进程通信访问你的Service,并且想要在你的Service处理多线程的时候才是必要的。简单的来说,就是多个客户端,多个线程并发的情况下要使用AIDL。

AIDL 就是定义一个接口,客户端(调用端)通过 bindService 来与远程服务端建立一个连接,在该连接建立时会将返回一个 IBinder 对象,该对象是服务端 Binder 的 BinderProxy。在建立连接时,客户端通过 asInterface 函数将该 BinderProxy 对象包装成本地的 Proxy,并赋值给Proxy类的 mRemote 字段,本地通过 mRemote 即可调用远程方法。

Binder是什么?

  1. wait和sleep的区别

wait是Object的方法,wait是对象锁,锁定方法不让继续执行,当执行notify方法后就会继续执行,sleep 是Thread的方法,sleep 是使线程睡眠,让出cpu,结束后自动继续执行

  1. Service生命周期

startService:

onCreate()-> onStartCommand()-> onDestroy()

bindService:

onCreate()-> onBind()-> onUnBind()->onDestroy()

  1. 简单介绍contentprovider

ContentProvider(内容提供者)是 Android 的四大组件之一,管理 Android 以结构化方式存放的数据,以相对安全的方式封装数据(表)并且提供简易的处理机制和统一的访问接口供其他程序调用。Android 为常见的一些数据提供了默认的 ContentProvider(包括音频、视频、图片和通讯录等)要实现与其他的 ContentProvider 通信首先要查找到对应的 ContentProvider 进行匹配。Android 中 ContenProvider 借助 ContentResolver 通过 Uri 与其他的 ContentProvider 进行匹配通信。其它应用可以通过 ContentResolver 来访问 ContentProvider 提供的数据,而 ContentResolver 通过 uri 来定位自己要访问的数据

Uri:统一资源定位符,每一个 ContentProvider 都拥有一个公共的 URI,用于表示 ContentProvider 所提供的数据。// 示例

content://com.wang.provider.myprovider/tablename/id:

MIME是指定某个扩展名的文件用一种应用程序来打开,就像你用浏览器查看 PDF 格式的文件,浏览器会选择合适的应用来打开一样。Android 中的工作方式跟 HTTP 类似,ContentProvider 会根据 URI 来返回 MIME 类型,ContentProvider 会返回一个包含两部分的字符串。MIME 类型一般包含两部分,如

text/html

text/css

text/xml

application/pdf

Uri 代表要操作的数据,在开发过程中对数据进行获取时需要解析 Uri,Android 提供了两个用于操作 Uri 的工具类,分别为 UriMatcher 和 ContentUris 。

UriMatcher 类用于匹配 Uri

ContentUris 类用于操作 Uri 路径后面的 ID 部分,它有两个比较实用的方法:withAppendedId(Uri uri, long id) 和 parseId(Uri uri)。

  1. 线程和进程的区别

进程:是系统进行资源分配的独立单元

进程是程序的一次执行过程。若程序执行两次甚至多次,则需要两个甚至多个进程。
进程是正在运行程序的抽象。它代表运行的CPU,也称进程是对CPU的抽象。系统资源(如内存、文件)以进程为单位分配。
操作系统为每个进程分配了独立的地址空间

线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。

与线程的区别是:

定义方面:进程是程序在某个数据集合上的一次运行活动;线程是进程中的一个执行路径。(进程可以创建多个线程)
角色方面:在支持线程机制的系统中,进程是系统资源分配的单位,线程是CPU调度的单位。
资源共享方面:进程之间不能共享资源,而线程共享所在进程的地址空间和其它资源。同时线程还有自己的栈和栈指针,程序计数器等寄存器。
独立性方面:进程有自己独立的地址空间,而线程没有,线程必须依赖于进程而存在。
开销方面。进程切换的开销较大。线程相对较小。

  1. 序列化

序列化的目的

(1).永久的保存对象数据(将对象数据保存在文件当中,或者是磁盘中

(2).通过序列化操作将对象数据在网络上进行传输(由于网络传输是以字节流的方式对数据进行传输的.因此序列化的目的是将对象数据转换成字节流的形式)

  (3).将对象数据在进程之间进行传递(Activity之间传递对象数据时,需要在当前的Activity中对对象数据进行序列化操作.在另一个Activity中需要进行反序列化操作讲数据取出)

  (4).Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长(即每个对象都在JVM中)但在现实应用中,就可能要停止JVM运行,但有要保存某些指定的对象,并在将来重新读取被保存的对象。这是Java对象序列化就能够实现该功能。(可选择入数据库、或文件的形式保存)

  (5).序列化对象的时候只是针对变量进行序列化,不针对方法进行序列化.

  (6).在Intent之间,基本的数据类型直接进行相关传递即可,但是一旦数据类型比较复杂的时候,就需要进行序列化操作了.

  1. mvc 、 mvp 、mvvm 的区别

Mvc:View层一般由XML布局文件充当在Model层中我们会进行一些数据处理的工作,比如网络数据请求、数据库操作等。Controller层通常由Activity、Fragment充当,并在其中进行界面、数据相关的业务处理。核心的业务逻辑都写在Controller中,导致Controller中的代码略显臃肿

Mvp:Model层同样提供数据操作的功能,但View层的职责由Activity、Fragment、或某个View控件担任,最后Presenter层作为View层和Model层沟通的中介。通常在View层中,有一个Presenter成员变量,同时我们的View(可以是Activity、Fragment)需要实现一个接口,这样可以将View层中需要的业务逻辑操作通过该接口转交给Presenter来实现,进而Presenter通过Model得到相应的数据,并通过View层实现的接口返回到View中。这样View层和Model层的耦合就解除了,同时也将业务逻辑从View中抽离出来,转移到Presenter中,避免了Activity或Fragment过度臃肿,充斥大量业务逻辑代码。

Mvvm:在MVVM中,View层和Model层进行了双向绑定(即Data Binding),所以Model数据的更改会表现在View上,反之亦然。ViewModel就是用来根据具体情况处理View或Model的变化。

  1. compiledSdk、minimunSdk、targetSdk

minimunSdk:app能够运行的最小版本

compiledSdk:当前编译程序的sdk版本

targetSdk:targetSdkVersion是android向前兼容的主要方式在Android4.4之后的设备上,系统会判断你的targetSdkVersion是否小于19,如果小于的话,那就按照19之前的api方法,如果大于等于19,那么就按照之后的api方法来走,保证了程序运行的一致性。也就是向前兼容性。

  1. 屏幕适配方案

ConstraintLayout百分比布局

动态计算屏幕百分比

适配各种分辨率的布局文件

dp:dp能够让同一数值在不同的分辨率展示出大致相同的尺寸大小。但是当设备的尺寸差异较大的时候,就无能为力了

  1. Okhttp
  1. 、当我们通过OkhttpClient创立一个Call,并发起同步或者异步请求时;
    (2)、okhttp会通过Dispatcher对我们所有的RealCall(Call的具体实现类)进行统一管理,并通过execute()及enqueue()方法对同步或者异步请求进行解决;
    (3)、execute()及enqueue()这两个方法会最终调用RealCall中的getResponseWithInterceptorChain()方法,从阻拦器链中获取返回结果;
    (4)、阻拦器链中,依次通过RetryAndFollowUpInterceptor(重定向阻拦器)、BridgeInterceptor(桥接阻拦器)、CacheInterceptor(缓存阻拦器)、ConnectInterceptor(连接阻拦器)、CallServerInterceptor(网络阻拦器)对请求依次解决,与服务的建立连接后,获取返回数据,再经过上述阻拦器依次解决后,最后将结果返回给调用方。

RetryAndFollowUpInterceptor:负责重定向

构建一个StreamAllocation对象,然后调用下一个拦截器获取结果,从返回结果中获取重定向的request,如果重定向的request不为空的话,并且不超过重定向最大次数的话就进行重定向,否则返回结果。注意:这里是通过一个while(true)的循环完成下一轮的重定向请求。


BridgeInterceptor:负责将原始Requset转换给发送给服务端的Request以及将Response转化成对调用方友好的Response。
具体就是对request添加Content-Type、Content-Length、cookie、Connection、Host、Accept-Encoding等请求头以及对返回结果进行解压、保持cookie等。

CacheInterceptor:负责读取缓存以及更新缓存

ConnectInterceptor:负责与服务器建立连接。

CallServerInterceptor:负责从服务器读取响应的数据

  1. aysncTask

AsyncTask是一个抽象类,它是由Android封装的一个轻量级异步类,它可以在线程池中执行后台任务,然后把执行的进度和最终的结果传递给主线程并在主线程中更新UI。

  1. 实现多线程
    在工作线程中执行任务,如 耗时任务
  2. 异步通信、消息传递
    实现工作线程 & 主线程(UI线程)之间的通信,即:将工作线程的执行结果传递给主线程,从而在主线程中执行相关的UI操作

·  结论
AsyncTask不与任何组件绑定生命周期

·  使用建议
在Activity 或 Fragment中使用 AsyncTask时,最好在Activity 或 Fragment的onDestory()调用 cancel(boolean)

·  若AsyncTask被声明为Activity的非静态内部类,当Activity需销毁时,会因AsyncTask保留对Activity的引用 而导致Activity无法被回收,最终引起内存泄露

·  使用建议
AsyncTask应被声明为Activity的静态内部类

  1. android安全机制

Android将安全设计贯穿系统架构的各个层面,覆盖系统内核、虚拟机、应用程序框架层以及应用层各个环节,力求在开放的同时,也恰当保护用户的数据、应用程序和设备的安全。Android安全模型主要提供以下几种安全机制:

  • 进程沙箱隔离机制
  • 应用程序签名机制
  • 权限声明机制
  • 访问控制机制
  • 进程通信机制
  • 内存管理机制

进程沙箱隔离机制,使得Android应用程序在安装时被赋予独特的用户标识(UID),并永久保持。应用程序及其运行的Dalvik虚拟机运行在独立的Linux进程空间,与其它应用程序完全隔离

  1. 图片缓存

三级缓存:

网络缓存,不优先加载,速度慢,浪费流量

本地缓存,次优先加载,速度快

内存缓存,优先加载,速度快

内存缓存:

·  通过 HashMap键值对的方式保存图片,key为地址,value为图片对象,但因是强引用对象,很容易造成内存溢出,可以尝试SoftReference软引用对象

·  通过 HashMap>SoftReference 为软引用对象(GC垃圾回收会自动回收软引用对象),但在Android2.3+后,系统会优先考虑回收弱引用对象,官方提出使用LruCache

·  通过 LruCache least recentlly use 最少最近使用算法
会将内存控制在一定的大小内, 超出最大值时会自动回收, 这个最大值开发者自己定

Bitmap位图简介

位图文件(Bitmap),扩展名可以是.bmp或者.dib。位图是Windows标准格式图形文件,它将图像定义为由点(像素)组成,每个点可以由多种色彩表示,包括2、4、8、16、24和32位色彩。位图文件是非压缩格式的,需要占用较大存储空间。ARGB_8888 一个像素占四个字节

Bitmap的回收

官方建议我们3.0以后使用recycle()方法进行回收,该方法可以不主动调用,因为垃圾回收器会自动收集不可用的Bitmap对象进行回收.

LruCache是个泛型类,内部采用LinkedHashMap来实现缓存机制,它提供get方法和put方法来获取缓存和添加缓存,其最重要的方法trimToSize是用来移除最少使用的缓存和使用最久的缓存,并添加最新的缓存到队列中

压缩

质量压缩在保持像素的前提下改变图片的位深及透明度等,来达到压缩图片的目的

采样率压缩设置inSampleSize的值(int类型)后,假如设为n,则宽和高都为原来的1/n,宽高都减少,内存降低。

  1. 自定义View的流程

requestLayout:当View的边界,也可以理解为View的宽高,发生了变化,不再适合现在的区域,可以调用requestLayout方法重新对View布局。View执行requestLayout方法,会向上递归到顶级父View中,再执行这个顶级父View的requestLayout,所以其他View的onMeasure,onLayout也可能会被调用

Invalidate:invalidate方法会执行draw过程,重绘View树。 当View的appearance发生改变,比如状态改变(enable,focus),背景改变,隐显改变等,这些都属于appearance范畴,都会引起invalidate操作。所以当我们改变了View的appearance,需要更新界面显示,就可以直接调用invalidate方法。 View(非容器类)调用invalidate方法只会重绘自身,ViewGroup调用则会重绘整个View树。

区别:调用invalidate方法只会执行onDraw方法;调用requestLayout方法只会执行onMeasure方法和onLayout方法,并不会执行onDraw方法。 所以当我们进行View更新时,若仅View的显示内容发生改变且新显示内容不影响View的大小、位置,则只需调用invalidate方法;若View宽高、位置发生改变且显示内容不变,只需调用requestLayout方法;若两者均发生改变,则需调用两者,按照View的绘制流程,推荐先调用requestLayout方法再调用invalidate方法。

ViewRoot:连接器,对应于ViewRootImpl

DecorView:Android 视图树的根节点;同时也是 FrameLayout 的子类

measure:测量。系统会先根据xml布局文件和代码中对控件属性的设置,来获取或者计算出每个View和ViewGrop的尺寸,并将这些尺寸保存下来。

layout:布局。根据测量出的结果以及对应的参数,来确定每一个控件应该显示的位置。

draw:绘制。确定好位置后,就将这些控件绘制到屏幕上

Activity 与 Window、PhoneWindow、DecorView 之间的关系

Activity 创建时通过 attach()初始化了一个 Window 也就是PhoneWindow,一个 PhoneWindow 持有一个DecorView 的实例,DecorView 本身是一个 FrameLayout,继承于 View,Activty 通过setContentView 将xml 布局控件不断 addView()添加到 View 中,最终显示到 Window 于我们交互;

  Activity:是Android 四大组件之一, 是存放View对象的容器,也是我们界面的载体,可以用来展示一个界面。它有一个SetContentView()方法 ,可以将我们定义的布局设置到界面上。

  1. SurfaceView

View通过刷新来重绘视图,Android系统通过发出VSYNC信号来进行屏幕的重绘,刷新的时间间隔为16ms

在一些需要频繁刷新,执行很多逻辑操作的时候,超过了16ms,就会导致卡顿

SurfaceView继承之View,但拥有独立的绘制表面,即它不与其宿主窗口共享同一个绘图表面,可以单独在一个线程进行绘制,并不会占用主线程的资源。这样,绘制就会比较高效,游戏,视频播放,还有最近热门的直播,都可以用SurfaceView

SurfaceView有两个子类GLSurfaceView和VideoView

SurfaceView和View的区别:

View主要适用于主动更新的情况下,而SurfaceView主要适用于被动更新,例如频繁地刷新

View在主线程中对画面进行刷新,而SurfaceView通常会通过一个子线程来进行页面的刷新

View在绘图时没有使用双缓冲机制,而SufaceView在底层实现机制中就已经实现了双缓冲机

简述TCP,UDP,Socket

TCP是经过3次握手,4次挥手完成一串数据的传送

UDP是无连接的,知道IP地址和端口号,向其发送数据即可,不管数据是否发送成功

Socket是一种不同计算机,实时连接,比如说传送文件,即时通讯

  1. 数组和链表的区别

二者都属于一种数据结构

从逻辑结构来看

1. 数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费;数组可以根据下标直接存取。

2. 链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。(数组中插入、删除数据项时,需要移动其它数据项,非常繁琐)链表必须根据next指针找到下一个元素

从内存存储来看

1. (静态)数组从栈中分配空间, 对于程序员方便快速,但是自由度小

2. 链表从堆中分配空间, 自由度大但是申请管理比较麻烦

从上面的比较可以看出,如果需要快速访问数据,很少或不插入和删除元素,就应该用数组;相反, 如果需要经常插入和删除元素就需要用链表数据结构了。

  1. 反射的原理

在运行状态下,通过class文件对象(Class的对象),去使用构造方法,成员变量,成员方法。

  1. 常见的排序算法

1.选择排序:遍历数组  然后每次遍历到一个元素之后  继续遍历该元素之后的所有元素 然后找到最小的元素 和其换位置

2.插入排序(冒泡);

3.快速排序:在快排中要设定分水岭 就是随机一个元素  然后比他小的放置到其左边 比其大的在有边 循环往复

4.合并排序:合并排序就是将数组拆分,然后比较多的再用插入排序的方式实现排序;用于处理元素或者较为复杂的情况;

5.堆排序

  1. JVM、DVM、ART的区别

JVM是整个java平台的基石,是java语言编译代码的运行平台。JVM结构包含运行时数据区,执行引擎,本地方法库,本地方法接口组成。Java文件被编译后生成Class文件,这种二进制格式文件不依赖于特定硬件和操作系统。每一个Class文件都对应着唯一的类或者接口定义信息。无论任何语言只要能编译成Class文件,都能被Java虚拟机识别并执行。

一个Java文件被加载到Java虚拟机内存中到从内存中卸载的过程被称为类的生命周期。类的生命周期包括:加载、链接、初始化、使用、卸载,其中链接包括了三个阶段:验证,准备,解析。因此类的生命周期包括了7个阶段。

android中的每一个app都是运行在自己VM实例之中(沙盒)。每一个VM实例在linux中又是一个单独的进程,所以可以认为是同一个概念。运行在自己的DVM进程之中,不同的app不会相互干扰,且不会因为一个DVM的崩溃导致所有的app进程都崩溃。

1.1 什么是Dalvik虚拟机?

java的运行需要JVM(后面有大量篇幅介绍),同样android中使用了java语言,也需要一个VM。针对手机处理器和内存等硬件资源不足而推出来的一款VM,为android运行提供环境。

2.与JVM的区别

2.1.基于架构的不同。JVM是基于栈的架构,而DVM是基于寄存器架构。

2.1.1 为什么JVM设计成基于栈架构, DVM是基于寄存器架构?

优点:

a.基于栈架构的指令集更容易生成(http://rednaxelafx.iteye.com/blog/492667);

b.节省资源。其零地址指令比其他指令更加紧凑

c.可移植性。考虑到JVM使用的场合大多是pc和服务器,这类机器的处理器中通用寄存器的数量不尽相同,如果使用基于寄存器其功能提升不多;而栈架构可以自由分配实际的寄存器,这样的可移植性比较高,也符合java的设计理念(一次编写,处处运行)。

DVM为什么基于寄存器:

优点:

a.android手机制造商的处理器绝大部分都是基于寄存器架构的。

b.栈架构中有更多的指令分派和访问内存,这些比较耗时。所有相对来认为dvm的执行效率更高一些。

c.DVM就是为android运行而设计的,无需考虑其他平台的通用。

2.2.jvm运行的是字节码文件,而dvm运行自己定义的dex文件格式

dvm和jvm区别简单来说就是

区别一:dvm执行的是.dex格式文件 jvm执行的是.class文件 android程序编译完之后生产.class文件,然后,dex工具会把.class文件处理成.dex文件,然后把资源文件和.dex文件等打包成.apk文件。apk就是android package的意思。 jvm执行的是.class文件。

区别二:dvm是基于寄存器的虚拟机 而jvm执行是基于虚拟栈的虚拟机。寄存器存取速度比栈快的多,dvm可以根据硬件实现最大的优化,比较适合移动设备。

区别三:.class文件存在很多的冗余信息,dex工具会去除冗余信息,并把所有的.class文件整合到.dex文件中。减少了I/O操作,提高了类的查找速度

ART虚拟机

ART(Android Runtime)虚拟机是Android 4.4发布的,用来替换Dalvik虚拟机。Android 4.4默认还是DVM,但是可以在开发者选项中切换成ART。在Android 5.0之后默认采用了ART,从此DVM退出了历史舞台。

ART和dvm的区别:
1)前文了解到,DVM中的应用每次运行时,字节码都需要通过JIT编译器编译为机器码,这样会使应用程序的运行效率降低。而在ART中,系统安装应用程序时会进行一次AOT(ahead of time compilation),将字节码预编译成机器码并存储在本地,这样应用程序每次运行时就不需要执行编译了,会大大增加效率。但是AOT不是完美的,它的缺点主要有两个:第一个是AOT会使安装应用的时间变长,尤其是复杂的应用。第二个是字节码预先编译成机器码,机器码需要存储空间会多一些。为了解决这两个问题,Android 7.0版本中的ART加入了JIT即时编译器,作为AOT的一个补充。应用程序安装时并不会将字节码全部编译成机器码,而是在系统运行中将热点代码编译成机器码,从而缩短应用程序安装时间,并且节省内存。

2)DVM是为32位CPU设计的,而ART是支持64位并且兼容32位CPU,这也是DVM被淘汰的主要原因之一。

3)ART对垃圾回收机制进行了改进,比如更频繁的执行并行垃圾收集,将GC暂停由2次减少为1次等等。

  1. ART运行时堆空间划分和DVM不同

  1. 线程池的原理

在android开发中经常会使用多线程异步来处理相关任务,而如果用传统的newThread来创建一个子线程进行处理,会造成一些严重的问题:

1:在任务众多的情况下,系统要为每一个任务创建一个线程,而任务执行完毕后会销毁每一个线程,所以会造成线程频繁地创建与销毁。

2:多个线程频繁地创建会占用大量的资源,并且在资源竞争的时候就容易出现问题,同时这么多的线程缺乏一个统一的管理,容易造成界面的卡顿。

3:多个线程频繁地销毁,会频繁地调用GC机制,这会使性能降低,又非常耗时。

线程池使用的好处:

1:对多个线程进行统一地管理,避免资源竞争中出现的问题。

2:(重点):对线程进行复用,线程在执行完任务后不会立刻销毁,而会等待另外的任务,这样就不会频繁地创建、销毁线程和调用GC。

3:JAVA提供了一套完整的ExecutorService线程池创建的api,可创建多种功能不一的线程池,使用起来很方便。

1:ThreadPoolExecutor 创建基本线程池

2:FixedThreadPool (可重用固定线程数):该方法返回一个固定线程数量的线程池,该线程池中的线程数量始终不变,即不会再创建新的线程,也不会销毁已经创建好的线程,自始至终都是那几个固定的线程在工作,所以该线程池可以控制线程的最大并发数。

3:CachedThreadPool (按需创建):该方法返回一个可以根据实际情况调整线程池中线程数量的线程池。即该线程池中的线程数量不确定,是根据实际情况动态调整的。

4:SingleThreadPool(单个核线的fixed):该方法返回一个只用一个线程的线程池,即每次只执行一个线程任务,多余的任务会保存到一个任务队列中,等待这个线程空闲,然后再按照FIFO方式顺序执行任务队列中的任务。

5:ScheduledThreadPool(定时延时执行):该方法返回一个可以控制线程池内线程定时或周期性执行某任务的线程池。

23、activity启动流程

当我们点击屏幕上的应用icon时,Laucher发送启动应用的请求给AMS,因为Launcher是在Launcher进程,而AMS是在SystemServer进程。因此,实现这个请求。本质上,是在实现一个跨进程通信的功能。在安卓领域实现跨进程通信的技术有多种,像是Broadcast、ContentProvinder以及AIDL等。在系统领域,不敢说全部,但大部分的进程间通信技术都采用AIDL技术。Launcher发送请求成功后,启动应用的重担,就落到AMS头上了。

2. 从AMS到ApplicationThread。

收到Launcher创建应用的请求后,AMS会做以下几件事:

检查调用者,是否有资格来创建应用。 比如,调用者的进程是否被隔离、是否有权限,是否有配置启动应用的理由说明。

封装创建应用所需的相关信息。 比如,包名,apk的描述信息和启动的flag等。

通过上面所封装的信息,来判断应用所在的任务栈、进程,是否存在,如果不存在,就创建新的。

创建应用进程。 AMS并不直接创建应用进程,而是交给Zygote进程来创建。Zygote进程通过fork自身进程,来创建新的应用进程,这样可以让新的应用进程继承自己的相关权限。

将启动Activity的信息,转发给ApplicationThread。 这又是一个跨进程的操作,从SystemServer进程到应用进程。

3. 从ApplicationThread到Activity。

ApplicationThread收到消息后,再通过H类(Handler,指向UI线程),将消息发送到UI线程,再在UI线程启动应用,接着调用onCreated方法,实现应用的启动。

24、Handler原理

Handler:发送和处理消息,是android线程之间的消息机制,主要的作用是将一个任务切换到指定的线程中去执行,(准确的说是切换到构成handler的looper所在的线程中去出处理)android系统中的一个例子就是主线程中的所有操作都是通过主线程中的handler去处理的。

Messagequeue:messagequeue 是 一 个 消 息 队 列 , 它是采用单链表的数据结构来存储消息的,因为单链表在插入删除上 的效率非常高。

Looper:负责消息循环,循环取出 MessageQueue 里面的 Message,并交给相应的 Handler 进行处理。

Looper取消息的过程是这样的:

如果队列中有消息:

1、判断队头消息的执行时间是否大于当前时间,如果大于,就调用nativePollOnce阻塞一段时间(队头消息的执行时间-当前时间)然后取出队头消息进行执行。

2、否则就立即取出队头消息进行执行。

3、如果队列中没有消息,就一直阻塞,直到下一个消息来到,才唤醒取消息的线程继续上述循环。

Android中为什么主线程不会因为Looper.loop()里的死循环导致(anr)卡死?

1、首先Anr和死循环不是一个概念。

2、另外从looper取消息的过程来看,取消息的线程大部分时候处于阻塞状态,主线程会释放CPU资源进入休眠状态,直到下个消息到达时调用nativewake,通过往pipe管道写端写入数据来唤醒主线程工作。其他线程以及进程仍可利用cpu给主线程发消息,主线程被唤醒后,处理消息。

为什么主线程中会采用死循环呢?

线程是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了。而对于主线程,我们是绝不希望会被运行一段时间就退出,所以采用死循环保证它不会被退出。

而anr原因是,主线程中messagequeue中一个message的处理时间过长,导致接下来的消息无法处理,比如说一个消息的处理时间超过了5秒,导致用户的输入无法响应,才会出现anr。

Handler 内存泄漏原因

静态内部类,或者匿名内部类。使得Handler默认持有外部类的引用。在Activity销毁时,由于Handler可能有未执行完/正在执行的Message。导致Handler持有Activity的引用。进而导致GC无法回收Activity。

  1. 事件分发机制

我们对屏幕的点击,滑动,抬起等一系的动作都是由一个一个MotionEvent对象组成的。根据不同动作,主要有以下三种事件类型:

1.ACTION_DOWN:手指刚接触屏幕,按下去的那一瞬间产生该事件

2.ACTION_MOVE:手指在屏幕上移动时候产生该事件

3.ACTION_UP:手指从屏幕上松开的瞬间产生该事件

从ACTION_DOWN开始到ACTION_UP结束我们称为一个事件序列

点击事件发生后,事件先传到Activity、再传到ViewGroup、最终再传到 View

Activity:dispatchTouchEvent() -> 是否消耗此次事件 :调用 TouchEvent()结束事件。:TouchEvent -> viewGroup的dispatchTouchEvent -> onInterceptTouchEvent 是否拦截-> 是:TouchEvent 自己处理该事件。 :调用子view的dispatchTouchEvent -> onTouch -> true:dispatchTouchEvent 返回true 结束不调用onclick 。False:dispatchTouchEvent返回onTouchEvent -> onclick

  1. Binder原理

Binder是Android系统进程间通信(IPC)方式之一

为了保护进程空间不被别的进程破坏或者干扰,Linux的进程是相互独立的(进程隔离),而且一个进程空间还分为用户空间和内核(Kernel)空间,相当于把Kernel和上层的应用程序抽像的隔离开。这里有两个隔离,一个进程间是相互隔离的,二是进程内有用户和内核的隔离。

Binder框架定义了四个角色:Server,Client,ServiceManager以及Binder驱动。其中Server,Client,ServiceManager运行于用户空间,驱动运行于内核空间。这四个角色的关系类似:Server是服务器,Client是客户终端,ServiceManager是服务注册中心(类似房屋中介)。

要进行Client-Server之间的通信,从面向对象的角度,在Server内部有一个Binder实体,在Client内部有一个Binder对象的引用,其实就是Binder的一个代理,Client通过对Binder引用间接的操作Server内部的Binder实体,这样就实现了通信。

每个Server如果要提供服务就必须要去ServiceManager那里去注册,ServiceManager在一张查找表中记录一个Server的名字,对应着Server的引用。Client想要获得Server,必须通过名字到ServiceManager取找Server的引用,获得这个Server的binder引用,通过这个binder引用去和Server通信。

一次完整的 Binder IPC 通信过程通常是这样:

首先 Binder 驱动在内核空间创建一个数据接收缓存区; 接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系; 发送方进程通过系统调用 copyfromuser() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。

性能方面

在移动设备上(性能受限制的设备,比如要省电),广泛地使用跨进程通信对通信机制的性能有严格的要求,Binder相对出传统的Socket方式,更加高效。Binder数据拷贝只需要一次,而管道、消息队列、Socket都需要2次,共享内存方式一次内存拷贝都不需要,但实现方式又比较复杂。

安全方面

首先传统IPC的接收方无法获得对方进程可靠的UID和PID(用户ID进程ID),从而无法鉴别对方身份。Android为每个安装好的应用程序分配了自己的UID,故进程的UID是鉴别进程身份的重要标志。其实Binder机制主要是在本机内进行跨进程通信,而socket等IPC方式主要是用于跨网络的通信手段,因而socket不能限制了入口和出口,所以从使用场景来看,socket给用户提供的使用范围更广,而binder由于为了保证它的数据安全性,必须限制于android系统的机子中,而且在内核中就已经对UID进行了验证。

  1. Socket

Socket API 不属于 TCP/IP协议簇,只是操作系统提供的一个是一个对 TCP / IP协议进行封装 的编程调用接口,工作在应用层与传输层之间:

一个 Socket 包含两个必要组成部分:

地址:IP 和端口号组成一队套接字

协议:Socket 所用的是传输层协议,目前有 TCP、UDP、raw IP

基于 TCP协议,采用 流的方式 提供可靠的字节流服务。TCP 协议有以下特点:

  • 面向连接:指的是要使用TCP传输数据,必须先建立TCP连接,传输完成后释放连接,就像打电话一样必须先拨号建立一条连接,打完后挂机释放连接。
  • 全双工通信:即一旦建立了TCP连接,通信双方可以在任何时候都能发送数据。
  • 可靠的:指的是通过TCP连接传送的数据,无差错,不丢失,不重复,并且按序到达。

面向字节流:流,指的是流入到进程或从进程流出的字符序列。简单来说,虽然有时候要传输的数据流太大,TCP报文长度有限制,不能一次传输完,要把它分为好几个数据块,但是由于可靠性保证,接收方可以按顺序接收数据块然后重新组成分块之前的数据流,所以TCP看起来就像直接互相传输字节流一样,面向字节流。

Http连接是 “请求-响应” 的方式,不仅在请求时需要先建立连接,而且需要客户端向服务器发出请求后,服务器端才能回复数据。

  1. Gc回收机制

1、引用计数算法

“引用计数算法”其实就是每一个对象都维护一个内存字段来统计它被引用的数量,当对象被引用的时候它的引用计数器就加1,引用失效之后就减1,当为0的时候,表示为“无用的对象”,则标记为“垃圾”待下一步的回收。这种标记方式的优点在于它只维护局部的信息,不用扫描全局的对象图就可以释放无用的对象,其效率比较高。

2、根搜索算法

“根搜索算法”是大多数程序语言中使用的算法,其思想就是从一个名为“GC Roots”的对象作为根出发点,通过引用关系遍历对象图,搜索过过的路径称为“引用链”,当一个对象与GC Roots之间没有任何的引用链,即从GC Roots到该对象不可到达,就判定该对象死亡。但是我们必须注意的是这一过程的本质是找出所有存活的对象,把其他的对象认定为“无用”,而不是找出死的对象回收它们占用的空间。

  1. App启动优化

冷启动:当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,然后再根据启动的参数,启动对应的进程组件,这个启动方式就是冷启动

热启动:当启动应用时,后台已有该应用的进程(例:按back键、home键,应用虽然会退出,但是该应用的进程是依然会保留在后台,可进入任务列表查看),所以在已有进程的情况下,这种启动会从已有的进程中来启动对应的进程组件,这个方式叫热启动

启动窗口优化

不要禁止系统默认的启动窗口:即不要在主题里面设置 android:windowDisablePreview 为 true

自己定制启动窗口的内容,比如将启动页主题背景设置成闪屏页图片,或者尽量使闪屏页面的主题和主页一致。可以参考知乎、抖音的做法

合并闪屏和主页面的 Activity :微信的做法,不过由于微信设置了 android:windowDisablePreview , 且他在各个厂商的白名单里面,一般不会被杀,冷启动的机会比较少。不过也是一个可以思考的地方

线程优化

线程优化主要是减少 CPU 调度带来的波动,让启动时间更稳定。如果启动过程中有太多的线程一起启动,会给 CPU 带来非常大的压力,尤其是比较低端的机器。过多的线程同时跑会让主线程的 Sleep 和 Runnable 状态变多, 增加了应用的启动速度,优化的过程中要注意:

控制线程数量 – 线程池

检查线程间的锁 ,防止依赖等待

使用合理的启动架构

微信内部使用的 mmkernel

阿里 Alpha

系统调度优化

启动过程中减少系统调用,避免与 AMS、WMS 竞争锁。启动过程中本身 AMS 和 WMS 的工作就很多,且 AMS 和 WMS 很多操作都是带锁的,如果此时 App 再有过多的 Binder 调用与 AMS、WMS 通信,SystemServer 就会出现大量的锁等待,阻塞关键操作

启动过程中不要启动子进程,如果好几个进程同时启动,系统负担则会加倍,SystemServer 也会更繁忙

启动过程中除了 Activity 之外的组件启动要谨慎,因为四大组件的启动都是在主线程的,如果组件启动慢,占用了 Message 通道,也会影响应用的启动速度

Application 和主 Activity 的 onCreate 中异步初始化某些代码

GC 优化

启动过程中减少 GC 的次数

避免进行大量的字符串操作,特别是序列化和反序列化

频繁创建的对象需要考虑复用

IO 优化

  1. 性能优化

内存优化

由于Android应用的沙箱机制,每个应用所分配的内存大小是有限度的,内存太低就会触发LMK(Low Memory Killer)机制,进而会出现闪退现象。

常见的内存问题如下

内存泄露:使用LeakCanary工具。

构造单例的时候尽量别用Activity的引用;如果一个对象已经没有用处了,但是单例还持有它的引用,那么在整个应用程序的生命周期它都不能正常被回收,从而导致内存泄露。
静态引用时注意应用对象的置空或者少用静态引用;
使用静态内部类+软引用代替非静态内部类;
及时取消广播或者观察者注册;
耗时任务、属性动画在Activity销毁时记得cancel;
文件流、Cursor等资源及时关闭;
Activity销毁时WebView的移除和销毁

图片Bitmap相关

代码质量 & 数量

布局优化:

布局复用,使用标签重用layout;

提高显示速度,使用延迟View加载;

减少层级,使用标签替换父级布局;

注意使用wrap_content,会增加measure计算成本;

删除控件中无用属性;

启动优化:

代码混淆。使用proGuard 代码混淆器工具,它包括压缩、优化、混淆等功能。

资源优化。比如使用 Android Lint 删除冗余资源,资源文件最少化等。

图片优化。比如利用 AAPT 工具对 PNG 格式的图片做压缩处理,降低图片色彩位数等。

避免重复功能的库,使用 WebP图片格式等。

插件化,比如功能模块放在服务器上,按需下载,可以减少安装包大小

  1. Retrofit

是一个 RESTful 的 HTTP 网络请求框架的封装,但网络请求不是Retrofit来完成的,它只是封装了请求参数、Header、Url、返回结果处理等信息,而请求是由OkHttp3来完成的。

·  App应用程序通过Retrofit请求网络,实际上是使用Retrofit接口层封装请求参数,之后由OkHttp完成后续的请求操作。

·  在服务端返回数据之后,OkHttp将原始的结果交给Retrofit,Retrofit根据用户的需求对结果进行解析。

·  完成数据的转化(converterFactory),适配(callAdapterFactory),通过设计模式进行各种扩展。

callAdapterFactory

通过calladapter将原始Call进行封装,找到对应的执行器。如rxjavaCallFactory对应的Observable,转换形式Call --> Observable

converterFactory

数据解析Converter,将response通过converterFactory转换成对应的数据形式,GsonConverterFactory,FastJsonConverterFactory。

Retrofit网络通信八步骤

创建Retrofit实例

定义网络请求接口,并为接口中的方法添加注解

通过动态代理生成网络请求对象

通过网络请求适配器将网络请求对象进行平台适配

通过网络请求执行器,发送网络请求(call)

通过数据解析器解析数据

通过回调执行器,切换线程

用户在主线程处理返回结果

  1. 多线程

Android提供了四种常用的操作多线程的方式,分别是:
1. Handler+Thread:Android主线程包含一个消息队列(MessageQueue),该消息队列里面可以存入一系列的Message或Runnable对象。通过一个Handler你可以往这个消息队列发送Message或者Runnable对象,并且处理这些对象。每次你新创建一个Handle对象,它会绑定于创建它的线程(也就是UI线程)以及该线程的消息队列,从这时起,这个handler就会开始把Message或Runnable对象传递到消息队列中,并在它们出队列的时候执行它们。




2. AsyncTask:AsyncTask是android提供的轻量级的异步类,可以直接继承AsyncTask,在类中实现异步操作,并提供接口反馈当前异步执行的程度(可以通过接口实现UI进度更新),最后反馈执行的结果给UI主线程。


3. ThreadPoolExecutor:ThreadPoolExecutor提供了一组线程池,可以管理多个线程并行执行。这样一方面减少了每个并行任务独自建立线程的开销,另一方面可以管理多个并发线程的公共资源,从而提高了多线程的效率。所以ThreadPoolExecutor比较适合一组任务的执行。Executors利用工厂模式对ThreadPoolExecutor进行了封装,使用起来更加方便。


4. IntentService:IntentService继承自Service,是一个经过包装的轻量级的Service,用来接收并处理通过Intent传递的异步请求。客户端通过调用startService(Intent)启动一个IntentService,利用一个work线程依次处理顺序过来的请求,处理完成后自动结束Service。

  1. Jetpack

2018年谷歌I/O 发布了一系列辅助Android开发者的实用工具,合称Jetpack,以帮助开发者构建出色的 Android 应用。

基础组件

AppCompat:使得支持较低的 Android 版本。从以前继承 Activity 到现在继承 AppCompatActivity 就是属于这一部分

Android KTX:Kotlin 的扩展支持库

Multidex:多 dex 文件支持

Test:测试支持库

2. 架构组件

Data Binding:MVVM 的一种实践

Lifecycles:管理你的 Activity 和 Fragment 生命周期

LiveData:通过观察者模式感知数据变化,类比 RxJava

Navigation:处理 Fragment 导航相关逻辑

Paging:分页数据加载方案

Room:官方 ORM 库

ViewModel:通过数据驱动 V 视图发生改变

WorkManager:管理后台任务

34、Rxjava

Rxjava是一个用来实现异步的、基于事件的第三方库,RxJava 的异步实现,是通过一种扩展的观察者模式来实现的。

RxJava 有四个基本概念:Observable (可观察者,即被观察者)、 Observer (观察者)、 subscribe (订阅)、事件。Observable 和 Observer 通过 subscribe() 方法实现订阅关系,从而 Observable 可以在需要的时候发出事件来通知 Observer。

onCompleted(): 事件队列完结。RxJava 不仅把每个事件单独处理,还会把它们看做一个队列。RxJava 规定,当不会再有新的 onNext() 发出时,需要触发

onCompleted() 方法作为标志。

onError(): 事件队列异常。在事件处理过程中出异常时,onError() 会被触发,同时队列自动终止,不允许再有事件发出。

Scheduler (线程切换)

在不指定线程的情况下, RxJava 遵循的是线程不变的原则,即:在哪个线程调用 subscribe(),就在哪个线程生产事件;在哪个线程生产事件,就在哪个线程消费事件。如果需要切换线程,就需要用到 Scheduler (调度器)。

RxJava 已经内置了几个 Scheduler :

Schedulers.newThread(): 总是启用新线程,并在新线程执行操作。

Schedulers.io(): I/O 操作(读写文件、读写数据库、网络信息交互等)所使用的 Scheduler。行为模式和 newThread() 差不多,区别在于 io() 的内部实现是是用一个无数量上限的线程池,可以重用空闲的线程,因此多数情况下 io() 比 newThread() 更有效率。不要把计算工作放在 io() 中,可以避免创建不必要的线程。

Schedulers.computation(): 计算所使用的 Scheduler。这个计算指的是 CPU 密集型计算,即不会被 I/O 等操作限制性能的操作,例如图形的计算。这个 Scheduler 使用的固定的线程池,大小为 CPU 核数。不要把 I/O 操作放在 computation() 中,否则 I/O 操作的等待时间会浪费 CPU。

AndroidSchedulers.mainThread(),它指定的操作将在 Android 主线程运行

有了这几个 Scheduler ,就可以使用 subscribeOn() 和 observeOn() 两个方法来对线程进行控制了。

observeOn(): 指定 Subscriber 所运行在的线程。或者叫做事件消费的线程。

subscribeOn(Schedulers.io()) //在io执行上述操作

.observeOn(AndroidSchedulers.mainThread())//在UI线程执行下面操作


创建操作符 变换操作符

35、协程

它是基于线程封装的一套更上层工具库,我们可以使用kotlin协程库提供的api方便的灵活的指定协程中代码执行的线程、切换线程,但是不需要接触线程Thread类。协程最重要的是通过非阻塞挂起和恢复实现了异步代码的同步编写方式,把原本运行在不同线程的代码写在一个代码块{}里,看起来就像是同步代码。

你可能感兴趣的:(android)