最近复习面试题,顺便整理简单记录一番。
含答案所以内容较多,建议收藏慢慢复习。
自己总结的一些点,欢迎各位大佬补充。
文中附有转载博客链接,如侵权联系删。
一、Android基础:
1、四大组件:
①Activity:
②Service:
③BroadcastReceiver:
④ContentProvider:
2、Activity生命周期:
activity的四种状态:
运行状态(前台状态):
当activity处于栈顶,在屏幕最前方,onResume
暂停状态(可视状态):
当activity失去焦点但仍然可见,如栈顶的activity是透明的或者栈顶的activity不是全屏的,onPause
停止状态(后台状态):
当activity被其他activity完全遮挡,此时此activity对用户不可见,
当系统内存需要被用在其他地方的时候,stopped的Activity将被杀掉;
销毁状态(空进程状态):
当activity由于人为或者系统原因(如内存吃紧)被销毁,onDestroy
生命周期:
onCreate、onStart、onResume、onPause、onStop、onDestroy、onRestart
activity生命周期方法回调情况:
一个完整的正常的activity生命周期如下:
onCreate->onStart->onResume->onPause->onStop->onDestroy
当A被启动时,执行的回调有:
A:onCreate->A:onStart->A:onResume
当用户点击A中按钮来到B时,假设B全部遮挡住了A,将依次执行:
A:onPause->B:onCreate->B:onStart->B:onResume->A:onStop
此时如果点击Back键返回A时,将依次执行:
B:onPause->A:onRestart->A:onStart->A:onResume->B:onStop->B:onDestroy
当栈中只有A时:
此时如果按下Back键,系统返回到桌面,并依次执行:
A:onPause->A:onStop->A:onDestroy。
此时如果按下Home键(非长按),系统返回到桌面,并依次执行:
A:onPause->A:onStop。由此可见,Back键和Home键主要区别在于是否会执行onDestroy
3、Activity四种启动模式:
①standard:
系统默认启动模式,每次启动都会创建一个新的activity实例;
任务栈中允许有多个相同的activity实例,相同的activity实例允许叠加;
②singleTop:
启动的时候,如果任务栈中该activity实例不在栈顶,则创建新的activity实例;
在栈顶则不创建,而是调用activity的onNewIntent()方法;
任务栈中允许有多个相同的activity实例,但是不允许相同的activity实例叠加,避免重复创建activity实例;
适用场景:
适合任务栈中可以有多个实例,但栈顶只有一个实例的情况,比如系统浏览器的书签页面;
③singleTask:
启动的时候,任务栈中没有则创建新的activity实例,存在则移除任务栈中该activity实例上面的activity实例;
任务栈中不允许有多个相同的activity实例,节约内存;
适用场景:
适合作为程序的入口页面,比如系统浏览器的主页面
④singleInstance:
单独的栈,该单独的栈中只有一个activity实例,
整个手机系统也只会存在一个activity实例;
适用场景:
适合需要与程序分离开的页面,系统的闹钟提醒、来电显示、紧急呼叫等页面
4、Activity的任务栈(Task):
针对activity的后进先出的任务栈列表,用来维护用户的界面体验;
5、Context原理:
Context在加载资源、启动Activity、获取系统服务、创建View等操作都要参与。
Context类本身是一个纯abstract类,他有两个具体的实现子类:ContextImpl和ContextWrapper;
ContextWrapper类,只是一个包装而已,ContextWrapper构造函数中必须包含一个真正的Context引用,
同时ContextWrapper提供了attachBaseContext()用于给ContextWrapper对象中指定真正的Context对象,
调用ContextWrapper的方法都会被转向其所包含的真正的Context对象;
ContextWrapper有三个子类:Application、Service和ContextThemeWrapper;
而Activity又是ContextThemeWrapper的子类;
ContextImpl类则真正实现了Context中的所有函数,应用程序中所调用的各种Context类的方法,其实现均来于该类。
Context的两个子类分工明确,其中ContextImpl是Context的具体实现类,ContextWrapper是Context的包装类。
6、activity启动方式:
启动方式:
①显示启动:
显示启动就是在初始化Intent对象的时候直接引用需要启动的Activity的字节码,
显示引用的好处就是可以直接告诉Intent对象启动的Activity对象,
但是显示引用不能启动其他进程的Activity对象,因为无法获取其他进程的Activity对象的字节码;
②隐式启动:
可以通过配置IntentFilter启动其他进程的Activity对象;
适用场景:
在应用内,我们一般都是使用显示启动的方式启动Activity;
而如果需要启动其他应用的Activity时,一般使用隐式启动的方式;
7、android framwork进程启动流程:
init进程 -> Zygote进程 -> SystemServer进程 -> 各种应用进程;
8、Launcher启动流程:
技术链接:
http://blog.csdn.net/qq_23547831/article/details/51112031
9、SystemServer进程启动流程:
技术链接:
http://blog.csdn.net/qq_23547831/article/details/51105171
SystemServer进程作用:
SystemServer进程主要的作用是启动各种系统服务,比如ActivityManagerService,PackageManagerService,WindowManagerService等服务;
当我们的应用需要使用各种系统服务的时候其实也是通过与SystemServer进程通讯,获取各种服务对象的句柄的进而执行相应的操作;
SystemServer进程启动流程:
①初始化一些系统变量,加载类库,创建Context对象,创建SystemServiceManager对象;
②启动boot服务:
启动PowerManagerService,PowerManagerService主要用于计算系统中和Power相关的计算,然后决策系统应该如何反应;
启动LightsService,LightsService主要是手机中关于闪光灯,LED等相关的服务;
启动DisplayManagerService,DisplayManagerService主要是手机显示方面的服务;
启动PackageManagerService,该服务也是android系统中一个比较重要的服务,包括多apk文件的安装,解析,删除,卸载等等操作;
启动UserManagerService和SensorService;
③启动core服务:
启动BatteryService(电池相关服务),UsageStatsService,WebViewUpdateService服务等;
④启动other服务:
启动系统中其他的服务;
总结:
①SystemServer进程是android中一个很重要的进程,由Zygote进程启动;
②SystemServer进程主要用于启动系统中的服务;
③SertemServer进程在尝试启动服务之前会首先尝试与Zygote建立socket通讯,
只有通讯成功之后才会开始尝试启动服务;
④创建的系统服务过程中主要通过SystemServiceManager对象来管理,
通过调用服务对象的构造方法和onStart方法初始化服务的相关变量;
⑤服务对象都有自己的异步消息对象,并运行在单独的线程中;
10、activity启动过程:
技术链接:
http://blog.csdn.net/qq_23547831/article/details/51224992
概述:
在Actvity启动过程中,其实是ActivityThread进程与AMS进程相互配合启动Activity的过程,
其中ActivityThread进程主要用于执行具体的Activity的启动过程,回调生命周期方法等操作,
ActivityThread进程启动activity实际的操作类就是Intrumentation。
而AMS进程则主要是调用其中的各种服务,将Activity保存在栈中,协调各种系统资源等操作;
activity启动过程:
①通过startActivity方法启动activity,最终会调用Instrumentation的execStartActivity方法,
在该方法中,通过ActivityManagerNative向AMS进程发出启动Activity请求;
②AMS接收请求后,创建了AMS进程的activity对象->ActivityRecord,
解析启动模式,并根据Activity的启动模式执行不同栈的处理,
然后通过IApplicationThread,通知ActivityThread进程pause当前栈顶的Activity;
③ActivityThread进程接收通知后,经过一系列分发,
回调到Instrumentation的callActivityOnPuase方法,最后调用栈顶activity的onPause方法,
接着ActivityThread进程告知AMS进程,栈顶Activity已经执行完成onPause方法;
④AMS进程接收通知后,判断要启动的Activity所需要的应用进程是否已经启动,
若已启动的话,则直接调用realStartAtivityLocked方法执行启动activity,
否则调用startProcessLocked方法启动应用进程;
AMS进程通过socket与Zygote进程通讯,并告知Zygote进程fork出一个新的应用程序进程,
然后通知ActivityThread执行main方法;
⑤当应用进程已经启动,AMS通知ActivityThread进程执行activity的启动;
⑥ActivityThread进程接收到通知后,通过反射机制创建出Activity对象,
然后通过Instrumentation对象,依次执行Activity的onCreate、onStart、onResume生命周期方法;
然后通知AMS进程;
⑥AMS进程接着通知ActivityThread进程stop栈顶的activity;
⑦ActivityThread进程接收到通知后,最终通过Instrumentation执行了activity的onStop方法;
activity启动过程的Binder机制:
①ActivityThread进程向AMS进程传递消息通过ActivityManagerNative;
②AMS进程向ActivityThread进程传递消息通过IApplicationThread;
11、简述AMS(ActivityManagerService)原理:
技术链接:
http://www.jianshu.com/p/194a37755fea
http://blog.csdn.net/qq_23547831/article/details/51224992
AMS作用:
①统一调度各应用程序的activity:
应用程序要启动activity,会首先报告给AMS,
然后由AMS决定该activity是否启动,如果可以,AMS再通知应用程序运行指定的activity;
②内存管理:
activity退出后,并不会立即被杀死,这些activity只有在内存紧张时,才会被自动杀死,
从而在下次启动该activity时能够提高启动速度,而这些是在AMS中完成的。
③进程管理:
AMS向外提供了查询系统正在运行的进程信息API;
AMS调度、管理activity机制:
activity调度基本思路:
各应用进程要启动新的activity或者停止当前的activity,都要先报告给AMS,不能擅自处理。
AMS在内部为所有应用进程都做了记录,当AMS接到启动或停止的报告时,首先更新内部记录,
然后再通知相应的客户进程运行或停止指定的activity。
由于AMS内部有所有的activity记录,也就能够调度这些activity,
并根据activity和系统内存的状态自动杀死后台的activity。
activity启动流程:
①通过startActivity方法启动activity,最终会调用Instrumentation的execStartActivity方法,
在该方法中,通过ActivityManagerNative向AMS进程发出启动Activity请求;
②AMS接收请求后,创建了AMS进程的activity对象->ActivityRecord,
解析启动模式,并根据Activity的启动模式执行不同栈的处理,初始化了WindowManager服务,
然后通过IApplicationThread,通知ActivityThread进程pause当前栈顶的Activity;
③ActivityThread进程接收通知后,经过一系列分发,
回调到Instrumentation的callActivityOnPuase方法,最后调用栈顶activity的onPause方法,
接着ActivityThread进程告知AMS进程,栈顶Activity已经执行完成onPause方法;
④AMS进程接收通知后,判断要启动的Activity所需要的应用进程是否已经启动,
若已启动的话,则直接调用realStartAtivityLocked方法执行启动activity,
否则调用startProcessLocked方法启动应用进程;
AMS进程通过socket与Zygote进程通讯,并告知Zygote进程fork出一个新的应用程序进程,
然后通知ActivityThread执行main方法;
⑤当应用进程已经启动,AMS通知ActivityThread进程执行activity的启动;
⑥ActivityThread进程接收到通知后,通过反射机制创建出Activity对象,
然后通过Instrumentation对象,依次执行Activity的onCreate、onStart、onResume生命周期方法;
然后通知AMS进程;
⑥AMS进程接着通知ActivityThread进程stop栈顶的activity;
⑦ActivityThread进程接收到通知后,最终通过Instrumentation执行了activity的onStop方法;
12、Parcel:
技术链接:
http://www.jianshu.com/p/f5e103674953
概述:
Java层的数据包装器,跨进程通信传递的数据的载体就是Parcel;
Parcel提供了一套机制,可以将序列化之后的数据写入一个共享内存中,
其他进程通过Parcel可以从这块共享内存读出字节流,并反序列化成对象。
Parcel是一个存放读取数据的容器,
系统中的binder进程间通信(IPC)就使用了Parcel类来进行客户端与服务端数据交互,
而且AIDL的数据也是通过Parcel来交互的。
Parcel是内存中的结构的是一块连续的内存,会自动根据需要自动扩展大小。
13、Binder机制:
技术链接:
http://www.jianshu.com/p/bdef9e3178c9
http://www.jianshu.com/p/82cdb9d53ca3
http://www.jianshu.com/p/b4a8be5c6300
概述:
所有在Binder中传输的接口都必须实现IInterface接口;
作用:
实现android系统Zygote进程、SystemServer进程和各种应用进程之间的通信;
Binder为什么性能好?
①通过共享内存来提高性能,0次数据拷贝;
②Binder基于Client-Server通信模式,传输过程只需一次拷贝,
为发送方添加UID/PID身份,既支持实名Binder也支持匿名Binder,安全性高;
Binder机制接口:
①IInterface:
IInterface是Binder中相关接口的基类。定义新接口的时候,你必须从IInterface派生。
IInterface接口提供了类型转化的功能,将服务或者服务代理类转为IBinder类型。
②IBinder:
一个远程对象的基接口,不仅可以在跨进程可以调用,也可以在进程内部调用;
定义了Java层Binder通信的一些规则;提供了transact方法来调用远程服务;
在远程调用的时候,一端用IBinder.transact()发送,另一端用Binder的Binder.onTransact()接受,并且是同步的;
transact()方法发送的是Parcel;
系统为每个进程维护一个进行跨进程调用的线程池;
可以使用pingBinder()方法来检测目标进程是否存在;
建议继承Binder类,而不是直接实现这个接口;
③Binder:
实现了IBinder接口,封装了JNI的实现。Java层Binder服务的基类。存在服务端的Binder对象;
大多数开发人员不会直接使用这个类,而是使用AIDL工具来实现这个接口,使其生成适当的Binder子类;
构造方法中首先执行init()方法,init()是native层的;
④BinderProxy:
实现了IBinder接口,封装了JNI的实现,提供了transaction()方法提供进行远程调用;
Binder机制重要的角色:
①Client:
一般是指Android系统上面的应用程序。它可以请求Server中的服务。
②Server:
指的是SystemServer,向客户端提供服务。
③Binder Driver;
Android系统IPC的核心部分,客户端的服务代理通过它向server发送请求,
服务器也是通过它把处理结果返回给客户端的服务代理对象;
④Proxy:
代理对象,从IInterface类派生,它实现了Binder服务的函数接口;
通过代理对象,应用程序能像使用本地对象一样使用远端实体对象提供服务;
Binder框架:
Binder架构提供了Server、Binder Driver、Client三大模块。
一个Binder服务端实际上就是一个Binder类的对象,该对象一旦创建,内部就会启动一个隐藏线程。
该线程接下来会接收Binder驱动发送的消息,收到消息后,会执行Binder对象中的onTransact()函数,
并按照该函数的参数执行不同的服务代码。因此,要实现一个Binder服务,就必须重载onTransact()方法。
而onTransact()函数的参数来源是客户端调用transact()函数时输入的,
客户端怎么调用transact()函数呢?
任意一个Binder对象被创建时,同时会在Binder驱动中创建一个mRemote对象,该对象的类型也是Binder类。
客户端要访问远程服务时,都是通过mRemoto对象。
客户端获取远程服务对应的mRemote引用后,就可以调用其transact()方法向服务端发送请求,
transact()接收四个参数,
第一个参数是code用于标识客户端想调用服务端的哪个服务;
第二个参数data是客户端要传给远程Binder服务的包裹(Parcel),包裹不是客户端自己创建的,
而是通过Parcel.obtain()申请的,远程服务所需的参数必须放到这个包裹中,这个包裹只能放入特定类型的变量,
比如String、int等原子类型,或者继承Parcel的类。
包裹中添加的内容是有序的,这个顺序必须是客户端和服务端事先约好的,在服务端的onTransact()方法中会按顺序取出参数。
第三个参数reply是服务端返回给客户端的包裹。
最后一个参数flag是执行进程间通信调用的模式,0表示双向,1表示单向;
当调用transact()方法后,客户端线程会进入Binder驱动,Binder驱动会挂起当前线程,并向服务端发送一条消息,
消息中包含客户端传进来的包裹。服务端拿到包裹后,会对包裹进行拆解,然后执行指定的服务函数,
执行完毕后,再把执行结果放入客户端提供的reply包裹中。
然后服务端向Binder驱动发送一个notify消息,从而使得客户端线程从Binder驱动代码区返回客户端代码区。
最后,客户端就可以从reply包裹中解析返回的数据,同样,返回包裹中的数据也必须是有序的。
Binder框架中的aidl:
android的sdk中提供了一个aidl工具,该工具可以把一个aidl文件转换为一个java类,在该java类中,
同时重载了transact()和onTransact()方法,统一存入了包裹和读取包裹的参数。
在该java类中,还有一个内部静态类Stub以及一个私有内部静态Proxy;
Proxy作为客户端访问服务端的代理;
Stub是个抽象类,基于Binder类,并且实现aidl文件中定义的接口;
之所以是抽象类,是因为具体的服务函数必须由具体服务端实现;
同时,该Stub类重载了onTransact()方法,由于transact()方法也是aidl工具定义的,
所以transact()和onTransact()方法中包裹的参数的顺序是一致的。
另外,Stub类还提供了asInterface()函数,用于将服务端的Binder对象转换为客户端所需要的接口对象;
Binder框架在系统中的运用:
系统服务中的Binder对象。
在开发时,通过getSystemService()获取一个系统服务,这些服务的Binder引用就是通过Binder框架传递给客户端的。
ServiceManager是一个单独进程,管理各种系统服务。
其他系统服务启动时,首先把自己的Binder对象传递给ServiceManager。
Binder实体和引用问题:
在Binder的工作机制中,我们是依赖Binder驱动程序去执行数据的调度,发送方依赖Binder打包数据,接收方依赖Binder回传数据。
Client和Server为了通过Binder通信,则Client要拥有自己的自己的Binder实体,以及Server的Binder的应用;
Server有用自己的Binder的实体,以及Client的Binder引用。
从Client向Server发送数据:Client为发送方,拥有Binder实体;Server为接收方,拥有Binder引用;
从Server向Client发送数据:Server为发送方,拥有Binder实体:Client为接收方,拥有Binder引用;
Binder的工作流程:
①Client通过持有的Server的Proxy,调用Server的Proxy的方法,Client处于阻塞状态;
②Server的Proxy将Client的参数打包成Parcel对象,并将该Parcel发送给内核中的BinderDriver;
③BinderDriver将Parcel数据写到缓存,然后通过transact接口将数据传输给Server;
④Server收到BinderDriver中请求的数据,解包Parcel对象,处理并将结果通过onTransact接口返回;
⑤Client接收到结果,结束阻塞状态;
ServiceManager:
主要用来负责管理服务。Android中提供的系统服务都要通过ServiceManager注册自己,
将自己添加进服务管理链表中,为客户端提供服务。而客户端如果要和特定的系统服务端通讯,
就需要向ServiceManager来查询和获得所需要服务;
匿名Binder:
在Android中Binder还可以建立点对点的私有通道,匿名Binder就是这种方式。
在Binder通信中,并不是所有通信的Binder实体都需要注册给ServerManager的,
Server可以通过已建立的实体Binder连接将创建的Binder实体传给Client。
而这个Binder没有向ServerManager注册名字。这样Server和Client通信就有很高的隐私性和安全性;
C/S的请求/响应的过程:
当Client发出向Server的请求时,ServerManager会通过Client发出的请求命令判断由哪个ManagerService进行处理,
ServiceManager会将相应的Service取出,Client获取到Service的引用,
通过这个引用即可向远程Server发送请求数据,Server处理完成之后会给Client一个返回响应;
Client和Server之间如何实现数据共享?
共享内存;
数据的共享是发送方和接收方通过互相获取对方缓冲区里的数据完成的。
这样做的优点是通过建立公共的缓存区,发送方和接收方均可操作这块内存空间的数据,不需要再申请其他内存。
Binder中的线程池:
客户端在使用Binder可以调用服务端的方法,如果我们服务端的方法是一个耗时的操作,
那么对于我们客户端和服务端都存在风险,如果有很多客户端都来调用它的方法,那么是否会造成ANR?
多个客户端调用,是否会有同步问题?如果客户端在UI线程中调用的这个是耗时方法,那么是不是它也会造成ANR?
服务端所有这些被调用方法都是在一个线程池中执行的,不在服务端的UI线程中,因此服务端不会ANR,
但是服务端会有同步问题,因此我们提供的服务端接口方法应该注意同步问题。
14、AIDL:
技术链接:
http://www.jianshu.com/p/375e3873b1f4
http://www.jianshu.com/p/419cc7b95358
概述:
Binder就是Android中最具特色的IPC方式,AIDL其实就是通过Binder实现的,
因为在我们定义好aidl文件后,会自动生成了相关的Binder类;
作用:
通过定义相同包下相同接口实现Android中进程间通信;
AIDL文件支持的数据类型:
①基本数据类型(int,long,char,boolean,float,double,byte,short八种基本类型);
②String和CharSequence;
③List:只支持ArrayList,里面每个元素都必须能够被AIDL支持;
④Map:只支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value;
⑤Parcelable:所有实现了Parcelable接口的对象;
⑥AIDL:所有的AIDL接口本身也可以在AIDL文件中使用;
备注:
AIDL中除了基本数据类型,其他类型的参数必须标上方向:in、out或者inout;
in:纯粹的输入参,这意味着是从客户端到服务器的数据;
out:参数是纯粹的输出,这意味它的数据是通过服务器到客户端的;
inout:参数是输入也是输出,客户端的值在服务器可能会被修改;
AIDL开发流程:
①定义AIDL文件:IReporter.aidl
interface IReporter{
int report(String values, int type);
}
②系统根据AIDL文件,studio会自动帮我们生成一个继承android.os.IInterface接口的IReporter,
所有在Binder中传输的接口都必须实现IInterface接口;
接口定义了我们在AIDL文件中定义的方法,然后还有个内部静态类Stub,私有内部静态Proxy;
Stub继承了android.os.Binder并实现IReporter接口,Stub中构成如下:
asInterface静态方法:
用于将服务端的Binder对象转换为客户端所需要的接口对象,
该过程区分进程,如果进程一样,就返回服务端Stub对象本身,否则呢就返回封装后的Stub.Proxy对象;
onTransact方法:
运行在服务端的Binder线程中的,当客户端发起远程请求后,在底层封装后会交由此方法来处理。
通过code来区分客户端请求的方法,注意一点的是,如果该方法返回false的换,客户端的请求就会失败。
一般可以用来做权限控制;
静态类:Stub.Proxy
Proxy实现了IReporter接口;
Proxy构造函数接收远程的IBinder对象,相当于持有了远程Server的句柄,通过这个句柄就可以调用远程Server的方法;
asBinder()方法,返回远程的IBinder对象;
Proxy实现了IReporter接口的report方法,report方法运行在客户端,当客户端发起远程请求时,
_data会写入参数,然后调用transact方法发起请求,同时挂起当前线程,
然后服务端的onTransact方法就会被调起,直到服务端返回后,当前线程继续执行,并从_reply取出返回值;
AIDL系统使用的场景:
以电话服务作为例子,来简单说明下如何在系统没有提供挂断电话的API的情况下强行挂电话:
首先在相同包名下申明同样的AIDL文件,再通过编译后,就会生成相应的Stub文件。
其次通过反射拿到TELEPHONY_SERVICE的binder。这个binder就是可以操作另一个进程来挂断电话的句柄。
将这个binder作为Proxy的参数,并通过asInterface注入进去,从何获得相应的接口,
最后调用telephony.endCall()即可完成操作。代码如下:
Method method=Class.forName(“android.os.ServiceManager”).getMethod(“getService”, String.class);
IBinder binder=(IBinder)method.invoke(null, newObject[]{TELEPHONY_SERVICE});
ITelephony telephony=ITelephony.Stub.asInterface(binder);
telephony.endCall();
15、JNI、NDK和AIDL:
AIDL:
Android中进程间通信的一种方式;
JNI;
Java Native Interface,Java原生接口。
为了方便Java调用Native代码所封装的一层接口。JNI是Java语言的东西,并不专属于Android;
JNI是一套编程接口,用来实现Java代码与本地的C/C++代码进行交互;
NDK:
Native Development Kit,原生开发工具集。
NDK是Google开发的一套开发和编译工具集,可以生成动态链接库,主要用于Android的JNI开发;
16、JNI:
技术链接:
http://www.jianshu.com/p/ac00d59993aa
作用:
JNI是一套编程接口,用来实现Java代码与本地的C/C++代码进行交互;
加载so库:
通过静态代码块加载:
static{
System.loadLibrary(“main”);
}
java文件中声明native方法:
通过native关键字声明,没有方法体;
publicnativevoidgetData();
声明为native方法生成的c函数中,会包含至少两个参数,第一个参数是JNIEnv对象,通过JNIEnv可以访问jvm内部的各种对象;
第二个是jobject,是调用该函数的对象。
头文件:
jni.h头文件就是为了让C/C++类型和Java原始类型相匹配的头文件定义;
通过javah命令生成头文件:
javah-ooutputpackagename.classname
JNI数据类型映射:
Java Native类型 符号属性 字长
boolean jboolean 无符号 8位
byte jbyte 无符号 8位
char jchar 无符号 16位
short jshort 有符号 16位
int jint 有符号 32位
long jlong 有符号 64位
float jfloat 有符号 32位
double jdouble 有符号 64位:
基本数据类型都是可以直接在Native层直接使用的;
引用类型不能直接在Native层使用,需要根据JNI函数进行类型的转化后,才能使用;
方法和变量ID:
java中定义的方法和变量同样不能直接在Native层使用。当Native层需要调用Java的某个方法时,
需要通过JNI函数获取它的ID,根据ID调用JNI函数获取该方法;变量的获取也是类似;
JNI描述符:
①基本数据类型描述符:
基本的数据类型的描述符,除了boolean和long类型分别是Z和J外,
其他的描述符对应的都是Java类型名的大写首字母。另外,void的描述符为V;
②引用类型描述符:
一般引用类型描述符的规则如下:
L+类描述符+;
如String类型的域描述符为:
Ljava/lang/String;
③类描述符:
类描述符是类的完整名称:包名+类名,java中包名用.分割,jni中改为用/分割;
④方法描述符:
方法描述符需要将所有参数类型的域描述符按照声明顺序放入括号,
然后再加上返回值类型的域描述符,其中没有参数时,不需要括号;
JNIEnv:
JNIEnv是jni.h文件最重要的部分,它是一个指针,一组JNI函数,通过这些函数可以实现Java层和JNI层的交互;
特点:
①JNIEnv是一个指针,指向一组JNI函数,通过这些函数可以实现Java层和JNI层的交互,
就是说通过JNIEnv调用JNI函数可以访问Java虚拟机,操作Java对象;
②所有本地函数都会接收JNIEnv作为第一个参数;
③用作线程局部存储,不能在线程间共享一个JNIEnv变量,也就是说JNIEnv只在创建它的线程有效,不能跨线程传递;
相同的Java线程调用本地方法,所使用的JNIEnv是相同的,一个native方法不能被不同的Java线程调用;
JNIEnv和JavaVM:
①每个进程只有一个JavaVM,每个线程都会有一个JNIEnv,大部分JNIAPI通过JNIEnv调用;
也就是说,JNI全局只有一个JavaVM,而可能有多个JNIEnv;
②Android中每当一个Java线程第一次要调用本地C/C++代码时,Dalvik虚拟机实例会为该Java线程产生一个JNIEnv指针;
③Java每条线程在和C/C++互相调用时,JNIEnv 是互相独立,互不干扰的,这样就提升了并发执行时的安全性;
④当本地的 C/C++ 代码想要获得当前线程所想要使用的 JNIEnv 时,
可以使用 Dalvik VM 对象的 JavaVM* jvm->GetEnv()方法,该方法会返回当前线程所在的 JNIEnv*;
JNI的两种注册方式:
(1)静态注册:
原理:
根据函数名建立Java方法和JNI函数的一一对应关系;
流程:
①先编写Java的native方法;
②然后用javah工具生成对应的头文件,执行命令javahpackagename.classname可以生成由包名加类名命名的jni层头文件,
或执行命名javah -o custom.h packagename.classname,其中custom.h为自定义的文件名;
③实现JNI里面的函数,再在Java中通过System.loadLibrary加载so库即可;
静态注册的方式有两个重要的关键词JNIEXPORT和JNICALL,这两个关键词是宏定义,主要是注明该函数式JNI函数,
当虚拟机加载so库时,如果发现函数含有这两个宏定义时,就会链接到对应的Java层的native方法。
JNI函数调用规则:
①JNI的调用函数的定义是按照一定规则命名的:JNIEXPORT 返回值 JNICALL Java_全路径类名_方法名_参数签名(JNIEnv* , jclass, 其它参数);
②包名或类名或方法名中含下划线 _ 要用 1 连接;
③重载的本地方法命名要用双下划线 __ 连接;
④参数签名的斜杠 “/” 改为下划线 "" 连接,分号 “;” 改为 “_2” 连接,左方括号 “[” 改为 “_3” 连接;
动态注册:
原理:
直接告诉native方法其在JNI中对应函数的指针。
通过使用JNINativeMethod结构来保存Java native方法和JNI函数关联关系;
流程:
①先编写Java的native方法;
②编写JNI函数的实现(函数名可以随便命名);
③利用结构体JNINativeMethod保存Java native方法和JNI函数的对应关系;
④利用registerNatives(JNIEnv* env)注册类的所有本地方法;
⑤在JNI_OnLoad方法中调用注册方法;
⑥在Java中通过System.loadLibrary加载完JNI动态库之后,会调用JNI_OnLoad函数,完成动态注册;
17、跟任务栈Task有关的manifest清单文件中activity标签属性:
技术链接:
http://blog.csdn.net/ghj1976/article/details/6371549
①android:taskAffinity:
taskAffinity可以用于指定一个Activity更加愿意依附于哪一个任务栈;
taskAffinity的值如果没指定,则所有activity共用application标签指定的taskAffinity;
如果application也没指定,则taskAffinity值默认为应用包名;
备注:
android:taskAffinity属性只有通过Flag位为FLAG_ACTIVITY_NEW_TASK的Intent启动Activity时,
该Activity的android:taskAffinity属性才会生效,系统才会将具有相同Task亲和力的Task切换到前台,然后启动该Activity;
否则该Activity仍然运行在启动它的Task中
②android:allowTaskReparenting:
用来标记Activity能否从启动的Task移动到有着affinity的Task(当这个Task进入到前台时)
“true”,表示能移动,“false”,表示它必须呆在启动时呆在的那个Task里;
一般来说,当Activity启动后,它就与启动它的Task关联,并且在那里耗尽它的整个生命周期。
当当前的Task不再显示时,你可以使用这个特性来强制Activity移动到有着affinity的Task中。
典型用法是:把一个应用程序的Activity移到另一个应用程序的主Task中;
Actvity的affinity是由taskAffinity特性定义的。Task的affinity是通过读取根Activity的affinity决定。
因此,根Activity总是位于相同affinity的Task里。
由于启动模式为"singleTask"和"singleInstance"的Activity只能位于Task的底部,
因此,重新宿主只能限于"standard"和"singleTop"模式。
③android:alwaysRetainTaskState:
用来标记Activity所在的Task的状态是否总是由系统来保持。
“true”,表示总是;“false”,表示在某种情形下允许系统恢复Task到它的初始化状态。默认值是"false"。
这个特性只针对Task的根Activity有意义;对其它Activity来说,忽略之;
④android:clearTaskOnLaunch:
用来标记是否从Task中清除所有的Activity,除了根Activity外(每当从主画面重新启动时)
“true”,表示总是清除至它的根Activity,“false"表示不。默认值是"false” ;
这个特性只对启动一个新的Task的Activity(根Activity)有意义;对Task中其它的Activity忽略;
⑤android:launchMode:
standard
singleTop
singleTask
singleInstance
⑥android:noHistory:
用于标记当用户从Activity上离开并且它在屏幕上不再可见时Activity是否从Activitystack中清除并结束(调用finish()方法);
“true”,表示它应该关闭,“false”,表示不需要。默认值是"false";
比如启用界面的就可以借用这个
18、跟任务栈Task有关的Intent对象中设置的Flag:
技术链接:
http://blog.csdn.net/javensun/article/details/8700265
①Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT:
默认标记;
通常在应用代码中不需要设置这个FLAG,当launchMode为singleTask时系统会默认设置这个标志;
②FLAG_ACTIVITY_CLEAR_TOP:
清空任务中在其之上的Activity;
如果设置了这个标志,并且待启动的Activity已经存在于当前的task中,那就不会再给这个activity新起一个实例,
而是将task中在它之上的其它activity全部关闭,然后把Intent作为一个新的Intent传给这个Activity。
③FLAG_ACTIVITY_CLEAR_TASK:
清空任务标志;
如果Intent中设置了这个标志,会导致含有待启动Activity的Task在Activity被启动前清空。
也就是说,这个Activity会成为一个新的root,并且所有旧的activity都被finish掉。
这个标志只能与FLAG_ACTIVITY_NEW_TASK一起使用;
④FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET:
任务重置时将任务中在此标记之后的Activity清空;
设置这个标志意味着在activity栈中做一个标记,在Task重置的时候栈就把从标记往上的activity都清除。
也就是说,下次这个Task被通过FLAG_ACTIVITY_RESET_TASK_IF_NEEDED调到前台时(通常是由于用户从桌面重新启动),
这个activity和它之上的activity都会被finish掉,这样用户就不会再回到他们,而是直接回到在它们之前的activity
⑤FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:
不显示在近期任务中;
如果设置这个标志,这个Activity就不会在近期任务中显示
⑥FLAG_ACTIVITY_FORWARD_RESULT:
转发结果;
如果ActivityA在启动ActivityB时设置了这个标志,那A的答复目标目标会传递给B,
这样一来B就可以通过调用setResult(int)将返回结果返回给A的答复目标
⑦FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY:
从近期任务中启动的标志;
这个标志通常情况下不会通过应用的代码来设置,而是在通过最近任务启动activity时由系统设置的;
⑧FLAG_ACTIVITY_MULTIPLE_TASKActivity:
可在多任务运行的标志;
除非你实现了自己的顶级应用启动器,否则不要使用这个标志。
与FLAG_ACTIVITY_NEW_TASK一起使用可以不再把已存在的任务唤起到前台。
当被设置时,系统总会为Intent的Activity启动一个新的task,而不管是否已经有已存在的任务在做同样的事情。
因为默认系统不包含图形化的任务管理功能,所以除非你给用户提供了返回到已启动任务的方法,否则就不要用这个标志。
如果FLAG_ACTIVITY_NEW_TASK没有设置,则这个标志也被忽略。
⑨FLAG_ACTIVITY_NEW_TASK:
尝试在新任务中启动Activity的标志,并不一定就会在新的任务中;
这个标志通常被用来呈现一种"laucher"类型的行为:
为用户提供一个可单独解决的事情列表,完全独立于启动他们的Activity之外运行。
使用这个标志时,如果有一个任务已经运行了你要启动的Activity,那就不会在创建新的Activity,
而是将现有的任务保持之前的状态直接唤到前台;
⑩FLAG_ACTIVITY_NO_ANIMATION:
禁用切换动画;
禁用掉系统默认的Activity切换动画
19、intent-filter:
技术链接:
http://blog.csdn.net/cnnumen/article/details/8464786
20、activity被系统自动销毁如何保存数据?
技术链接:https://www.cnblogs.com/xuan52rock/p/5288630.html
系统恢复activity的状态数据原理:
当系统内存不足时,调用onPause和onStop方法后的activity可能会被系统销毁,
由于activity是被异常终止的,系统会调用onSaveInstanceState方法对Activity的状态进行保存,
比如activity中各种UI的控件的状态,android几乎所有系统控件都实现了onSaveInstanceState方法,
因此当activity被摧毁和重建时,这些UI控件会自动保存和恢复状态数据。
比如EditText控件会自动保存和恢复输入的数据,CheckBox控件会自动保存和恢复选中状态,
只需要为这些控件指定一个唯一的ID,剩余的事情就可以自动完成了。
如果没有为控件指定ID,则这个控件就不会进行自动的数据保存和恢复操作。
当Activity被重新创建后,系统会将之前onSaveInstanceState保存的数据Bundle,
传递给onRestoreInstanceState和onCreate方法。
注意:
onSaveInstanceState在onStop之前调用,与onPause没有既定的时序关系;
onRestoreInstanceState的调用时机在onStart之后;
在正常情况下Activity的创建和销毁不会调用onSaveInstanceState和onRestoreInstanceState方;
onSaveInstanceState只适合用于保存一些临时性的状态,比如UI控件的状态,成员变量的值等;
而onPause适合用于数据的持久化保存。
onCreate中参数和onRestoreInstanceState中的Bundle参数是一样的,不过onCreate中的Bundle参数可能为空,
而Bundle为Null时,系统不会调用onRestoreInstanceState
activity被系统自动销毁的情况:
按HOME键,应用程序切换到后台时
长按HOME键时
按下电源按键锁屏时
横竖屏切换时
activity被系统销毁View数据的保存:
①系统默认控件:
Android默认控件都已经为我们实现了数据保存和恢复机制;
但是大部分都没有开启这个机制,需要我们自己去控制,通过android:freezesText=“true"设置
②自定义控件:
因为自定义控件的数据不确定性,导致保存和恢复的逻辑只能我们自己来实现,但是也不会脱离View的范围。
保存数据的Model,这个要继承基础类BaseSavedState,通过重写onSaveInstanceState()和onRestoreInstanceState()方法实现;
但是需要注意的是,在onRestoreInstanceState中一定要调用super.onRestoreInstanceState否则会报错,
其原因就在我们继承的BaseSavedState中。在BaseSavedState中,系统还为我们处理了一个变量mStartActivityRequestWhoSaved的存取,
这个变量是用来接收startActivityForResult处理结果的标示符,因此不能再重建的时候进行改变
21、服务启动过程:
http://blog.csdn.net/qq_23547831/article/details/51105171
22、Service:运行在主线程里的,没有界面的后台组件,Service是Android中实现程序后台运行的解决方案。
技术链接:http://blog.csdn.net/guolin_blog/article/details/11952435/
生命周期方法:
onCreate:
Service第一次被创建的时候调用
onStartCommand:
每次通过startService()方法启动服务都会调用;
onBind:
每次通过bindService()方法启动服务都会调用;
onUnbind:
每次通过unbindService()方法停止服务都会调用;
onDestroy:
Service被销毁的时候调用
开启服务的两种方式:
startService():
通过start启动的服务生命周期:onCreate() -> onStartCommand() -> onDestory();
一旦服务开启跟调用者就没有任何关系了,调用者退出了,调用者挂了,服务还在后台长期的运行;
开启者不能调用服务里面的方法;
bindService():
bind启动的服务生命周期是onCreate() -> onBind() -> onunbind() -> onDestory();
bind的方式开启服务,绑定服务,调用者挂了,服务也会跟着挂掉;
绑定者可以调用服务里面的方法;
停止服务的两种方法:
stopService():停止通过startService()方法启动的服务
unbindService():停止通过bindService()方法启动的服务
绑定服务:调用服务中的方法
绑定本地服务:
①创建了一个ServiceConnection的匿名类,重写onServiceConnected()方法和onServiceDisconnected()方法;
②调用bindService()方法,将ServiceConnection的匿名类传入bindService()方法第二个参数;
②定义Binder的实现类,新建Binder的实现类对象,在Service的onBind生命周期方法中返回Binder的实现类对象
④在ServiceConnection的实现类的onServiceConnected方法中将参数IBinderservice强转成自定义的Binder的实现类
绑定远程服务(AIDL:androidinterfacedefinitionlanguage):
应用A调用应用B中服务的方法,AIDL技术将两个应用中相同包下相同声明的接口变成一个接口
为什么用aidl实现进程间通信?什么情况下使用?
安卓实现进程间通信的方式很多,比如广播、ContentProvider、Messager,官方文档中介绍aidl有这么一句话:
只有当你允许来自不同的客户端访问你的服务并且需要处理多线程问题时你才必须使用AIDL。
可见AIDL是处理多线程、多客户端并发访问的。而Messager是单线程处理。
23、在什么情况下使用startService或bindService或同时使用startService和bindService?
①如果你只是想要启动一个后台服务长期进行某项任务那么使用startService便可以了;
②如果你想要与正在运行的Service取得联系,那么有两种方法,一种是使用broadcast,另外是使用bindService,
前者的缺点是如果交流较为频繁,容易造成性能上的问题,并且BroadcastReceiver本身执行代码的时间是很短的,
而后者则没有这些问题,因此我们肯定选择使用bindService。
24、Service和Thread的区别?什么时候用Service?什么时候用Thread?
Service和Thread的区别:
Thread是子线程;
Service如果是本地服务,则Service是运行在主线程的main线程里的;
如果是远程服务,则Service是运行在单独进程的main线程里的;
Service使用场景:
既然Service是运行在主线程里的,那为什么要有Service呢?
Service是后台组件,不依赖UI,即使Activity被销毁了,或者程序关闭了,只要进程还在,Service就可以继续运行。
比如需要与服务器之间始终保持着心跳连接,就可以使用Service来实现;
但是Service是运行在主线程的,所以不能直接在Service中进行耗时的操作,否则会发生ANR,
可以在Service里面创建子线程进行耗时的操作。
除了Service是不依赖UI的后台组件,Service还有一个优点,
就是所有的Activity都可以与Service进行关联,然后可以很方便地操作其中的方法。
25、service保活机制,强杀应用,怎么拉起来?
技术链接:http://blog.csdn.net/pvlking/article/details/50503803
http://www.jianshu.com/p/b16631a2fe3c
service保活机制:
要做到后台进程保活,我们需要做到两方面:
①提高进程优先级,降低被回收或杀死概率;
②在进程被干掉后,进行拉起;
具体服务保活方法有以下几种:
①提高服务的优先级,创建前台Service:
后台Service的系统优先级还是比较低的,当系统出现内存不足情况时,就有可能会回收掉正在后台运行的Service。
如果你希望Service可以一直保持运行状态,而不会由于系统内存不足的原因导致被回收,就可以考虑使用前台Service。
前台Service和普通Service最大的区别就在于,它会一直有一个正在运行的图标在系统的状态栏显示,
下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。
当然有时候你也可能不仅仅是为了防止Service被回收才使用前台Service,
有些项目由于特殊的需求会要求必须使用前台Service,
比如说墨迹天气,它的Service在后台更新天气数据的同时,还会在系统状态栏一直显示当前天气的信息。
②JobScheduler机制唤醒:
Android系统在5.x以上版本提供了一个JobSchedule接口,
系统会根据自己实现定时去调用改接口传递的进程去实现一些操作,
而且这个接口在被强制停止后依然能够正常的启动;不过在一些国产设备上可能无效,比如小米。
②onStartCommand方法返回START_STICKY:
将onStartCommand()返回值设置为START_STICKY,利用系统机制在Service挂掉后自动拉活;
不过这种方式只适合比较原生一些的系统,像小米,华为等这些定制化比较高的第三方厂商,
他们都已经把这些给限制掉了;
当短时间内Service被杀死达到5次,这种方式不适用,系统不再拉起Service
③在service的onDestory里面重启服务:
这个方法在所有能触发onDestory的情况下都是有效的;
④守护进程:
双服务,但360会同时杀掉两个服务;
native守护进程;
26、HandlerThread:
技术链接:
http://blog.csdn.net/qq_23547831/article/details/50936584
作用:
创建一个包含Looper的线程;
使用场景:
假如在应用程序当中为了实现同时完成多个任务,所以我们会在应用程序当中创建多个线程。
为了让多个线程之间能够方便的通信,我们会使用Handler实现线程间的通信。
这个时候我们手动实现的多线程+Handler的简化版就是我们HandlerThrea所要做的事了。
原理:
HandlerThread继承于Thread,所以说HandlerThread本质上是一个线程;
HandlerThread通过start方法开启线程;start方法内部调用run方法;
在run方法内部调用了Looper.prepare()方法和Loop.loop()方法;
通过run方法,HandlerThread创建了该线程的Looper与MessageQueue;
run方法里面当mLooper创建完成后,在同步代码块中调用了notifyAll()方法,为什么呢?
那是因为HandlerThread的getLooper()方法中有个wait(),因为Looper是在HandlerThread的run方法中初始化,
而我们的handler是在UI线程初始化的,也就是说,我们必须等到Looper创建完成,才能正确的返回getLooper();
wait()、notifyAll()就是为了解决这两个线程的同步问题;
在UI线程初始化的Handler时,在Handler构造函数中传入了HandlerThread的Looper对象,
所以Handler对象就相当于含有了HandlerThread线程中Looper对象的引用;
调用Handler的sendMessage方法发送消息,在Handler的handleMessge方法中就可以接收到消息了;
最后需要注意的是在我们不需要这个looper线程的时候,需要通过HandlerThread的quit()方法手动停止掉;
27、IntentService:
技术链接:
http://blog.csdn.net/qq_23547831/article/details/50958757
http://blog.csdn.net/lmj623565791/article/details/47143563
作用:
IntentService是一个基于Service的一个类,用来处理异步的请求。
通过startService(Intent)来提交请求,该Service会在需要的时候创建,
当完成所有的任务以后自己关闭,且请求是在工作线程处理的;
使用了IntentService最起码有两个好处,
一方面不需要自己去newThread了;另一方面不需要考虑在什么时候关闭该Service了;
底层实现:
IntentService继承Service,内部封装了一个ServiceHandler和一个HandlerThread;
IntentService被创建的时候,在其create方法会创建一个HandlerThread,并且调用HandlerThread的start方法,
HandlerThread内部封装了一个Looper和MessageQueue;
IntentService的create方法在创建HandlerThread对象后,将HandlerThread的Looper对象传入ServiceHandler构造函数,
初始化ServiceHandler对象,让ServiceHandler对象和HandlerThread内部的Looper关联,
保证ServiceHandler的handMessage方法在HandlerThread线程中执行;
每次启动IntentService的时候,会执行onStartCommand方法,该方法通过ServiceHandler对象发送一个Message,
将接收到的intent对象传递到消息队列中处理;在ServiceHandler的handleMessage方法中,
调用了其onHandlerIntent抽象方法;而ServiceHandler对象是依附于HandlerThread线程的,
所以onHandlerIntent在子线程中执行;然后调用stopSelf(msg.arg1),注意这个msg.arg1是个int值,
相当于一个请求的唯一标识。每发送一个请求,会生成一个唯一的标识,然后将请求放入队列;
当IntentService的消息队列中含有消息时调用stopSelf(startId)并不会立即stop自己,
只有当消息队列中最后一个消息被执行完成时才会真正的stop自身。如果传入的是-1则直接销毁。
在IntentService任务完成销毁,会回调onDestory,
在onDestory方法通过HandlerThread的quit()中释放掉HandlerThread的Looper。
注意:
①自定义自己的IntentService时,IntentService的构造函数一定是参数为空的构造函数,
然后再在其中调用super(“name”)这种形式的构造函数。
因为Service的实例化是系统来完成的,而系统是用参数为空的构造函数来实例化Service的。
②IntentService中的多个耗时操作任务是串行的,
多次启动IntentService,则每一个耗时操作会以工作队列的方式在IntentService的onHandleIntent回调方法中执行,
并且,每次只会执行一个工作线程,执行完第一个再执行第二个,以此类推。
28、服务器只提供数据接收接口,在多线程或多进程条件下,如何保证数据的有序到达?
IntentService实现;
29、BroadcastReceiver:
类型:
无序广播:广播不能被中断,接收者之间不能传递数据
有序广播:广播可被中断(如垃圾短信过滤),接收者之间可传递数据
声明方式:
动态声明和清单文件声明
广播接收条件:
权限和action
广播只有在接收到时才会创建广播接收者,接收完后就销毁该对象,故广播中不能进行耗时的操作;
30:如何通过广播拦截和abort一条短信?广播是否可以请求网络?广播引起anr的时间限制?
广播引起anr的时间限制:
10秒;
广播是否可以请求网络:
BroadcastReceiver的生命周期很短,从onReceiver()方法开始执行到结束,为其有效期,
之后系统会销毁BraodcastReceiver对象,所以在onReceive方法中执行异步请求操作,
很可能请求结果没有返回,BroadcastReceiver就被系统回收了。
所以要在BroadcastReceiver中执行耗时操作,通过创建子线程的方式是不可靠的,
因为BroadcastReceiver的生命周期很短,一旦结束,其所在进程属于空进程,
极易在系统内存不足时优先被杀死,如此,正在工作的子线程也会被杀死。
在BroadcastReceiver中执行耗时操作,可开启一个Service将耗时操作交给Service,保证耗时操作执行完成。
如何通过广播拦截和abort一条短信:
BroadcastReceiver可以监听系统进程,比如android的收短信,电量低,电量改变,系统启动等;
而接收短信的广播是有序广播,有序广播可以被中断。
只需要定义一个类继承BroadcastReceiver,然后在清单文件manifest中注册,添加接收短信的action,
同时将优先级设高点,然后我们就可以接收到接收短信的广播,
在自定义的BroadcastReceiver类的onReceive方法中,
通过abortBroadcast()方法取消广播。这样就实现拦截一条短信。
31、ContentProvider:内容提供者
作用:
对外提供对数据的统一访问方式,避免直接对数据的不安全操作,保证数据的安全;
使用场景:
①ContentProvider提供了对底层数据存储方式的抽象,底层使用了SQLite数据库,在用了ContentProvider封装后,
即使你把数据库换成MongoDB,也不会对上层数据使用层代码产生影响;
②是ContentProvider为应用间的数据交互提供了一个安全的环境。
它准许你把自己的应用数据根据需求开放给其他应用进行增、删、改、查,
而不用担心直接开放数据库权限而带来的安全问题;
生命周期方法:
onCreate:
第一次访问时被调用,通常用来给内容提供者匹配器添加uri,即具体访问内容提供者数据库哪张表的,以及初始化SQLiteHelper
insert:
插入
query:
查询
update:
更新
delete:
删除
getType:
返回单条数据还是多条数据
uri:每个ContentProvider都有一个公共的URI,这个URI用于表示这个ContentProvider所提供的数据,uri包含如下信息:
authority:
授权信息,用于区别其他ContentProvider,其他应用方位该内容提供者的唯一通道;
path:
表名,即访问数据库哪张表的访问路径,用以区分ContentProvider中不同的数据表;
id:匹配器匹配uri的path时返回的值,用以区别表中的不同数据;
监听内容提供者数据变化:
内容观察者ContentObserver想要监听到内容提供者的内容变化,
则ContentProvider必须通过ContentResolver.setNotifyChange方法通知内容观察者内容变化了,
即在insert、update和delete方法中调用;
ContentResolver:
为什么我们不直接访问Provider,而是又在上面加了一层ContentResolver来进行对其的操作?
手机中不只有一个ContentProvider,比如系统联系人、短信等,如果不提供ContentResolver,
那么你得去了解每个ContentProvider的实现,所以android提供了ContentResolver来统一管理与不同ContentProvider间的操作;
ContentObserver:
内容观察者;
目的是观察特定Uri引起的数据库的变化,继而做一些相应的处理;
32、ContentProvider的权限管理(读写分离,权限控制-精确到表级,URL控制):
权限管理:
清单文件manifast中provider标签的属性:
permission:
读、写ContentProvider中的数据所必需的权限名称;
readPermission:
使用ContentProvider的查询功能所必需的权限,
即使用ContentProvider里的query()函数的权限。
writePermission:
使用ContentProvider的修改功能所必须的权限,
即使用ContentProvider的insert()、update()、delete()函数的权限。
33、什么是ANR?如何避免它?
什么是ANR:
在Android上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,
这个对话框称作应用程序无响应(ANR:ApplicationNotResponding)对话框;
不同的组件发生ANR的时间不一样,主线程(Activity、Service)是5秒,BroadCastReceiver是10秒;
如何避免:
将所有耗时操作,比如访问网络,Socket通信,查询大量SQL语句,复杂逻辑计算等都放在子线程中去;
如果耗时操作需要让用户等待,那么可以在界面上显示进度条;
34、Fragment生命周期;Fragment状态保存:
技术链接:
http://blog.csdn.net/guolin_blog/article/details/8881711
Fragment生命周期:
onAttach、onCreate、onCreateView、onActivityCreate、onStart、onResume、
onPause、onStop、onDestroyView、onDestroy、onDetach
开启一个Fragment时,Fragment依次执行:
onAttach、onCreate、onCreateView、onActivityCreate、onStart、onResume
这时点击一下home键,Fragment依次执行:
onPause、onStop
再切换回来,Fragment依次执行:
onStart、onResume
点击back键退出,Fragment依次执行:
onPause、onStop、onDestroyView、onDestroy、onDetach
Fragment参数传递:
为什么官方推荐Fragment.setArguments(Bundlebundle)这种方式来传递参数,而不推荐通过构造方法直接来传递参数?
因为当fragment被系统销毁重建时,系统会通过默认的空参数构造方法创建fragment,导致通过构造方法传递的参数丢失。
可能导致fragment被异常销毁的几种情况:
①按HOME键返回桌面时;
②按菜单键回到系统后台,并选择了其他应用时;
③按电源键时;
④屏幕方向切换时;
Fragment数据保存和恢复:
技术链接:http://www.jianshu.com/p/75dc2f51cd63
①只有一个Fragment在栈中时,旋转屏幕,fragment被异常销毁:
这种情况会调用onSaveInstanceState方法;
可以在onSaveInstanceState方法将数据保存到它的参数bundle对象中;
在onActivityCreated或者onRestoreInstanceState中恢复数据,
它的参数中的bundle对象包含了在异常销毁前保存的数据;
②后退栈中的fragment:
这种情况Fragment从后退栈中返回时,会回调onDestroyview方法和onCreateview方法,没调用onSaveInstanceState方法;
此时可以通过Argument保存数据和恢复;
③在后退栈中超过一个fragment时,旋转屏幕两次:
当旋转屏幕一次,onSaveInstanceState会被回调,UI的状态也会被保存;
当旋转屏幕时,onSaveInstanceState方法被调用,但是在后退栈中的fragment会完全销毁视图,
直到你浏览返回到原来那个fragment才会重新创建。因此,你再次旋转屏幕,就没有视图来保存状态。
解决方法是保存状态的时候检查在fragment中视图是否存在。
如果存在那就保存,如果不存在,那就在Argument中savedState不需要保存,然后返回时保存。
或者我们甚至不需要做任何事,因为在Argument中已经做好了。
35、startActivityForResult是哪个类的方法?在什么情况下使用?如果在Adapter中使用应该如何解耦?
startActivityForResult是哪个类的方法?
activity的方法;
在什么情况下使用?
想在Activity中得到新打开Activity关闭后返回的数据时候使用startActivityForResult;
如果在Adapter中使用应该如何解耦?
在Adapter中不能调用startActivityForResult方法(为什么),所以需要解耦,
可以在Activity中定义一个方法,然后在adapter中调用。
36、SharedPreferences是否是线程安全的?
SharedPreferences线程不安全。
37、Handler机制及底层实现:
技术链接:
http://blog.csdn.net/qq_23547831/article/details/50751687
http://blog.csdn.net/guolin_blog/article/details/9991569
Handler消息传递机制:
①Handler通过sendMessage()方法发送Message:
Handler提供很多发送消息的方法,除了sendMessageAtFrontOfQueue(),
其它的发送消息方法最终都会转到sendMessageAtTime(Messagemsg,longuptimeMillis)方法,
该方法接收两个参数,第一个参数是我们发送的Message对象,第二个参数是发送消息的时间,
发送消息最终会调用Handler的enqueueMessage方法,在该方法中,会将Message的target指向当前的Handler对象,
然后调用MessageQueue的enqueueMessage方法,将消息传递到MessageQueue;
②MessageQueue将Message入队;
在MessageQueue的enqueueMessage方法中,并没有使用一个集合把所有的Message都保存起来,
它只使用了一个mMessages对象表示当前待处理的Message,然后将所有的Message按时间来进行排序,
根据时间的顺序将Message的next指向下一个Message;
③Looper通过loop()方法循环遍历MessageQueue,将待处理消息mMessages出列:
Looper的loop()会进入了一个死循环,然后不断地调用的MessageQueue的next()方法,将消息出队,
MessageQueue的next()方法里面对多线程通过同步代码块做了并发控制;
如果当前MessageQueue中存在待处理mMessages,就将这个消息出队;
否则否则就进入一个阻塞状态,一直等到有新的消息入队;
loop方法将出列的消息通过msg.target.dispatchMessage回调到Handler的dispatchMessage方法;
④Handler处理消息:
在dispatchMessage()方法中,如果message的callback不为空,则调用message.callback.run();
如果Handler的Callback不为空,则调用Handler中Callback的handleMessage()方法;
否则直接调用Handler的handleMessage()方法,并将消息对象作为参数传递过去;
在子线程更新UI的方法:
①Handler的sendMessage()方法;
②Handler的post()方法;
其内部调用就是sendMessageDelayed方法;
③View的post()方法;
其调用的就是activity中默认保存的handler对象的post方法;
④Activity的runOnUiThread()方法;
方法内部判断当前线程是否是UI线程,如果不是,则调用handler的post方法,否则直接执行run方法;
Handler如何保证不引起频繁GC操作:
通过Handler.obtainMessage方法重用Message,避免创建对象申请内存的开销;
当消息被remove或者从消息队列出队执行后,会调用消息的recycle方法,接着调用recycleUnchecked,
在recycleUnchecked方法中,会将当前消息保存到sPool中,同时sPoolSize加1,PoolSize最大值为50;
当通过Handler.obtainMessage方法获取message时,最终会调用Message的obtain方法,
该方法是线程安全的,在该方法中,如果sPool不为空,则返回,同时将sPool赋值为sPool.next,sPoolSize减1;
否则调用message的构造方法创建一个message返回;
38、Handler内存泄露及解决:
当在activity中通过内部类创建Handler的时候,Handler对象会隐式地持有外部对象Activity的引用,
而handler创建时,会和主线程的Looper关联,Looper对象在整个应用生命周期中存在;
当handler通过sendMessageDelayed或者postDelayed方法发送延迟消息的时候,
如果activity关闭后,延迟消息还没被处理,则延迟消息会继续在主线程的消息队列中,
而消息持有handler的引用,handler又持有activity的引用,
所以就导致activity对象无法被回收,从而导致了activity内存泄漏。
Handler内存泄漏解决方法:
①在页面关闭的时候,在onDestroy方法中通过handler.removeCallbacksAndMessages(null),把消息从消息队列移除;
②将Handler声明为静态类,静态类不持有外部类的对象,但由于Handler不持有Activity的引用了,
无法操作Activity中的属性和方法,所以需要在Handler中增加对Activity的弱引用;
39、Looper:
概述:
Looper对象是存在当前线程的ThreadLocal中;
为什么子线程直接创建Handler对象会报错?而在Activity中能直接创建?
在Handler构造方法里,会通过Looper.myLooper()来获取当前线程的Looper对象,
如果Looper对象为空,则抛出RuntimeException;
在Activity中能直接创建Handler是因为ActivityThread的main()方法中,
已经通过Looper.prepareMainLooper()为我们准备了一个Looper对象;
为什么每个线程只能调用一次Looper.prepare()创建Looper对象?
Looper中有一个ThreadLocal成员变量,用来维护Looper对象的副本;
在Looper.prepare()方法中,会先ThreadLocal的get方法获取Looper对象,
如果获取到的Looper对象不为空,则抛出RuntimeException。
在子线程创建Handler正确步骤:
①Looper.prepare():(必须调用一次,不调用或者调用多次程序都会崩溃)
②Handlerhandler=newHandler()
③Looper.loop();
40、点九图片的实现原理:
NinePatchDrawable的源码:
九图的原理:
一个普通的Drawable加上一个用于描述拉伸坐标的数组chunk,
当缩放Drawable的时候,也必须更新chunk,不然拉伸的坐标就对不上;
41、setContentView:
先是调用Window的setContentView方法,而Window的实现类是PhoneWindow,所以最终调用的是PhoneWindow的setContentView
42、View的绘制过程:
技术链接:
http://www.jianshu.com/p/5a71014e7b1b
每一个View的绘制过程都必须经历三个最主要的阶段:onMeasure()、onLayout()和onDraw()
onMeasure():
作用:用于对视图进行测量,即测量视图的大小;
测量过程:
View系统的绘制流程会从ViewRoot的performTraversals()方法中开始,在其内部调用View的measure()方法。
View的measure()方法接收两个参数,widthMeasureSpec和heightMeasureSpec,
这两个值分别用于确定视图的宽度和高度的规格和大小;
MeasureSpec的值由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格。
specMode一共有三种模式:
①UPSPECIFIED:
父容器对于子容器没有任何限制,子容器想要多大就多大;
②EXACTLY:
父容器已经为子容器设置了尺寸,子容器应当服从这些边界,不论子容器想要多大的空间;
③AT_MOST:
子容器可以是声明大小内的任意大小;
通常情况下,父View把自己的MeasureSpec传给子View,
结合子View自己的LayoutParams算出子View的MeasureSpec,然后继续往下传。
子view的MeasureSpec具体怎么确定的呢?
具体情况如下:
①父view的specMode是EXACTLY,即父view的大小是确切的,如果一个View的specMode是EXACTLY,那么它的size是多大,最后展示到屏幕就一定是那么大。
如果子view的layout_xxxx是match_parent,那么子view的size=父view的size,specMode=EXACTLY;
如果子view的layout_xxxx是wrap_content,也就是子view的大小是根据自己的content来决定的,但是子View的毕竟是子View,
大小不能超过父View的大小,但是子View的是wrap_content,我们还不知道具体子View的大小是多少,
要等到child.measure(childWidthMeasureSpec,childHeightMeasureSpec)调用的时候才去真正测量子View的大小,
所以,子View的Specmode的应该是AT_MOST,而size暂定父View的size;
如果子View的layout_xxxx是确定的值,不管你父View的mode和size是什么,
我都是确定的值,不管我的父View有多大,也不管我自己的content有多大,反正我就是这么大,
所以这种情况Specmode=EXACTLY,size=layout_xxxx的值;
②父view的specMode是AT_MOST,说明父View的大小是不确定,最大的大小是MeasureSpec的size值,不能超过这个值。
如果子View的layout_xxxx是match_parent,因为父View的大小是不确定(只知道最大只能多大),
子View的大小match_parent(充满整个父View),那么子View你即使充满父容器,你的大小也是不确定的,
父View自己都确定不了自己的大小,你MATCH_PARENT你的大小肯定也不能确定的,
所以子View的mode=AT_MOST,size=父View的size;
如果子View的layout_xxxx是wrap_content,父View的大小是不确定(只知道最大只能多大),
子View又是WRAP_CONTENT,那么在子View的Content没算出大小之前,子View的大小最大就是父View的大小,
所以子View的Specmode的就是AT_MOST,而size暂定父View的size。
如果子View的layout_xxxx是确定的值,不管你父View的mode和size是什么,
我都是确定的值,不管我的父View有多大,也不管我自己的content有多大,反正我就是这么大,
所以这种情况Specmode=EXACTLY,size=layout_xxxx的值;
③父view的specMode是UNSPECIFIED(未指定),表示没有任何束缚和约束,不像AT_MOST表示最大只能多大,
如果子view的layout_xxxx是match_parent,因为父View的MeasureSpec是UNSPECIFIED,父View自己的大小并没有任何约束和要求,
那么对于子View来说无论是wrap_content还是match_parent,子View也是没有任何束缚的,想多大就多大;
如果子View的layout_xxxx是确定的值,那么就简单了,不管你父View的mode和size是什么,
我都是确定的值,不管我的父View有多大,也不管我自己的content有多大,反正我就是这么大,
所以这种情况Specmode=EXACTLY,size=layout_xxxx的值;
measure()会调用onMeasure(),onMeasure()方法里面才是真正去测量和设置view的大小;
在onMeasure()中,默认会调用getDefaultSize()方法来获取视图的大小,
在getDefaultSize()方法中,如果specMode等于AT_MOST或EXACTLY,系统默认返回测量specSize,否则返回设置的size;
之后会在onMeasure()方法中调用setMeasuredDimension()方法来设定测量出的大小;
至此,一次measure过程就结束了;
对于View默认是测量很简单,大部分情况就是拿计算出来的MeasureSpec的size当做最终测量的大小。
而对于其他的一些View的派生类,如TextView、Button、ImageView等,它们的onMeasure方法系统了都做了重写,
不会这么简单直接拿MeasureSpec的size来当大小,而去会先去测量字符或者图片的高度等,
然后拿到View本身content这个高度(字符高度等),如果MeasureSpec是AT_MOST,
而且View本身content的高度不超出MeasureSpec的size,那么可以直接用View本身content的高度。
onLayout():
作用:用于对视图进行布局,即确定视图的位置;
布局过程:
ViewRoot的performTraversals()方法会在measure结束后继续执行,并调用View的layout()方法来执行此layout的过程;
layout()方法接收四个参数,分别代表着左、上、右、下的坐标,这个坐标是相对于当前视图的父视图而言的;
这四个坐标是怎么计算出来的呢?
根据onMeasure方法计算出来的宽高、xml中设置的gravity,以及其他参数一起来确定子View在父视图的具体位置;
在layout()中,会调用setFrame()方法来判断视图的大小是否发生过变化,以确定有没有必要对当前的视图进行重绘,
如果必要,则调用onLayout()方法,
但View的onLayout()方法是个空方法,因为onLayout()过程是为了确定视图在布局中所在的位置,
在ViewGroup中的onLayout(),则是一个抽象方法,这就意味着所有ViewGroup的子类都必须重写这个方法;
当我们对view的布局有特殊要求时,可通过重写onLayout()实现自定义控件;
onDraw():
作用:用于对视图进行绘制;
绘制过程原理:
layout结束后,ViewRoot会继续执行并创建出一个Canvas对象,然后调用View的draw()方法来执行具体的绘制工作;
在draw()方法中,
首先会绘制背景,得到一个mBGDrawable对象;
接着根据layout过程确定的视图位置来设置背景的绘制区域,调用mBGDrawable的draw()方法完成背景的绘制工作;
接着会调用onDraw()方法绘制视图的内容,View的onDraw()是个空方法,ViewGroup的onDraw也没有实现,需要子类去实现;
接着会通过dispatchDraw()对当前视图的所有子视图进行绘制,View的dispatchDraw()也是个空方法,ViewGroup的dispatchDraw不是空方法;
最后对视图的滚动条进行绘制(如Listview和ScrollView):
实现自定义控件,使用最多的方法
onDraw()和dispatchDraw()的区别:
①绘制VIew本身的内容,通过调用View.onDraw(canvas)函数实现;
绘制自己的孩子通过dispatchDraw(canvas)实现;
②ViewGroup容器组件的绘制,当它没有背景时直接调用的是dispatchDraw()方法, 而绕过了draw()方法,
当它有背景的时候就调用draw()方法,而draw()方法里包含了dispatchDraw()方法的调用。
因此要在ViewGroup上绘制东西的时候往往重写的是dispatchDraw()方法而不是onDraw()方法;
43、事件分发机制:
技术链接:
http://www.jianshu.com/p/e99b5e8bd67b
事件分发:Activity - >window -> PhoneWindow -> decorview -> Viewgroup -> view;
①默认情况下,就是没给任何view注册监听任何事件,也没重写任何view的事件回调,则事件分发过程如下:
从activity的dispatchTouchEvent方法开始,一层一层往下分发,最后分发到被触摸的控件的disdispatchTouchEvent方法;
事件不会分发给被触摸控件的兄弟控件,也不会分发给被触摸控件的所有父控件的兄弟控件;
然后,再从被触摸控件的onTouchEvent方法开始,在onTouchEvent依次执行ACTION_WODN、ACTION_MOVE和ACTION_UP;
一层一层向上传递,依次传递到所有父控件的onTouchEvent方法,在onTouchEvent依次执行ACTION_WODN、ACTION_MOVE和ACTION_UP;
最后传递到activity的disdispatchTouchEvent方法;
如页面布局:RelativeLayout>LinearLayout>TextView|LinearLayout(>表示包含,|表示并列),点击TextView区域,则事件分发如下:
Activity.dispatchTouchEvent->RelativeLayout.dispatchTouchEvent->LinearLayout.dispatchTouchEvent->TextView.dispatchTouchEvent;
TextView.onTouchEvent(ACTION_WODN、ACTION_MOVE、ACTION_UP)->LinearLayout.onTouchEvent(ACTION_WODN、ACTION_MOVE、ACTION_UP)
->如页面布局:RelativeLayout.onTouchEvent(ACTION_WODN、ACTION_MOVE、ACTION_UP)->Activity.dispatchTouchEvent
②如果ViewGroup重写了onInterceptTouchEvent方法并返回true,则事件会被该ViewGroup拦截,
事件不会再往下分发;
③dispatchTouchEvent方法返回值对事件传递的影响:
如果activity或者控件重写disdispatchTouchEvent方法,并且直接返回false或者true,则事件都不会再向下分发;
④onTouchEvent方法返回值对事件传递的影响:
如果控件的onTouchEvent方法直接返回false,不影响事件的分发,但是会导致该给该控件设置的OnClickListener不起作用;
如果控件的onTouchEvent方法直接返回true,不影响事件通过dispatchTouchEvent向下分发,会影响事件通过onTouchEvent向上传递;
页面布局如下:RelativeLayout>LinearLayout>TextView(>表示包含);
LinearLayout重写onTouchEvent直接返回true,触摸TextView时,事件分发如下:
Activity.dispatchTouchEvent->RelativeLayout.dispatchTouchEvent->LinearLayout.dispatchTouchEvent->TextView.dispatchTouchEvent;
TextView.onTouchEvent(ACTION_WODN、ACTION_MOVE、ACTION_UP)->LinearLayout.onTouchEvent(ACTION_WODN、ACTION_MOVE、ACTION_UP);
Activity.dispatchTouchEvent->RelativeLayout.dispatchTouchEvent->LinearLayout.dispatchTouchEvent;
LinearLayout.onTouchEvent(ACTION_WODN、ACTION_MOVE、ACTION_UP);
④OnClickListener对事件分发的影响:
设置OnClickListener的控件,不影响事件通过dispatchTouchEvent向下分发,会影响事件通过onTouchEvent向上传递;
设置OnClickListener的控件的所有父控件不会执行onTouchEvent方法;
页面布局如下:RelativeLayout>LinearLayout>TextView(>表示包含);
给LinearLayout设置一个OnClickListener,触摸TextView时,事件分发如下:
Activity.dispatchTouchEvent->RelativeLayout.dispatchTouchEvent->LinearLayout.dispatchTouchEvent->TextView.dispatchTouchEvent;
TextView.onTouchEvent(ACTION_WODN、ACTION_MOVE、ACTION_UP)->LinearLayout.onTouchEvent(ACTION_WODN、ACTION_MOVE、ACTION_UP);
Activity.dispatchTouchEvent->RelativeLayout.dispatchTouchEvent->LinearLayout.dispatchTouchEvent->LinearLayout.onTouchEvent(ACTION_UP)->LinearLayout.onClick
⑤:OnTouchListener对事件分发的影响:
设置OnTouchListener的控件并且onTouch方法返回true,不影响事件通过dispatchTouchEvent向下分发,会影响事件通过onTouchEvent向上传递;
页面布局如下:RelativeLayout>LinearLayout>TextView(>表示包含);
给LinearLayout设置一个OnTouchListener,onTouch方法返回false,对事件分发没有影响,onTouch方法返回true,触摸TextView时,事件分发如下:
Activity.dispatchTouchEvent->RelativeLayout.dispatchTouchEvent->LinearLayout.dispatchTouchEvent->TextView.dispatchTouchEvent;
TextView.onTouchEvent(ACTION_WODN、ACTION_MOVE、ACTION_UP)->Activity.dispatchTouchEvent->RelativeLayout.dispatchTouchEvent->LinearLayout.dispatchTouchEvent->LinearLayout.onTouch;
View的dispatchTouchEvent方法事件分发:
当触摸view控件时,最终会调用view的dispatchTouchEvent方法,
在view的dispatchTouchEvent方法中,首先进行一个判断,
如果OnTouchListener不为空,且View是可用的,则事件分发到onTouch方法;
如果onTouch方法返回true,则则dispatchTouchEvent方法直接返回true,事件不会分发到onTouchEvent方法;
否则事件传递到view的onTouchEvent(event)方法;在onTouchEvent(event)方法中,
如果该控件是可点击的,就会执行进行ACTION_DOWN、ACTION_MOVE、ACTION_UP等action的处理,
在ACTION_UP中,如果通过setOnClickListener给该控件设置过OnClickListener,
则会通过performClick()方法将事件传递到view的onClick方法。
备注:
touch事件的层级传递ACTION_DOWN,ACTION_MOVE,ACTION_UP,
当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发后一个action;
ViewGroup的dispatchTouchEvent方法事件分发:
当触摸ViewGroup控件时,最终事件会分发到ViewGroup的dispatchTouchEvent方法
在ViewGroup的dispatchTouchEvent方法中,如果onInterceptTouchEvent方法返回true,则事件被拦截,不会向下分发;
否则会通过dispatchTouchEvent向下分发;
44、onTouch和onTouchEvent的区别:
onTouch执行的前提:
view的OnTouchLister不为空;
view是可用的
onTouch优先于onTouchEvent执行;
如果在onTouch方法中通过返回true将事件消费掉,onTouchEvent将不会再执行;
45、ApplicationContext和ActivityContext的区别:
ApplicationContext:
生命周期与Application相关,随着Application的销毁而销毁,伴随Application的一生,与activity的生命周期无关
ActivityContext:
生命周期与Activity相关,随着Activity的销毁而销毁
46、说说Activity、Intent、Service是什么关系:
Activity和Service都是Android四大组件之一;
他俩都是Context类的子类ContextWrapper的子类;
Activity负责用户界面的显示和交互,Service负责后台任务的处理;
Activity和Service之间可以通过Intent传递数据;
47、Parcelable和Serializable的区别:
①Serializable的本质是使用了反射,序列化的过程比较慢,
这种机制在序列化的时候会创建很多临时的对象,比引起频繁的GC;
Parcelable方式的本质是将一个完整的对象进行分解,
而分解后的每一部分都是Intent所支持的类型,这样就实现了传递对象的功能了;
①Serializable的作用是为了保存对象的属性到本地文件、数据库、网络流,以方便数据传输;
Android的Parcelable的设计初衷是因为Serializable效率过慢,
为了在程序内不同组件间以及不同Android程序间(AIDL)高效的传输数据而设计,
这些数据仅在内存中存在,Parcelable是通过IBinder通信的消息的载体;
②Parcelable的性能比Serializable好,在内存开销方面较小,所以在内存间数据传输时推荐使用Parcelable,
如activity间传输数据,而Serializable可将数据持久化方便保存,
但其在内存序列化上开销比较大(Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC),
所以在需要保存或网络传输数据时选择Serializable;
④对于Serializable,类只需要实现Serializable接口,并提供一个序列化版本id;
而Parcelable则需要实现writeToParcel、describeContents函数以及静态的CREATOR变量
⑤Serializable序列化不保存静态变量,可以使用Transient关键字对部分字段不进行序列化,
也可以覆盖writeObject、readObject方法以实现序列化过程自定义
48、屏幕适配的处理技巧都有哪些?
技术链接:
http://www.jianshu.com/p/ec5a1a30694b
由于android系统的开放性,导致android系统、屏幕尺寸、屏幕分辨率碎片化,
为了保证用户获得一致的良好的用户体验,需要屏幕适配。
适配的本质:
使得布局、布局组件自适应屏幕尺寸;
使得图片资源匹配不同的屏幕密度;
适配技巧:
①使用相对布局(RelativeLayout),禁用绝对布局(AbsoluteLayout);
②多用match_parent;
③多用weight(但用weight不利于布局优化,因为要不断的测量自己的宽高);
④使用dip、sp等单位;
⑤.9图片;
⑥提供多套分辨率资源;
49、一张Bitmap所占内存以及内存占用的计算?
技术链接:
http://www.cnblogs.com/whoislcj/p/5547758.html
http://dev.qq.com/topic/591d61f56793d26660901b4e
Bitmap占用内存大小,与手机的屏幕密度、图片所放文件夹密度、图片的色彩格式有关;
占用的内存=图片长度图片宽度单位像素占用的字节数
ALPHA_8:
图片长度图片宽度1
ARGB_4444:
图片长度图片宽度2
ARGB_8888:
图片长度图片宽度4
RGB_565
图片长度图片宽度2
50、ListView的工作原理:
技术链接:http://blog.csdn.net/guolin_blog/article/details/44996879
Adapter的作用:
适配器;
Adapter是ListView和数据源之间起到了一个桥梁的作用,ListView并不会直接和数据源打交道,
而是会借助Adapter这个桥梁来去访问真正的数据源,Adapter的接口都是统一的,
因此ListView不用再去担心任何适配方面的问题;
而Adapter又是一个接口(interface),它可以去实现各种各样的子类,每个子类都能通过自己的逻辑来去完成特定的功能;
RecycleBin机制:
这个机制是ListView能够实现成百上千条数据都不会OOM最重要的一个原因;
RecycleBin是写在AbsListView中的一个内部类,所以所有继承自AbsListView的子类,都可以使用这个机制;
复用convertView:
51、ListView中图片错位的问题是如何产生的?如何解决?
技术链接:http://blog.csdn.net/guolin_blog/article/details/45586553
错位原因:
ListView在借助RecycleBin机制的帮助下,实现了一个生产者和消费者的模式,
不管有任意多条数据需要显示,ListView中的子View其实来来回回就那么几个,
移出屏幕的子View会很快被移入屏幕的数据重新利用起来;
根据ListView的工作原理,显然不可能为每张图片都单独分配一个ImageView控件,
ImageView控件的个数其实就比一屏能显示的图片数量稍微多一点而已,
移出屏幕的ImageView控件会进入到RecycleBin当中,而新进入屏幕的元素则会从RecycleBin中获取ImageView控件。
每当有新的元素进入界面时就会回调getView()方法,而在getView()方法中会开启异步请求从网络上获取图片,
注意网络操作都是比较耗时,当滑动ListView的时候,某一个位置上的元素进入屏幕后开始从网络上请求图片,
但是还没等图片下载完成,它就又被移出了屏幕,
根据ListView的工作原理,被移出屏幕的控件将会很快被新进入屏幕的元素重新利用起来,
而如果在这个时候刚好前面发起的图片请求有了响应,就会将刚才位置上的图片显示到当前位置上,
因为虽然它们位置不同,但都是共用的同一个ImageView实例,这样就出现了图片乱序的情况。
同时,新进入屏幕的元素它也会发起一条网络请求来获取当前位置的图片,
等到图片下载完的时候会设置到同样的ImageView上面,因此就会出现先显示一张图片,然后又变成了另外一张图片的情况。
解决方案:
①使用findViewWithTag:
②使用NetworkImageView:
52、Listview卡顿原因;
原因:
①Adapter的getView方法里面convertView没有使用setTag和getTag方式,
每次getView都要执行findViewById, 这是相当耗时的;
②item布局嵌套太深或者是item布局里面有大图片或者背景;
③listview的高度不是固定或者math_parent,导致多次重复调用getView方法;
④listview被多层嵌套,多次的onMessure导致卡顿;
53、Android开发中,minSdkVersion,compileSdkVersion,buildToolsVersion,targetSdkVersion区别是什么,各自作用是什么?
compileSdkVersion:
编译版本,真正决定代码是否能编译的关键;
minSdkVersion:
最小支持版本,比如设置成15,就无法在低于15的版本运行;
targetSdkVersion:
只是一个标示,如果targetSdkVersion与目标设备的API版本相同时,运行效率可能会高一些;
buildToolsVersion:
编译工具的版本,一般设置为最新即可;
54、内存优化:
技术链接:
http://blog.csdn.net/guolin_blog/article/details/42238627
内存泄露的原因:
导致内存泄漏最主要的原因就是某些长存对象持有了一些其它应该被回收的对象的引用,
导致垃圾回收器无法去回收掉这些对象,内存泄漏最终会导致内存溢出!
常见的内存泄露:
①查询数据库没有关闭Cursor
②registerReceiver后没有unregisterReceiver()
③有Dialog的activityfinish时没有Dialog.dismiss()
④没关闭InputStream/OutputStream
⑥Context泄露
⑦Handler泄漏
⑧Bitmap泄露
内存泄露检查:
重复执行某个操作,如果内存居高不下,则说明内存泄露了
借助内存分析工具如:EclipseMemoryAnalyzer(MAT)
Android官方推荐的内存优化技巧:
小心使用context:
不要让生命周期长的对象引用activitycontext
对于生命周期长的对象,可以使用applicationcontext
节制地使用Service:
当程序需要Service来执行后台任务时,执行才运行Service;任务执行结束停止Service时,需要注意停止service失败导致内存泄漏
启动Service时,系统会倾向于将这个Service所依赖的进程进行保留,这样就会导致这个进程变得非常消耗内存
为了控制Service的生命周期,Android官方推荐的最佳解决方案就是使用IntentService,
这种Service的最大特点就是当后台任务执行结束后会自动停止,从而极大程度上避免了Service内存泄漏的可能性
当界面不可见时释放内存:
当程序界面已经不再可见的时候,我们应当将所有和界面相关的资源进行释放
在Activity中重写onTrimMemory方法,然后在这个方法中监听TRIM_MEMORY_UI_HIDDEN这个级别时即为界面不可见
在Activity中重写onDestroy方法,释放资源
避免在Bitmap上浪费内存:
不要去加载不需要的分辨率
及时释放
使用优化过的数据集合:
AndroidAPI当中提供了一些优化过后的数据集合工具类,如SparseArray,SparseBooleanArray,以及LongSparseArray等,使用这些API可以让我们的程序更加高效
避免创建不必要的对象:
需要拼接字符串时,可优先考虑使用StringBuffer或者StringBuilder来进行拼接,而不是加号连接符,
因为使用加号连接符会创建多余的对象,拼接的字符串越长,加号连接符的性能越低
尽量使用基本数据类来代替封装数据类型,int比Integer要更加高效,其它数据类型也是一样
静态优于抽象:
对常量使用static final修饰符:
多使用系统封装好的API:
避免在内部调用Getters/Setters方法:
使用ProGuard简化代码:
ProGuard除了混淆之外,它还具有压缩和优化代码的功能
了解内存的开支情况:
使用枚举通常会比使用静态常量要消耗两倍以上的内存,在Android开发当中我们应当尽可能地不使用枚举
任何一个Java类,包括内部类、匿名类,都要占用大概500字节的内存空间
任何一个类的实例要消耗12-16字节的内存开支,因此频繁创建实例也是会一定程序上影响内存的。
在使用HashMap时,即使你只设置了一个基本数据类型的键,比如说int,但是也会按照对象的大小来分配内存,大概是32字节,而不是4字节
在activity、fragment中使用Handler时需要弱引用:
布局优化:
多使用include和merge标签
内存查看工具:
eclipse:
http://blog.csdn.net/aaawqqq/article/details/26289499
DDMS --> Heap:
内存分析工具 MAT(Memory Analyzer Tool):
data object的Total Size
studio:
http://www.jianshu.com/p/080473ae050b
Memory Monitor
LeakCanary:
55、SparseArray、SparseBooleanArray和LongSparseArray:
性能更好原因:
①因为它避免了对key的自动装箱;
②内部对数据还采取了压缩的方式来表示稀疏数组的数据,从而节约内存空间;
原理:
①内部通过两个数组来进行数据存储的,一个存储key,另外一个存储value;
②在put添加数据的时候,会使用二分查找法和之前的key比较当前我们添加的元素的key的大小,
然后按照从小到大的顺序排列好,所以,SparseArray存储的元素都是按元素的key值从小到大排列好的。
而在获取数据的时候,也是使用二分查找法判断元素的位置,所以,在获取数据的时候非常快,
比HashMap快的多,因为HashMap获取数据是通过遍历Entry[]数组来得到对应的元素。
56、AsyncTask:
作用:
AsyncTask是一个抽象类,它是由Android封装的一个轻量级异步类;
它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并在主线程中更新UI;
实现原理:
AsyncTask是一个抽象类,它是由Android封装的一个轻量级异步类;
它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并在主线程中更新UI;
AsyncTask是对Handler与线程池的封装;
由于AsyncTask内部包含一个Handler,所以可以发送消息给主线程让它更新UI;
AsyncTask内还包含了一个线程池。使用线程池的主要原因是避免不必要的创建及销毁线程的开销;
AsyncTask的核心方法:
onPreExecute:
这个方法会在后台任务开始执行之间调用,在主线程执行。用于进行一些界面上的初始化操作,比如显示一个进度条对话框等
doInBackground:
这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务;
在这个方法中是不可以进行UI操作的
onProgressUpdate:
当在后台任务中调用了publishProgress方法后,这个方法就很快会被调用;
在这个方法中可以对UI进行操作,在主线程中进行,利用参数中的数值就可以对界面元素进行相应的更新
onPostExecute:
当doInBackground执行完毕并通过return语句进行返回时,这个方法就很快会被调用;
返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些UI操作,在主线程中进行,
比如说提醒任务执行的结果,以及关闭掉进度条对话框等
上面几个方法的调用顺序:
onPreExecute()–>doInBackground()–>publishProgress()–>onProgressUpdate()–>onPostExecute()
AsyncTask不足:
①AsyncTask对象必须在主线程中创建:
②AsyncTask对象的execute方法必须在主线程中调用:
③一个AsyncTask对象只能调用一次execute方法:
57、android的多线程间通信:
Handler和AsyncTask
58、进程间通信:
①Bundle/Intent传递数据:
可传递基本类型,String,实现了Serializable或Parcellable接口的数据结构;
②文件共享:
对同一个文件先后写读,从而实现传输;
③Messenger:
Messenger是基于AIDL实现的,服务端(被动方)提供一个Service来处理客户端(主动方)连接,
维护一个Handler来创建Messenger,在onBind时返回Messenger的binder。
双方用Messenger来发送数据,用Handler来处理数据。
Messenger处理数据依靠Handler,所以是串行的,也就是说,Handler接到多个message时,就要排队依次处理;
④aidl:
AIDL通过定义服务端暴露的接口,以提供给客户端来调用,AIDL使服务器可以并行处理,
而Messenger封装了AIDL之后只能串行运行,所以Messenger一般用作消息传递;
通过编写aidl文件来设计想要暴露的接口,编译后会自动生成响应的java文件,
服务器将接口的具体实现写在Stub中,用iBinder对象传递给客户端,客户端bindService的时候,
用asInterface的形式将iBinder还原成接口,再调用其中的方法
⑤ContentProvider:
底层也是Binder实现,主要用来为其他APP提供数据
⑥Socket:
本机进程之间可以利用socket通信,跨进程之间也可利用socket通信;
socket通信是一种比较复杂的通信方式,通常客户端需要开启单独的监听线程来接受从服务端发过来的数据,
客户端线程发送数据给服务端,如果需要等待服务端的响应,并通过监听线程接受数据,
需要进行同步,是一件很麻烦的事情。socket通信速度也不快。
⑦管道:
任何进程都能通讯,但速度慢;
⑧信号:
不能传递复杂消息,只能用来同步;
59、应用程序启动优化:
①在Application的构造器方法、attachBaseContext()、onCreate()方法中不要进行耗时操作的初始化,
一些数据预取放在异步线程中,可以采取Callable实现;
②对于sp的初始化,因为sp的特性在初始化时候会对数据全部读出来存在内存中,所以这个初始化放在主线程中不合适,
反而会延迟应用的启动速度,对于这个还是需要放在异步线程中处理
③对于MainActivity,由于在获取到第一帧前,需要对contentView进行测量布局绘制操作,尽量减少布局的层次,
考虑StubView的延迟加载策略,当然在onCreate、onStart、onResume方法中避免做耗时操作
60、注解:
技术链接:
http://www.jianshu.com/p/1942ad208927
http://www.trinea.cn/android/java-annotation-android-open-source-analysis/
概述:
Annotation就是注解了,该功能可用于类,构造方法,成员变量,方法,参数等的声明中,
比如常见的@Override,该功能并不影响程序的运行,主要作用就是提供类型安全,对编译器警告等辅助工具产生影响。
作用:
①标记,用于告诉编译器一些信息;
②编译时动态处理,如动态生成代码;
③运行时动态处理,如得到注解信息;
注解类型:
①Nullness注解:
包括@NoNull和@Nullable;
@NoNull编译到类文件里,应用于方法,参数和成员变量
②资源类型注解:
此类注解以Res结尾,比如@BoolRes,@IdRes,@IntegerRes,@StringRes,@ColorRes等
③线程注解:
④变量限制注解:
⑤权限注解:
⑥结果检查注解:
⑦CallSuper注解:
⑧枚举注解:
注解框架:
①Retrofit:
Retrofit是运行时Annotation,并且只能用于修饰Method;
Retrofit检查每个方法的每个Annotation,看是否被RestMethod这个Annotation修饰的Annotation修饰,
这个有点绕,就是是否被GET、DELETE、POST、PUT、HEAD、PATCH这些Annotation修饰,
然后得到Annotation信息,在对接口进行动态代理时会调用到这些Annotation信息从而完成调用;
②ButterKnife:
ButterKnife的InjectViewAnnotation是编译时Annotation,并且只能用于修饰属性;
61、Activity的onCreate方法里面子线程为何能设置UI界面?
技术链接:http://blog.csdn.net/lc_miao/article/details/53909415
查看TextView的setText方法,发现setText里面调用了checkForRelayout,而在checkForRelayout方法里面调用了invalidate这个方法;
invalidate方法调用了ViewGroup.invalidateChild,最终调用ViewRootImpl.checkThread()方法,
ViewRootImpl是一个隐藏类,我们只能去看framework的源码,
发现framework的源码的checkThread方法会检查当前线程是不是UI线程。
如果不是,则会抛出CalledFromWrongThreadException,那为什么可以在onCreate方法里新建子线程去设置UI?
这个问题原因出现在Activity的生命周期中,在onCreate方法中,UI处于创建过程,对用户来说界面还不可视,
直到onStart方法后界面可视了,再到onResume方法后界面可以交互。
从某种程度来讲,在onCreate方法中进行setText不能算是更新UI,只能说是配置UI,或者是设置UI的属性。
这个时候并不会调用invalidate,也就是不会调用到ViewRootImpl.checkThread()。
而在onResume方法后,ViewRootImpl才被创建。这个时候去交互界面以及算是更新UI了,这个时候ViewRootImpl.checkThread()就会报错了。
62、HttpClient和HttpUrlConnection:
用哪个好?为什么?
安卓版本2.2使用HttpUrlConnection:
①HttpClient是apache的开源实现,而HttpUrlConnection是安卓标准实现,安卓SDK虽然集成了HttpClient,但官方支持的却是HttpUrlConnection
②HttpUrlConnection直接支持GZIP压缩;HttpClient也支持,但要自己写代码处理;
③HttpUrlConnection直接在系统层面做了缓存策略处理,加快重复请求的速度。
63、设计模式介绍:比如适配器模式、工厂模式、单例模式,手写单例模式代码(静态内部类>懒汉>饿汉)。可以结合Android源码说设计模式
技术链接:http://www.jianshu.com/p/731dfac62070
①单例设计模式:保证一个类在内存中只有唯一一个对象,比如android源码中的InputMethodManager(整个输入法框架)
静态内部类:
public class Singleton{
private Singleton(){}
private static class InstanceHolder{
private static final Singleton instance = new Singleton();
}
public static Singleton getSingleton(){
return InstanceHolder.instance;
}
}
懒汉式:
class Singleton{
private Singleton(){}
private static final Singleton s=null;
public static Singleton getInstance(){
if(s==null){
s = new Singleton();
}
return s;
}
}
恶汉式:
class Singleton{
private Singleton(){}
private static final Singleton s = new Singleton();
public static Singleton getInstance(){
return s;
}
}
②工厂模式:
提供创建对象的接口,隐藏创建细节
如XML文件的几种解析方法如DOM、SAX、PULL在创建解析器时都是用的工厂模式;
③适配器模式:
适配器模式使原本由于接口不兼容而不能一起工作的那些类可以一起工作
比如ListView和BaseAdapter
④模板方法模式:
定义一个操作中算法的骨架,将一些步骤的执行交给子类去完成
比如View的ondraw方法,BaseActivity
⑤观察者模式:
比如android源码中的按钮的点击事件、广播接收者、ContentProvider中的ContentObserver
⑥建造者模式:
Builder模式,Android中大量地方运用到了Builder模式,比如常见的对话框创建:
⑦策略模式:
65、APK编译构建过程:
apk包内容包括:
classes.dex
resources.arsc
assets
res
AndroidManifest.xml
META-INF
构建流程:
①通过aapt打包res资源文件,生成R.java、resources.arsc和res文件
②处理.aidl文件,生成对应的Java接口文件;
③通过JavaCompiler编译R.java、Java接口文件、Java源文件,生成.class文件;
④通过dex命令,将.class文件和第三方库中的.class文件处理生成classes.dex;
5通过apkbuilder工具,将aapt生成的resources.arsc和res文件、assets文件和classes.dex一起打包生成apk;
⑥通过Jarsigner工具,对上面的apk进行debug或release签名;
⑦通过zipalign工具,将签名后的apk进行对齐处理;
66、LruCache:
技术链接:
http://www.jianshu.com/p/b49a111147ee
LRU是Least Recently Used的缩写,翻译过来就是"最近最少使用”
核心思想是当缓存满时,会优先淘汰那些近期最少使用的缓存对象;
LruCache核心思想其实就是通过LinkedHashMap维护一个缓存对象列表,其中对象列表的排列方式是按照访问顺序实现的,
即一直没访问的对象,将放在队尾,即将被淘汰。而最近访问的对象将放在队头,最后被淘汰;
LruCache中维护了一个集合LinkedHashMap,该LinkedHashMap是以访问顺序排序的;
当调用LruCache的put方法保存元素时,添加过缓存对象后,会调用trimToSize()方法,来判断缓存是否已满,
如果满了就要删除近期最少使用的算法;trimToSize()方法不断地删除LinkedHashMap中队尾的元素,
直到缓存大小小于最大值;
当调用LruCache的get()方法获取集合中的缓存对象时,就代表访问了一次该元素,将会更新队列,
保持整个队列是按照访问顺序排序。这个更新过程就是在LinkedHashMap中的get()方法中完成的。
67、drawable和bitmap的区别:
Bitmap:
位图;
Drawable:
作为Android平下通用的图形对象,它可以装载常用格式的图像;
当然还提供一些高级的可视化对象,比如渐变、图形等;
区别:
①Bitmap是Drawable;Drawable不一定是Bitmap;
②Bitmap占用内存比Drawable大;
③Bitmap绘制速度比Drawable慢;
68、插件化原理;实现插件化需要解决的技术难点:类加载、四大组件生命周期、资源隔离、构建环境;
技术链接:
http://blog.csdn.net/lmj623565791/article/details/75000580
http://blog.csdn.net/Tencent_Bugly/article/details/70577599
插件化原理:
①使用DexDexClassLoader加载插件apk:
android提供了两个主要的类加载器,PathClassLoader和DexClassLoader,它们都继承自BaseDexClassLoader。
PathClassLoader可以加载系统目录/data/app下的apk,这也意味着,它只能加载已经安装的apk;
而DexClassLoader可以加载文件系统上的jar、dex、apk文件。
所以通过DexClassLoader去加载插件apk。
②启动插件Activity及生命周期管理:
正常activity的启动是ActivityThread进程与AMS进程相互配合启动Activity,
插件中的activity没有在清单文件中注册,所以没法正常的启动插件activity。
一种做法是通过DexClassLoader加载要启动activity的字节码,然后通过反射去创建activity对象,
然后通过代理activity接管插件activity,代理activity只是个空壳,把插件activity包装进去,
代理activity持有了插件actiivty的引用,通过代理activity的各个方法中对应调用插件activity的对应方法。
另一种做法是在宿主程序清单文件中提前申明好占坑的receiver、service、activity,
在向系统请求启动的过程中用这些预先申明号的组件去做请求,
等系统的校验流程结束后换回成目标的插件组件,从而达到瞒过系统。
让插件组件完全交由系统管理,拥有真正生命周期。
activity启动的过程,最终会进入Instrumentation的execStartActivity方法,
然后再通过ActivityManagerProxy与AMS进行交互。
通过hook Instrumentation的execStartActivity方法,
根据要启动的activity,配置的action、data、category等信息去已加载的plugin中匹配到确定的Activity,
如果启动的是插件中类,则将启动的包名和Activity类名存到了intent中,
最后intent通过setClassName替换启动的目标Activity了。
再根据launchMode决定选择哪一个占坑类。
最终达到将启动的activity替换为占坑Activity,将原本启动的包名,类名存储到了Intent中。
这样就可以欺骗过AMS,AMS在处理完启动Activity后,最终会回调到Instrumentation的newActivity方法,
通过hook Instrumentation的newActivity方法,从intent中取出我们的目标Activity,
然后通过DexClassLoader去加载,这样Activity就可以正常启动了。
③插件资源文件的加载:
由于插件activity中的上下文Context是宿主程序的,插件apk中的资源无法通过正常途径加载。
由于AssetManager中的addAssetPath方法是隐藏api,所以只能通过反射去调用AssetManager中的addAssetPath方法,
该方法传递的路径可以是zip文件也可以是一个资源目录,而apk就是一个zip,
所以直接将apk的路径传给它,资源就加载到AssetManager中。
然后再通过AssetManager来创建一个新的Resources对象,通过这个新的Resources对象就可以使用插件apk中的资源了。
④卸载插件apk:
清理记录在宿主插件框架里面的信息、删除代码和资源,同时停止所有该插件正在运行的组件及服务。
插件框架:
TwsPluginFramework插件框架(腾讯):
VirtualAPK插件框架(滴滴):
69、热修复:
技术链接:
http://www.jianshu.com/p/d10aa991ca76
http://www.jianshu.com/p/5646b3b57f77
代码修复:
底层替换方案:
优缺点:
限制多,但时效最好,加载轻快,立即见效;
原理:
在已经加载了的类中直接替换掉原有的方法,是在原来类的基础上进行修改。无法实现对原有类方法和字段的增减。
类加载方案:
优缺点:
限制少,时效性差,需要重新冷启动才能见效,修复范围广;
原理:
利用android一个类只加载一次的机制,app重新启动后,在加载要替换的类之前,让ClassLoader去加载新的类。
资源修复:
①构造一个新的AssetManager,并通过反射调用addAssetPath,把这个完整的新资源包加入到AssetManager中,
这样就得到了一个含有所有新资源的AssetManager。
②找到所有之前引用到原有AssetManager的地方,通过反射,把引用处替换为新的AssetManager。
SO库修复:
本质上是对native方法的修复和替换;
把补丁so库插入到nativeLibraryDirdetories数组的最前面。
就能够达到加载so库的时候加载的是补丁so库,而不是原来so库的目录,从而达到修复的目的。
70、Hook技术:
技术链接:
http://www.jianshu.com/p/4f6d20076922
作用:
钩子就是在事件传送到终点前截获并监控事件的传输,像个钩子钩上事件一样,
并且能够在钩上事件时,处理一些自己特定的事件;
原理:
通过对Android平台的虚拟机注入与Java反射的方式,来改变Android虚拟机调用函数的方式,
从而达到Java函数重定向的目的。
71、ActivityThread和ApplicationThead关系:
ActivityThread:
在Android中它就代表了Android的主线程;
它的main方法,是整个应用进程的入口;
作用:
它管理应用进程的主线程的执行;
并根据AMS的要求,负责调度和执行activities、broadcasts和其它操作;
关系:
①ApplicationThread是ActivityThread的私有内部类,也是一个Binder对象;
②一个进程对应一个ActivityThread实例,同时,一个应用程序也对应一个ApplicationThread对象,
ApplicationThread是ActivityThread与AMS进程连接的桥梁;
72、过渡绘制:
技术链接:
http://blog.csdn.net/moyameizan/article/details/47807327
概念:
屏幕上的某个像素在同一帧的时间内被绘制了多次。
在多层次的UI结构里面, 如果不可见的UI也在做绘制的操作,这就会导致某些像素区域被绘制了多次。
比如一个TextView后有背景,那么显示文本的像素至少绘了两次,一次是背景,一次是文本。
产生原因:
①冗余的背景;
②嵌套的layout;
查看过渡绘制情况:
设置 -> 开发者选项 -> 调试GPU过度渲染–> 显示过度渲染区域
蓝色:
GPU过度绘制了1倍。像素绘制了两次。大片的蓝色还是可以接受的;
绿色:
GPU过度绘制了2倍。像素绘制了三次。中等大小的绿色区域是可以接受的但你应该尝试优化、减少它们;
淡红:
GPU过度绘制了3倍。像素绘制了四次,小范围可以接受;
深红:
GPU过度绘制了4倍。像素绘制了五次或者更多。这是错误的,要修复它们;
解决方法:
①移除Window默认的Background
getWidow.setBackgroundDrawable(null);
②移除XML布局文件中非必需的Background;
③按需求显示占位背景图片;
④减少布局嵌套;
⑤当我们在自定义View的时候,可能会出现图片重叠导致Overdraw,
当我们使用canvas.clipRect(),会提高性能,另外,在onDraw方法里面尽量不要有耗时操作;
⑥ViewStub(延迟化加载);
⑦include layout用merge标签;
73、页面卡顿:
技术链接:http://blog.csdn.net/lmj623565791/article/details/58626355
74、HttpClient和HttpURLConnection:
技术链接:
http://blog.csdn.net/lb_fighting/article/details/53317799
在2.2系统之前,HttpURLConnection有个重大Bug,调用close()函数会影响连接池,导致连接复用失效,
2.3系统,HttpURLConnection禁用连接池,默认开启了gzip压缩,提高了HTTPS的性能,
4.0系统HttpURLConnection支持了请求结果缓存;另HttpURLConnection本身API相对简单,
对于提高速度和节省电池有帮助,同时谷歌也愿意在这方面花时间研究去更进一步的提高性、Android来说,在2.3后建议使用HttpURLConnection,之前建议使用HttpClient;
75、RecylerView:
技术链接:
http://blog.csdn.net/lmj623565791/article/details/45059587
JAVA相关:
多线程(线程池、生命周期,打断方式),锁,https,jvm内存(垃圾回收算法),堆栈,Classloader。
算法还可以记录一波: