(一)Android基础知识点
1、四大组件是什么
Activity,Service,ContentProvider,BroadcastReceiver
2、四大组件的生命周期和简单用法
Activity的生命周期:onCreate()->onStart()->onResume()->onPause()->onStop()->onRestart()->onDestroy()
用法:创建一个Intent对象,将该Activity以及将要打开的Activity传进去,然后Intent对象将代入startActivity()即可启动
Service的生命周期:
(1)onCreate()->onStart()->onDestroy()
(2)onCreate()->onBind()->onUnBind()->onRebind()->onDestroy()
用法:
(1)通过简单的startService()进行service启动,此后启动该Service的组件无法把控Service的生命周期,理论上此后该Service可以在后台无期限运行,但根据实际情况该Service可能会在任意一个时刻被杀死
我们可以在onStartCommand()里面做我们要做的操作,虽然运行ANR(Application No Response) 时间比Activity多了近一倍,但Service跟Activity一样不可以做耗时操作
(2)通过绑定的方式启动Service
绑定后,该Service与启动绑定操作的组件形成绑定,当组件销毁时,该Service也随着销毁
ContentProvider:
提供数据的进程使用contentProvider内容提供者
获取数据的进程使用contentResolver内容解析器
BroadcastReceive广播接收器生命周期:
生命周期只有十秒左右,如果在 onReceive() 内做超过十秒内的事情,就会报ANR程序无响应的错误信息
它的生命周期为从回调onReceive()方法开始到该方法返回结果后结束
用法:
(1)静态注册(常驻广播)
在AndroidManifest.xml中进行注册,App启动的时候自动注册到系统中,不受任何组件生命周期影响,(即便应用程序已经关闭),但是耗电,占内存
(2)动态注册(非常驻广播)
在代码中进行注册,通过IntentFilter意图过滤器筛选需要监听的广播,记得注销(推荐在onResume()注册,在onPause()注销),使用灵活,生命周期随组件变化
3、Activity之间的通信方式
(1)在Intent跳转时携带数据
- 在Intent里直接赋值
- 使用Bundle传值
使用Bundle传值对比在Intent里直接赋值的好处:
- 减少重复赋值操作(需要传递多次)
- 除了基本类型,还可以传实现Serializable 接口的对象
(2)借助类的静态变量来实现
由于类的静态成员可以通过“className.fileName”来访问,故而可以供两个Activity访问从而实现Activity之间的数据通信
(3)借助全局变量 Application 来实现
和类的静态变量类似,但是这个类作为单独第三个类(最好是写一个Application类)
(4)借助Service服务
Service设计的本意,就是提供一些后台的服务,数据存取,也可以归于其职责的一部分
Service是提供了直连机制,调用的Activity,可以通过bindService方法,与目标Service建立一条数据通路,拿到IBinder。这样,通过Android提供的IPC模型(进程间通信),就可以进行远程方法的调用和数据的传输了
通过这种模式,可以解决一定问题,但是对于Service来说,实在是太大才小用了,Service的专长,不是在数据,还是在逻辑。对于传数据而言,Service还是重量了一点,不但是有连接耗精力,传输经由IPC,写起来也够费劲。而且作为组件,Service随时可能死掉,你还是要费劲心机的处理数据的持久化,得不偿失
(5)借助外部存储来实现通讯
Ⅰ.借助 SharedPreference来实现
SharedPreferences是Android平台上一个轻量级的存储辅助类,用来保存应用的一些常用配置,它提供了string,set,int,long,float,boolean六种数据类型。最终数据是以xml形式进行存储。在应用中通常做一些简单数据的持久化缓存
Ⅱ.借助 SQLite 来实现
SQLite是一款轻量级的关系型数据库,它的运算速度非常快,占用资源很少,在存储大量复杂的关系型数据的时可以使用,比前面学过的只适用于存储简单数据的两种存储方式要好很多
Ⅲ.借助 Flie 来实现
文件存储方式是一种较常用的方法,在Android中读取/写入文件的方法,与Java中实现I/O的程序是完全一样的,提供openFileInput()和openFileOutput()方法来读取设备上的文件
4、Activity各种情况下的生命周期
(1)两个Activity(A->B)切换(B正常的Activity)的生命周期:onPause(A)->onCreate(B)->onStart(B)->onResume(B) ->oStop(A)
这时如果按回退键回退到A:onPause(B)->onRestart(A)->onStart(A)->onResume(A)->oStop(B)
如果在切换到B后调用了A.finish(),则会走到onDestory(A),这时点回退键会退出应用
(2)两个Activity(A->B)切换(B透明主题的Activity或是Dialog风格的Acivity)的生命周期:onPause(A)->onCreate(B) ->onStart(B)->onResume(B)
这时如果回退到A: onPause(B)->onResume(A)->oStop(B)->onDestory(B)
(3)Activity(A)启动后点击Home键再回到应用的生命周期:onPause(A)->oStop(A)->onRestart(A)->onStart(A) ->onResume(A)
(4)横竖屏切换的时候,Activity 各种情况下的生命周期
- 切换横屏时:onPause->onSaveInstanceState->onStop->onDestory->onCreate->onStart->onRestoreInstanceState ->onResume
- 切换竖屏时:onPause->onSaveInstanceState->onStop->onDestory->onCreate->onStart->onRestoreInstanceState ->onResume
- 如果在AndroidMainfest.xml中修改该Activity的属性,添加android:configChanges="orientation": onPause->onSaveInstanceState->onStop->onDestory->onCreate->onStart->onRestoreInstanceState ->onResume
-
如果AndroidMainfest.xml中该Activity中的android:configChanges="orientation|keyboardHidden",则只会打印onConfigurationChanged
(5)Activity上有Dialog的时候按Home键时的生命周期
AlertDialog并不会影响Activity的生命周期,按Home键后才会使Activity走onPause->onStop,AlertDialog只是一个组件,并不会使Activity进入后台
AlertDialog和Toast是通过WindowManager.addView()
来显示的,AlertDialog和Toast可以看做是当前Activity的一部分View
5、Activity与Fragment之间生命周期比较
Activity:
(1)onCreate():Activity创建界面时,调用此方法
(2)onStart():Activity界面可见时,调用此方法
(3)onResume():界面获得焦点可以和用户可交互时,调用此方法
(4)onPause(): 界面可见(变为半透明或弹出对话框)但失去焦点不可以和用户交互,调用此方法
(5)onStop(): 界面完全不可见时 ,调用此方法
(6)onDestroy(): Activity被销毁时,调用此方法
(7)onRestart():界面由不可见变为可见时,调用此方法和onStart()方法
Fragment:
(1)onAttach():Fragment和Activity关联时,调用此方法
(2)onCreate():创建Fragment时,调用此方法
(3)onCreateView():加载Fragment的ui布局时,调用此方法
(4)onActivityCreated():Activity的onCreate方法完成时,调用此方法
(5)onStart(): Fragment和Activity一起启动且可见时,调用此方法
(6)onResume():Fragment获取焦点时,调用此方法
(7)onPause():Fragment失去焦点但可见,调用此方法
(8)onStop():Fragment完全不可见时,调用此方法
(9)onDestroyView():Fragment布局被移除时,调用此方法
(10)onDestroy():Fragment被销毁时,调用此方法
(11)onDetach():Fragment和Activity解除关联,调用此方法
6、Activity的四种启动模式对比
standard(标准模式-默认)
特点:(1)Activity的默认启动模式
(2)每启动一个Activity就会在栈顶创建一个新的实例
缺点:当Activity已经位于栈顶时,再次启动Activity还需要再创建一个新的实例,不能直接复用
singleTop(栈顶复用模式)
特点:该模式会判断要启动的Activity实例是否位于栈顶,如果位于栈顶直接复用,否则创建新的实例
缺点:如果Activity并未处于栈顶位置,则可能还会创建多个实例
singleTask(栈内复用模式)
特点:使Activity在整个应用程序中只有一个实例。每次启动Activity时系统首先检查栈中是否存在当前Activity实例,如果存在
则直接复用,并把当前Activity之上所有实例全部出栈
singleInstance(全局唯一模式)
singleInstance模式也是单例的,但和singleTask不同,singleTask只是任务栈内单例,系统里是可以有多个singleTask Activity实例的,而singleInstance Activity在整个系统里只有一个实例,启动一singleInstance Activity时,系统会创建一个新的任务栈,并且这个任务栈只有他一个Activity
singleInstance模式并不常用,如果我们把一个Activity设置为singleInstance模式,你会发现它启动时会慢一些,切换效果不好,影响用户体验。它往往用于多个应用之间,例如一个电视launcher里的Activity,通过遥控器某个键在任何情况可以启动,这个Activity就可以设置为singleInstance模式,当在某应用中按键启动这个Activity,处理完后按返回键,就会回到之前启动它的应用,不影响用户体验
7、Fragment状态保存startActivityForResult是哪个类的方法,在什么情况下使用
Fragment和Activity里都有各自的startActivityForResult方法
Fragment和FragmentActivity都能接收到自己的发起的请求所返回的结果
FragmentActivity发起的请求,Fragment完全接收不到结果
Fragment发起的请求,虽然在FragmentActivity中能获取到结果,但是requestCode完全对应不上
如果希望在Fragment的onActivityResult接收数据,就要调用Fragment.startActivityForResult,而不是Fragment.getActivity().startActivityForResult,且requestCode一定不要大于0xffff(65535)
8、如何实现Fragment的滑动
(1)首先把Fragment,Viewpager都实例化
(2)把Fragment添加到泛型ArrayList里
(3)把带有Fragment的ArrayList和Adapter绑定
(4)Viewpager和Adapter进行绑定
9、Fragment之间传递数据的方式
(1)创建Fragment的需要添加tag(标签),然后在发送数据的FragmentA中根据tag找到接收数据的FragmentB,调用FragmentB中接收数据的方法
(2)利用接口回调,在FragmentB中创建一个接口,并且在触发FragmentA中事件的的时候如果有实现该接口的类,将产生回调
(3)利用第三方开源库EventBus,EventBus是一种用于线程间通信的封装类,用来代替Handler、BroadCastReceiver、Intent等在Activity、Service、Fragment之间传递数据,内部基本是基于观察者模式对被观察者进行实时监听和传递数据
10、Service和Activity怎么进行数据交互
(1)通过BroadcastReceiver
缺点:
- 系统会优先发送系统级广播,在某些特定的情况下,我们自定义的广播可能会延迟
- 广播接收器中不能处理长耗时操作,否则系统会出现ANR即应用程序无响应
(2)共享文件交互
优缺点分析:这种方式实现Activity与Service的交互,可以说很方便,就像使用管道,一个往里写,一个往外读。但这种方式也有缺陷,写入数据较为复杂以及数据量较大时,就有可能导致写入与读数据出不一致的错误。同时因为经过了一个中转站,这种操作将更耗时
(3)接口回调(Binder和ServiceConnection)
Server端用一个类继承自Binder并实现该接口。这里Binder就是「接口」(IBinder的实现)。Activity中通过Binder获取Service的实例,在ServiceConection中set一个listener到Service里,Service再回调数据给Activity
缺陷:不能进行跨进程通信
(4)Messenger
Android里大部分的跨进程的IPC都是基于Binder实现。这种方式是客户端和Service分别创建一个Messenger,用Handler发送msg
优缺点分析:通过Messenger来实现Activity和Service的交互,其实Messenger也是通过AIDL来实现的。对于前两种实现方式,Messenger方式总体上来讲也是比较容易理解的,这就和平时使用Handler和Thread通信一个道理
(5)AIDL
原理: AIDL属于Android的IPC机制,常用于跨进程通信,主要实现原理基于底层Binder机制。
优缺点分析:AIDL在Android中是进程间通信常用的方式,可能使用较为复杂,但效率高,扩展性好。同时很多系统服务就是以这种方式完成与应用程序通信的
11、谈谈你对ContentProvider的理解
ContentProvider是Android的四大组件之一,底层采用的是Binder机制,用于Android的进程间通信,使得数据可以跨APP传输和共享
12、说说ContentProvider、ContentResolver、ContentObserver 之间的关系
ContentPRrovider
- 四大组件的内容提供者,主要用于对外提供数据
- 实现各个应用程序之间的(跨应用)数据共享,比如联系人应用中就使用了ContentProvider,你在自己的应用中可以读取和修改联系人的数据,不过需要获得相应的权限。其实它也只是一个中间人,真正的数据源是文件或者SQLite等
- 一个应用实现ContentProvider来提供内容给别的应用来操作,通过ContentResolver来操作别的应用数据,当然在自己的应用中也可以
ContentResolver
- 内容解析者,用于获取内容提供者提供的数据
- ContentResolver.notifyChange(uri)发出消息
ContentObserver
- 内容监听器,可以监听数据的改变状态
- 目的是观察(捕捉)特定Uri引起的数据库的变化,继而做一些相应的处理,它类似于数据库技术中的触发器(Trigger),当ContentObserver所观察的Uri发生变化时,便会触发它。触发器分为表触发器、行触发器,相应地ContentObsever也分为表ContentObserver、行ContentObserver,当然这是与它所监听的Uri MIME Type有关的
- ContentResolver.registerContentObserver()监听消息
联系:使用ContentResolver来获取ContentProvider提供的数据,同时注册ContentObserver监听Uri数据的变化
13、本地广播和全局广播有什么差别
(1)BroadcastReceiver是针对应用间、应用与系统间、应用内部进行通信的一种方式,LocalBroadcastReceiver仅在自己的应用内发送接收广播,也就是只有自己的应用能收到,数据更加安全,效率更高
(2)BroadcastReceiver可以静态注册,也可采用动态注册的方式,LocalBroadcastReceiver不能静态注册,只能采用动态注册的方式
14、AlertDialog,PopupWindow,Activity区别
AlertDialog是非阻塞式对话框,AlertDialog弹出时,后台还可以做事情
PopupWindow是阻塞式对话框,PopupWindow弹出时,程序会等待,在PopupWindow退出前,程序一直等待,只有当我们调用了dismiss方法的后,PopupWindow退出,程序才会向下执行
Activity是Android系统中的四大组件之一,是一个用于与用户交互的系统模块
15、Application 和 Activity 的 Context 对象的区别
(1)生命周期不同,Application的生命周期即App的生命周期,通常比Activity的生命周期长的多,所以对于生命周期长的对象,一般使用Application作为context,避免不恰当的持有Activity造成内存泄漏
(2)Application 不能showDialog
(3)Application startActivity 时,必须new一个Task
(4)Application在layoutInflate 时,直接使用默认主题,可能与当前主题不一样
16、Android属性动画特性
- 作用对象进行了扩展:不只是View对象,甚至没对象也可以
- 动画效果:不只是4种基本变换,还有其他动画效果
- 作用领域:API11后引入的
17、LinearLayout、RelativeLayout、FrameLayout的特性及对比,并介绍使用场景
(1)RelativeLayout会让子View调用2次onMeasure,LinearLayout 在有weight时,也会调用子View2次onMeasure
注:RelativeLayout中子View的排列方式是基于彼此的依赖关系的,在确定每个子View的时候,需要先给所有的子View排序一下,所以需要横向纵向分别调用一次排序测量
如果LinearLayout中不适用weight(权重)属性,将只进行一次measure的过程
如果使用了weight属性,LinearLayout将在第一次测量时获取获取所有子View的所占据的空间,之后再将剩余的空间根据weight加到weight>0的子View上
(2)RelativeLayout的子View如果宽高和RelativeLayout不同,则会引发效率问题,当子View很复杂时,这个问题会更加严重。如果可以,尽量使用padding代替margin
(3)在不影响层级深度的情况下,使用LinearLayout和FrameLayout而不是RelativeLayou
(4)需要使用两层LinearLayoutd的时候,尽量用一个RelativeLayout来减少层级,此时RelativeLayout在时间上耗时更小
18、回调的原理
接口调用自己的抽象方法,相当于接口的实现类调用实现类中重写的抽象方法
通俗理解就是被调用者返回去调用调用者的方法(A问B一个问题,B给A回答了问题,然后A对B的回答进行了判断)
19、介绍下SurfaceView
SurfaceView是一个在表层的View对象,它有自己的生命周期。它跟其他View不一样,其他View是绘制在“表层”的上面,而它就是充当“表层”本身。
SurfaceView需要另外一个线程来执行绘制操作,所以我们可以在它生命周期的初始化阶段开辟一个新线程,然后开始执行绘制,当生命周期的结束阶段我们插入结束绘制线程的操作。这些是由其内部一个SurfaceHolder对象完成的。SurfaceView它的绘制原理是绘制前先锁定画布(获取画布),然后等都绘制结束以后在对画布进行解锁 ,最后在把画布内容显示到屏幕上。
SurfaceView相当于是另一个绘图线程,它不会阻碍主线程,并且它在底层实现机制中实现了双缓冲机制
20、序列化的作用,以及Android两种序列化的区别
作用:(1)方便传输,速度快,还很安全,被调用方序列化,调用方反序列化即可拿到传输前最原始的java对象,常用于不同进程之间的对象传输
(2)方便存储,不管是存储成文件还是数据库都可以。存储为文件,需要时可以直接反序列拿到对象
区别:(1)Serializable是属于JAVA的API,Parcelable是属于Android SDK的API
(2)Serializable序列化和反序列化过程需要大量的I/O操作,Parcelable序列化和反序列化过程不需要大量的I/O操作
(3)Serializable内存开销大,Parcelable内存开销小(Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC)
(4)Serializable效率低,Parcelable效率高
(5)Serializable适用于序列化到本地或者通过网络传输,Parcelable适用于内存序列化(Parcelable不能使用在要将数据存储在磁盘上的情况,因为Parcelable在外界有变化的情况下,不能很好的保证数据的持续性)
21、差值器(Interpolator)
定义:一个接口
作用:设置从初始值过渡到结束值的变化规律(匀速,加速,减速等)
应用场景:实现非线性运动的动画效果
22、估值器(TypeEvaluator)
定义:一个接口
作用:设置从初始值过渡到结束值的变化具体数值
应用场景:协助插值器实现非线性运动的动画效果
23、Android中数据存储方式
(1)使用SharedPreferences存储数据
保存基于XML文件存储的key-value键值对数据
适用范围:保存少量的数据,且这些数据的格式非常简单(字符串型、基本类型的值)。比如应用程序的各种配置信息(如是否打开音效、是否使用震动效果、小游戏的玩家积分等),解锁口令密码等
(2)文件存储数据
Context提供了两个方法来打开数据文件里的文件IO流
(3)SQLite存储数据
SQLite是轻量级嵌入式数据库引擎,它支持 SQL 语言,并且只利用很少的内存就有很好的性能
(4)ContentProvider
ContentProvider是一种内容共享型组件,它通过Binder向其他组件乃至其他应用提供数据。当ContentProvider所在的进程启动时,ContentProvider会同时启动并被发布到AMS中。需要注意的是,这个时候ContentProvider的onCreate要先于Application的onCreate而执行,这在四大组件中是一个少有的现象。系统预置了许多ContentProvider,比如通讯录信息、日程表信息等
(5)网络存储数据
(二)Android源码相关分析
1、Android各个版本API的区别
API等级19(Android 4.4 KitKat)
(1)READ_EXTERNAL_STORAGE权限改动
需要申请READ_EXTERNAL_STORAGE权限,无法读取外部存储空间上的共享文件
(2)沉浸式全屏
应用提供填充整个屏幕的布局,系统状态栏将会隐藏,用户可以沿着系统状态栏正常出现的区域向内滑动来显示系统状态栏
API等级21:(Android 5.0.1 L Lollipop棒棒糖)
(1)支持64位ART虚拟机
新系统放弃了Dalvik虚拟机,改用ART虚拟机,ART 虚拟机编译器在内存占用及应用程序加载时间上进行了大幅提升
(2)Material Design
扁平化的设计理念,一系列立体的动画,动态的控件(CardView,RecycleView,AppBarLayout等)
(3)支持多种设备
无论是智能手机、平板电脑、笔记本电脑、智能电视、汽车、智能手表甚至是各种家用电子产品,谷歌的Android系统已经可以在所有设备的屏幕上出现
API等级23:Android 6.0 M Marshmallow
(1)新增运行时权限概念
系统默认给app授权部分基础权限,当涉及敏感权限时,开发者需要手动请求系统授予权限
(2)新增瞌睡模式(Doze)和待机模式(Standby)
瞌睡模式:当不碰手机,手机自动关闭屏幕后,过一会,手机将进入瞌睡模式。在瞌睡模式下,设备只会定期的唤醒,然后继续执行等待中的任务接着又进入瞌睡
待机模式:假如用户一段时间不触碰手机,设备将进入待机模式。在这个模式下,系统会认为所有app是闲置的,这时系统会关闭网络,并且暂停app之前正在执行的任务
(3)移除对Apache HTTP Client的支持,建议使用HttpURLConnection
API等级24: Android 7.0 N Preview
(1)分屏多任务
进入后台多任务管理页面,然后按住其中一个卡片,然后向上拖动至顶部即可开启分屏多任务,支持上下分栏和左右分栏,允许拖动中间的分割线调整两个APP所占的比例
(2)通知栏快捷回复
用户可以在通知栏上直接对通知进行回复
(3)引入全新的JIT编译器
对ART进行代码分析,安装提速并且所占空间减少(App安装速度快了75%,编译代码的规模减少了50%)
API等级26: Android 8.0 O
(1)画中画
(2)自动填充(Auto-Fill)
对于用户设备上最常用的应用,Android O将会帮助用户进行快速登录,而不用每次都填写账户名和密码
(3)后台进程限制
当应用被置入后台后,Android O将自动智能限制后台应用活动,主要会限制应用的广播、后台运行和位置,但应用的整体进程并没有被杀掉
(4)自适应图标(Adaptive icons)
如果你的手机默认应用程序图示形状是圆角正方形(或其他形状),那么所有应用程序的图标都将是这个形状(前提是开发人员使用了这一功能)
(5)运行时权限策略变化
- 在 Android O 之前,如果应用在运行时请求权限并且被授予该权限,系统会错误地将属于同一权限组并且在清单中注册的其他权限也一起授予应用。
- 对于针对Android O的应用,此行为已被纠正。系统只会授予应用明确请求的权限。然而一旦用户为应用授予某个权限,则所有后续对该权限组中权限的请求都将被自动批准。
例如,假设某个应用在其清单中列出READ_EXTERNAL_STORAGE
和WRITE_EXTERNAL_STORAGE
。应用请求READ_EXTERNAL_STORAGE
,并且用户授予了该权限,如果该应用针对的是API级别24或更低级别,系统还会同时授予WRITE_EXTERNAL_STORAGE
,因为该权限也属于STORAGE权限组并且也在清单中注册过。如果该应用针对的是Android O,则系统此时仅会授予READ_EXTERNAL_STORAGE
,不过在该应用以后申请WRITE_EXTERNAL_STORAGE
权限时,系统会立即授予该权限,而不会提示用户
API等级28: Android 9.0 P
(1)刘海屏支持
(2)多摄像头支持
可以同时获取多个视频流
(3)提供了人工智能的API,整合形成“MLKit”
API等级29: Android 10.0 Q
(1)深色模式
(2)手势导航
引入手势导航后,应用程序不仅可以实现全屏幕的内容显示,还能最大限度地减少系统导航键的可见程度,这对于当下主流的全面屏手机尤为重要
2、requestlayout和onlayout,onDraw和drawChild的区别与联系
requestLayout()
调用时机:当前View发生了一些改变,这个改变使得现有的View失效时
调用requestLayout()方法会对View树进行重新布局,过程包括了measure()和layout()过程,但不会调用draw()过程,即不会发生重新绘制视图过程
onLayout()
调用时机:ViewGrounp需要子View设置大小和位置时
onDraw()
调用时机:每个view都要重载该方法绘制自身,viewGroup仅在设置背景时调用,否则不调用该方法
drawChild()
调用时机:viewGroup会调用dispatchDraw绘制子view,该方法中会调用drawChild()方法去重新回调子每个view的draw()方法(draw()方法中会调用onDraw()方法绘制view的内容)
3、invalidate和postInvalidate的区别及使用
区别:invalidate在主线程中调用,postInvalidate在子线程中调用(底层通过handler来调用View的Invalidate())
使用:invalidate在主线程中直接调用,若在子线程中进行刷新view的操作需要配合handler来使用,postInvalidate()可以在子线程中直接调用
4、Activity-Window-View三者的差别
Activity是安卓四大组件之一,负责界面展示、用户交互与业务逻辑处理(控制单元)
Window就是负责界面展示以及交互的职能部门,就相当于Activity的下属(承载模型)
View就是放在Window容器的元素,Window是View的载体,View是Window的具体展示(显示视图)
Activity通过Window来实现视图元素的展示,Window可以理解为一个容器,盛放着一个个的View,用来执行具体的展示工作
5、谈谈对Volley的理解
优点:(1)非常适合进行数据量不大,但通信频繁的网络操作(存储空间是内存中分配的,当存储的时候会先从ByteArrayPool中取出一块已经分配的内存区域, 不必每次存数据都要进行内存分配,而是先查找缓冲池中有无适合的内存区域,如果有,直接拿来用,从而减少内存分配的次数)
(2)可直接在主线程调用服务端并处理返回结果
(3)可以取消请求,容易扩展,面向接口编程
(4)网络请求线程NetworkDispatcher默认开启了4个,通过手机CPU数量可以进行优化优化
(5)通过使用标准的HTTP缓存机制保持磁盘和内存响应的一致
缺点:(1)使用的是HttpClient、HttpURLConnection
(2)6.0不支持HttpClient了,如果想支持得添加org.apache.http.legacy.jar
(3)对大文件下载 Volley的表现非常糟糕(采用了ByteArrayPool进行内存中的数据存储,下载大量的数据,这个存储空间就会溢出)
(4)只支持http请求
(5)图片加载性能一般
6、低版本SDK如何实现高版本API
(1)添加注解 @TargetApi(Build.VERSION_CODES.XXX)
不管项目的minSdkVersion是多少,被注解元素的的minSdk指定为特定版本,以跳过lint检查
(2)添加注解 @RequiresApi(Build.VERSION_CODES.XXX)
被注解的元素只能在被给的 API 版本或更高情况下才能被调用
(3)添加注解 @SuppressLint(“NewApi”)
Lint 静态检测工具将会忽略被注解元素的警告
(4)添加运行时SDK版本判断
判断为低版本时添加其他方法实现该功能
7、描述一次网络请求的流程
(1)通过URL找IP(域名解析)
- 首先查找本地的缓存,一般是以hosts文件的形式存在,维持着一个带域名的服务器地址和IP的对应关系,路由器缓存(也算是DNS服务器缓存)
- 如果没有结果,则会向上层DNS服务器询问,上层DNS服务器的本地缓存中如果没有该记录,则再向上层询问,一直到DNS根服务器
-
在根域名服务器中虽然没有每个域名的具体信息,但储存了负责每个域(如COM、NET、ORG等)的解析的域名服务器的地址信息。根域名服务器会将其管辖范围内顶级域名(如)服务器IP告诉本地DNS服务器,域名查询请求会进入到相应的顶级域名服务器。顶级域名服务器收到请求后查看区域文件记录,若找到则将其管辖范围内主域名(不带任何前缀的域名,如 baidu)服务器的IP地址告诉本地DNS服务器。如果还是没有找到,则进入到下一级域名服务器进行查找。如此重复,直到找到正确的 结果为止,返回 IP地址结果给本地DNS服务器
-
本地DNS服务器缓存结果,设置(Time-To-Live)即一条域名解析记录在DNS服务器上缓存时间,关于TTL如果IP经常改变,那么TTL设的短一点长一点都没有太大的 影响,而如果IP经常不变,可以把TTL时间拉长,这样有利于提高命中率
(2)对IP结果建立TCP连接
自己主机IP端口对目标IP端口建立三次握手TCP连接
(3)向服务器发送数据
浏览器将网络请求封装成HTTP报文,把HTTP报文通过TCP的分包,分成一个个TCP数据包。IP层把上层传输层数据包打包成IP层数据包,并把该数据包发送到更低层数据链路层,相反,IP层也把从低层接收来的数据包传送到更高层TCP或UDP层。(补:IP数据包是不可靠的,因为IP并没有做任何事情来确认数据包是否按顺序发送的或者有没有被破坏,IP数据包中含有发送它的主机的地址(源地址)和接收它的主机的地址(目的地址))
(4)服务器解析,并返回
对HTTP报文进行解析,根据HTTP报文决定它请求了什么。如果状态码校验成功,将响应报文,通过之前的过程返还主机IP
(5)浏览器解析HTML
浏览器加载显示html的顺序是:从上到下,渲染的顺序也是从上到下,下载和渲染是同时进行的。如果遇到语义解释性的标签嵌入文件(JS脚本,CSS ),下载过程会启用单独连接进行下载,并且在下载后进行解析,解析过程中,停止页面所有往下元素的下载(断点)。最终加载完成显示在浏览器上
8、HttpUrlConnection 和 okhttp关系
Android4.4之后的HttpUrlConnection 的实现是基于okhttp
9、Bitmap对象的理解
Bitmap(位图):可以看作一个画架,进行获取图像文件信息,旋转切割,放大缩小等操作
10、looper架构
Looper是Android为线程间异步消息通信提供的一种机制,利用Looper机制可以方便我们实现多线程编程时线程间的相互沟通。Looper的实现是利用消息队列的方式,为用户封装了一层API,用户不需要考虑互斥加锁的问题,方便用户的使用
Looper构造方法中创建了一个MessageQueue,且保存了此MessageQueue的引用,此MessageQueue属于Looper所在的线程
loop()是一个死循环,不断的从当前线程的MessageQueue中取数据,无消息时进入阻塞状态,释放资源进入休眠,获取到新消息后,唤醒线程,通过handleMessage将消息发送给目标Handler
11、ActivityThread,AMS(ActivityManagerService),WMS(WindowManagerService)的工作原理
ActivityThread就是我们常说的主线程或UI线程,ActivityThread的main方法是整个APP的入口
AMS统一调度所有应用程序的Activity
WMS控制所有Window的显示与隐藏以及要显示的位置
(1)应用程序在Activity中添加、删除窗口。具体实现就是通过调用WindowManager类的addView()和removeView()函数完成,转而调用ViewRoot类的相关方法,然后通过IPC调用到Wms中的相关方法完成添加、删除过程
(2)AMS通知ActivityThread销毁某个Actviity时,ActivityThread会直接调用WindowManager中的removeView()方法删除窗口
(3)AMS中直接调用WMS,告诉WMS一些信息,比如某个新的Activity要启动了,从而WMS保存一个该Activity记录的引用
(4)在WMS内部,全权接管输入消息的处理和屏幕的绘制。输入消息由InputManager类完成,屏幕绘制由SurfaceFlinger模块完成,SurfaceFlings是Linux的一个驱动,内部使用芯片的图形加速引擎完成界面的绘制
12、自定义View如何考虑机型适配
(1)合理使用warp_content,match_parent
(2)尽可能的使用RelativeLayout
(3)针对不同的机型,使用不同的布局文件放在对应的目录下,android会自动匹配
(4)尽量使用点9图片
(5)使用与密度无关的像素单位dp,sp
(6)引入android的百分比布局
(7)切图的时候切大分辨率的图,应用到布局当中。在小分辨率的手机上也会有很好的显示效果
13、LaunchMode应用场景
singleInstance:应用于大多数的Activity
singleTop:假设在当前的Activity中又要启动同类型的Activity,此时建议将此类型Activity的启动模式指定为SingleTop,能够降低Activity的创建,节省内存
singleTask:保持我们应用开启后仅仅有一个Activity的实例。最典型的样例就是应用中展示的主页(Home页)
singleInstance:经常使用于系统中的应用,比如Launch、锁屏键的应用。闹钟的提醒页面,当你在A应用中看视频时,闹钟响了,你点击闹钟提醒通知后进入提醒详情页面,然后点击返回就再次回到A的视频页面,这样不会过多干扰到用户先前的操作
14、SpareArray原理
SparseArray采用两个数组,用来存放key以及value值的,核心思想是通过折半查找来找到key对应的位置,然后插入值,或者取出值
SparseArray采用时间换取空间的方式来提高手机App的运行效率,通过二分查找法每次插入的时候会找到最合适的位置顺序插入,保证key在数组中是有序的,取出也是类似。时间复杂度是log(n),比较高效,SparseArray在大量数据,千以上时,会效率较低
15、IntentService原理及作用是什么
原理:IntentService第一次启动时会调用onCreate方法,创建一个HandlerThread对象,并开启HandlerThread线程,然后使用HandlerThread的Looper对象初始化mServiceHandler对象,通过mServiceHandler发送消息,并在HandlerThread中执行操作,所以虽然IntentService也是Service,但是可以执行耗时操作。每次启动IntentService都会调用onStartCommand()方法,onStartCommand方法会调用onStart方法
onStart方法中调用mServiceHandler发送了一个消息,将外界通过startIntent(intent)传入的intent,交给HandlerThread处理,startId是当前IntentService的启动次数
onHandleIntent是一个抽象方法,在子类中实现具体功能,该方法执行完以后会尝试调用stopSelf(startId)方法来终止服务。(stopSelf(int startId)与stopSelf()的区别:stopSelf()直接停止;stopSelf(startId)只有在startId与最后启动该service时生成的startId相等时才会执行停止服务,即所有消息都处理完成)。因为IntentService每启动一次都会发送一个消息请求HandlerThread执行,Looper是顺序处理消息的,这就意味着IntentService执行多个任务时也是顺序执行的
16、说说Activity、Intent、Service 是什么关系
Activity负责用户界面的显示和交互,Service负责后台任务的处理。Activity启动Service需要Intent表明跳转的意图,以及传递参数,Intent是这些组件间信号传递的承载者
17、SP是进程同步的吗?有什么方法做到同步
一个进程的情况,经常采用SharePreference来做,但是SharePreference不支持多进程,它是基于单个文件的,默认是没有考虑同步互斥,而且,APP对SP对象做了缓存,不好互斥同步
ContentProvider基于Binder,不存在进程间互斥问题,对于同步,也做了很好的封装,不需要开发者额外实现。
另外ContentProvider的每次操作都会重新getSP,保证了sp的一致性
18、谈谈多线程在Android中的使用
(1)Handler+Thread
Android主线程包含一个消息队列(MessageQueue),该消息队列里面可以存入一系列的Message或Runnable对象。通过一个Handler你可以往这个消息队列发送Message或者Runnable对象,并且处理这些对象。每次新创建一个Handle对象,它会绑定于创建它的线程(也就是UI线程)以及该线程的消息队列,从这时起,这个handler就会开始把Message或Runnable对象传递到消息队列中,并在它们出队列的时候执行它们
优缺点:
- Handler用法简单明了,可以将多个异步任务更新UI的代码放在一起,清晰明了
- 处理单个异步任务代码略显多
适用范围:多个异步任务的更新UI
(2)AsyncTask
AsyncTask是Android提供的轻量级的异步类,可以直接继承AsyncTask,在类中实现异步操作,并提供接口反馈当前异步执行的程度(可以通过接口实现UI进度更新),最后反馈执行的结果给UI主线程
优缺点:
- 处理单个异步任务简单,可以获取到异步任务的进度
- 可以通过cancel方法取消还没执行完的AsyncTask
- 处理多个异步任务代码显得较多
适用范围:单个异步任务的处理
(3)ThreadPoolExecutor
ThreadPoolExecutor提供了一组线程池,可以管理多个线程并行执行。这样一方面减少了每个并行任务独自建立线程的开销,另一方面可以管理多个并发线程的公共资源,从而提高了多线程的效率。所以ThreadPoolExecutor比较适合一组任务的执行。Executors利用工厂模式对ThreadPoolExecutor进行了封装,使用起来更加方便
适用范围:批处理任务
(4)IntentService
IntentService继承自Service,是一个经过包装的轻量级的Service,用来接收并处理通过Intent传递的异步请求。客户端通过调用startService(Intent)启动一个IntentService,利用一个work线程依次处理顺序过来的请求,处理完成后自动结束Service
特点:一个可以处理异步任务的简单Service
19、封装View的时候怎么知道view的大小
(1)如果测量的是一个View,可以getDefaultSize()方法来获取测量宽高
(2)如果是多个View或者ViewGroup嵌套,需要循环遍历视图中的所有View
20、RecycleView缓存原理
(1)scrap
当调用notifyXXX相关的代码的时候会将当前屏幕的ViewHolder缓存到scrap List数组中
分为mChangedScrap与mAttachedScrap两个缓存
mChangedScrap表示item变化了,有可能是数据变化,有可能是类型变化,所以它的viewHolder无法重用,会被移动到RecycledViewPool中,所以mChangedScrap对应的item需要从pool中取对应的viewHolder,然后重新绑定
mAttachedScrap存放数据未产生变化的item,可以直接使用,不需重新绑定
mChangedScrap只能在pre-layout中使用,mAttachedScrap可以在pre-layout与post-layout中使用
注:pre-layout,因为可以收到适配器的变化,所以这里可以做一些特殊的处理,对应于修改前的布局(例如屏幕中有A,B两个item,itemC在屏幕外,在进行删除B的操作时,会额外的显示出C,即使C已经超出屏幕的界限)
post-layout,一个正常的布局,对应于更改后的适配器状态
(2)cacheViews
不区分viewHolder的类型,大小默认限制为2,如果数组满了,按照FIFO的规则移除ViewHolder,通常情况下刚移除屏幕的viewholder并不会马上删除,而是存在cacheViews数组中,再次调用不需要绑定数据
(3)ViewCacheExtsension
自定义的缓存策略,使用有很大的限制,不建议使用
使用限制:
需要自己创建viewHolder,并将它缓存起来。当我们删除或添加一个item的时候,AdapterHelper回调RecyclerView通知它需要处理变化。RecyclerView会遍历当前显示的viewHolder然后移动它们的位置。但是这里有个bug,RecyclerView根本不知道创建的viewHolder,所以它不会管你自己缓存的viewHolder
使用需要满足的条件:
- 位置固定,比如,广告位
- 不会改变
- 数量合理,保存在内存中没啥关系
(4)RecycleViewPool
缓存屏幕外的ViewHolder,当CachedView数据已满,则会将CachedView抛弃的ViewHolder交给RecycleViewPool,但是ViewHolder的数据会被删除,再次调用的话需要调用onBinderViewHolder绑定数据,这和ListView使用ViewHolder复用convertView道理一致
注:从Recycler里面按照顺序先获取scrap里面的缓存,之后是cacheView里面的,再之后是ViewCacheExtsension里面的,最后是从RecycleViewPool里获取数据。如果都获取不到,则会调用Adapter的createViewHolder()方法生成一个ViewHolder
21、AndroidManifest的作用与理解
每个应用的根目录中都必须包含一个,并且文件名必须一模一样。这个文件中包含了APP的配置信息,系统需要根据里面的内容运行APP的代码,显示界面
作用:
(1)为应用的 Java 软件包命名,软件包名称充当应用的唯一标识符
(2)描述应用的各个组件,包括构成应用的 Activity、Service、BroadcastReceiver和ContentProvider。它还为实现每个组件的类命名并发布其功能,例如它们可以处理的 Intent 消息。这些声明向 Android 系统告知有关组件以及可以启动这些组件的条件的信息
(3)确定托管应用组件的进程
(4)声明应用必须具备哪些权限才能访问 API 中受保护的部分并与其他应用交互。还声明其他应用与该应用组件交互所需具备的权限
(5)列出 Instrumentation类,这些类可在应用运行时提供分析和其他信息。这些声明只会在应用处于开发阶段时出现在清单中,在应用发布之前将移除
(6)声明应用所需的最低Android API级别
(7)列出应用必须链接到的库
更多推荐
Android高级面试题汇总——Android篇(1)
发布评论