本文出自 Eddy Wiki ,转载请注明出处:http://eddy.wiki/interview-android.html
本文收集整理了 Android 面试中会遇到与 Android 知识相关的简述题。
线程
子线程中更新UI的方法
第一种:Handler+Message
第二种:
new Handler(context.getMainLooper()).post(
new Runnable(){
public void run(){
//更新UI
}
});
第三种:
((Activity)context)runOnUiThread(
new Runnable(){
public void run(){
//更新UI
}
}
);
多线程
- 继承 Thread 类
- 实现 Runnable 接口
- HandlerThread
- Handler
- AsyncTask
- Activity.runOnUiThread(Runnable)
- View.post(Runnable),View.postDelay(Runnable,long)
Service | Thread | IntentService | AsyncTask | |
---|---|---|---|---|
When to use ? | Task with no UI, but shouldn't be too long. Use threads within service for long tasks. | - Long task in general.- For tasks in parallel use Multiple threads (traditional mechanisms) | - Long task usually with no communication to main thread.(Update)- If communication is required, can use main thread handler or broadcast intents- When callbacks are needed (Intent triggered tasks). | - Small task having to communicate with main thread.- For tasks in parallel use multiple instances OR Executor |
Trigger | Call to methodonStartService() | Thread start() method | Intent | Call to method execute() |
Triggered From (thread) | Any thread | Any Thread | Main Thread (Intent is received on main thread and then worker thread is spawed) | Main Thread |
Runs On (thread) | Main Thread | Its own thread | Separate worker thread | Worker thread. However, Main thread methods may be invoked in between to publish progress. |
Limitations /****Drawbacks | May block main thread | - Manual thread management- Code may become difficult to read | - Cannot run tasks in parallel.- Multiple intents are queued on the same worker thread. | - one instance can only be executed once (hence cannot run in a loop) - Must be created and executed from the Main thread |
线程同步
- 使用 synchronized 关键字创建 synchronized 方法。
- 使用 synchronized 创建同步代码块。
参考:
http://www.juwends.com/tech/android/android-inter-thread-comm.html
进程
进程优先级
- 前台进程:即与用户正在交互的Activity或者Activity用到的Service等,如果系统内存不足时前台进程是最后被杀死的
- 可见进程:可以是处于暂停状态(onPause)的Activity或者绑定在其上的Service,即被用户可见,但由于失去了焦点而不能与用户交互
- 服务进程:其中运行着使用startService方法启动的Service,虽然不被用户可见,但是却是用户关心的,例如用户正在非音乐界面听的音乐或者正在非下载页面自己下载的文件等;当系统要空间运行前两者进程时才会被终止
- 后台进程:其中运行着执行onStop方法而停止的程序,但是却不是用户当前关心的,例如后台挂着的QQ,这样的进程系统一旦没了有内存就首先被杀死
- 空进程:不包含任何应用程序的程序组件的进程,这样的进程系统是一般不会让他存在的
AIDL 解决了什么问题
AIDL (Android Interface Definition Language) 是一种IDL 语言,用于生成可以在 Android 设备上两个进程之间进行进程间通信(interprocess communication, IPC)的代码。如果在一个进程中(例如Activity)要调用另一个进程中(例如 Service, 设置了属性 android:process=":remote" 后,Service 就会运行在另外一个进程)对象的操作,就可以使用AIDL生成可序列化的参数。 AIDL IPC机制是面向接口的,像COM或Corba一样,但是更加轻量级。它是使用代理类在客户端和实现端传递数据。
AIDL的全称是什么?如何工作?能处理哪些类型的数据
ADIL是一种接口定义语言,用于约束两个进程之间的通信规则,供编译器生成代码,实现android设备之间的进程通信。
进程之间的通信信息首先会被转换成AIDL协议消息,然后发送给对方,对方受到AIDL协议消息后在转换成相应的对象。AIDL支持类型包括java基础类型和String,List,Map,CharSequence,如果使用自定类型,必须实现Parcelable接口
Broadcast、Content Provider 和 AIDL的区别和联系
这3种都可以实现跨进程的通信,那么从效率,适用范围,安全性等方面来比较的话他们3者之间有什么区别?
Broadcast:用于发送和接收广播!实现信息的发送和接收!
AIDL:用于不同程序间服务的相互调用!实现了一个程序为另一个程序服务的功能!
Content Provider:用于将程序的数据库人为地暴露出来!实现一个程序可以对另个程序的数据库进行相对用的操作!
Broadcast,既然是广播,那么它的优点是:注册了这个广播接收器的应用都能够收到广播,范围广。缺点是:速度慢点,而且必须在一定时间内把事情处理完(onReceive执行必须在几秒之内),否则的话系统给出ANR。
AIDL,是进程间通信用的,类似一种协议吧。优点是:速度快(系统底层直接是共享内存),性能稳,效率高,一般进程间通信就用它。
Content Provider,因为只是把自己的数据库暴露出去,其他程序都可以来获取数据,数据本身不是实时的,不像前两者,只是起个数据供应作用。一般是某个成熟的应用来暴露自己的数据用的。 你要是为了进程间通信,还是别用这个了,这个又不是实时数据。
进程间传输方式
Android进程间通信,Binder机制
Android跨进程通讯的方式
Android Mashup设计的理解
触摸事件
View 的触摸事件分发机制
基础知识
- 所有Touch事件都被封装成了MotionEvent对象,包括Touch的位置、时间、历史记录以及第几个手指(多指触摸)等。
- 事件类型分为ACTION_DOWN, ACTION_UP, ACTION_MOVE, ACTION_POINTER_DOWN, ACTION_POINTER_UP, ACTION_CANCEL,每个事件都是以ACTION_DOWN开始ACTION_UP结束。
- 对事件的处理包括三类,分别为传递——dispatchTouchEvent()函数、拦截——onInterceptTouchEvent()函数、消费——onTouchEvent()函数和OnTouchListener
传递流程
- 事件从Activity.dispatchTouchEvent()开始传递,只要没有被停止或拦截,从最上层的View(ViewGroup)开始一直往下(子View)传递。子View可以通过onTouchEvent()对事件进行处理。
- 事件由父View(ViewGroup)传递给子View,ViewGroup可以通过onInterceptTouchEvent()对事件做拦截,停止其往下传递。
- 如果事件从上往下传递过程中一直没有被停止,且最底层子View没有消费事件,事件会反向往上传递,这时父View(ViewGroup)可以进行消费,如果还是没有被消费的话,最后会到Activity的onTouchEvent()函数。
- 如果View没有对ACTION_DOWN进行消费,之后的其他事件不会传递过来。
- OnTouchListener优先于onTouchEvent()对事件进行消费。
上面的消费即表示相应函数返回值为true。
- View不处理事件流程图:
- View处理事件流程图:
View.onTouchEvent()
返回true
- 拦截事件流程图:
ViewGroup.onInterceptTouchEvent()
对MOVE、UP事件进行拦截
参考:
onInterceptTouchEvent()和onTouchEvent()的区别?
onInterceptTouchEvent()用于拦截触摸事件。
onTouchEvent()用于处理触摸事件。
view的事件冲突处理
View
View 绘制流程
当 Activity 接收到焦点的时候,它会被请求绘制布局,该请求由 Android framework 处理。绘制是从根节点开始,对布局树进行 measure 和 draw。整个 View 树的绘图流程在ViewRoot.java类的 performTraversals() 函数展开,该函数所做 的工作可简单概况为是否需要重新计算视图大小(measure)、是否需要重新安置视图的位置(layout)、以及是否需要重绘(draw),流程图如下:
View的绘制流程是从ViewRoot的performTraversals()
方法开始,依次经过measure()
,layout()
和draw()
三个过程才最终将一个View绘制出来。
参考:
Android 绘图机制原理
参考:
requertlayout onlayout onDraw drawChild 的区别和联系
- requestLayout()方法 :会导致调用measure()过程 和 layout()过程 。 将会根据标志位判断是否需要ondraw。
- onLayout()方法:如果该View是ViewGroup对象,需要实现该方法,对每个子视图进行布局。
- 调用onDraw()方法绘制视图本身,每个View都需要重载该方法,ViewGroup不需要实现该方法。
- drawChild()去重新回调每个子视图的draw()方法。
View 刷新机制
在Android的布局体系中,父View负责刷新、布局显示子View;而当子View需要刷新时,则是通知父View来完成。
子View调用invalidate时,首先找到自己父View(View的成员变量mParent记录自己的父View),然后将AttachInfo中保存的信息告诉父View刷新自己。
在invalidate中,调用父View的invalidateChild,这是一个从第向上回溯的过程,每一层的父View都将自己的显示区域与传入的刷新Rect做交集。
这个向上回溯的过程直到ViewRoot那里结束,由ViewRoot对这个最终的刷新区域做刷新。
参考:
invalidata() 和 postInvalidata() 的区别及使用
- invalidata() 必须在 UI 线程中调用,所以一般都是配合 Handler 使用。
- postInvalidata() 可以在其他线程直接调用。
notifyDataSetChanged和notifyDataSetInvalidated的区别
- notifyDataSetInvalidated(),会重绘整个控件(还原到初始状态)
- notifyDataSetChanged(),重绘当前可见区域
SurfaceView和View的区别是什么?
SurfaceView中采用了双缓存技术,在单独的线程中更新界面。而View在UI线程中更新界面。
RemoteView在哪些功能中使用
在 AppWidget(桌面小插件)和 Notification(通知栏)中使用。
参考:
自定义 View
自定义View相关方法
- 自定义属性的声明和获取
- 分析需要的自定义属性
- 在res/values/attrs.xml定义声明
- 在layout文件中进行使用
- 在View的构造方法中进行获取
- 测量onMeasure
- 布局onLayout(ViewGroup)
- 绘制onDraw
- onTouchEvent
- onInterceptTouchEvent(ViewGroup)
- 状态的恢复与保存
如何自定义控件
有哪些实现自定义控件的方法?
自定义控件的实现有三种方式,分别是:组合控件、自绘控件和继承控件。
参考:
优化自定义 View
为了加速你的view,对于频繁调用的方法,需要尽量减少不必要的代码。先从onDraw开始,需要特别注意不应该在这里做内存分配的事情,因为它会导致GC,从而导致卡顿。在初始化或者动画间隙期间做分配内存的动作。不要在动画正在执行的时候做内存分配的事情。
你还需要尽可能的减少onDraw被调用的次数,大多数时候导致onDraw都是因为调用了invalidate().因此请尽量减少调用invaildate()的次数。如果可能的话,尽量调用含有4个参数的invalidate()方法而不是没有参数的invalidate()。没有参数的invalidate会强制重绘整个view。
另外一个非常耗时的操作是请求layout。任何时候执行requestLayout(),会使得Android UI系统去遍历整个View的层级来计算出每一个view的大小。如果找到有冲突的值,它会需要重新计算好几次。另外需要尽量保持View的层级是扁平化的,这样对提高效率很有帮助。
如果你有一个复杂的UI,你应该考虑写一个自定义的ViewGroup来执行他的layout操作。与内置的view不同,自定义的view可以使得程序仅仅测量这一部分,这避免了遍历整个view的层级结构来计算大小。这个PieChart 例子展示了如何继承ViewGroup作为自定义view的一部分。PieChart 有子views,但是它从来不测量它们。而是根据他自身的layout法则,直接设置它们的大小。
自定义view控件以及自定义属性的使用
NavigationDrawer,PageAdapter等UI模式的使用和定制
Layout 布局
Android中常用的五种布局
- FrameLayout(框架布局)
- LinearLayout (线性布局)
- AbsoluteLayout(绝对布局)
- RelativeLayout(相对布局)
- TableLayout(表格布局)
LinearLayout和RelativeLayout性能对比
- RelativeLayout会让子View调用2次onMeasure,LinearLayout 在有weight时,也会调用子View2次onMeasure
- RelativeLayout的子View如果高度和RelativeLayout不同,则会引发效率问题,当子View很复杂时,这个问题会更加严重。如果可以,尽量使用padding代替margin。
- 在不影响层级深度的情况下,使用LinearLayout和FrameLayout而不是RelativeLayout。
最后再思考一下文章开头那个矛盾的问题,为什么Google给开发者默认新建了个RelativeLayout,而自己却在DecorView中用了个LinearLayout。因为DecorView的层级深度是已知而且固定的,上面一个标题栏,下面一个内容栏。采用RelativeLayout并不会降低层级深度,所以此时在根节点上用LinearLayout是效率最高的。而之所以给开发者默认新建了个RelativeLayout是希望开发者能采用尽量少的View层级来表达布局以实现性能最优,因为复杂的View嵌套对性能的影响会更大一些。
参考 :
http://www.jianshu.com/p/8a7d059da746
Android中px,sp,dip,dp的区别与联系
px: pixel,即像素,1px代表屏幕上的一个物理的像素点。但px单位不被建议使用。因为同样像素大小的图片在不同手机显示的实际大小可能不同。要用到px的情况是需要画1像素表格线或阴影线的时候,如果用其他单位画则会显得模糊。
dip (dp): device independent pixel。dp (dip)是最常用也是最难理解的尺寸单位。与像素密度密切相关。Android系统定义了四种像素密度:低(120dpi)、中(160dpi)、高(240dpi)和超高(320dpi),它们对应的dp到px的系数分别为0.75、1、1.5和2,这个系数乘以dp长度就是像素数。例如界面上有一个长度为“80dp”的图片,那么它在240dpi的手机上实际显示为80x1.5=120px,在320dpi的手机上实际显示为80x2=160px。如果你拿这两部手机放在一起对比,会发现这个图片的物理尺寸“差不多”,这就是使用dp作为单位的效果。
sp: Scale-independent Pixel,即与缩放无关的抽象像素。sp和dp很类似但唯一的区别是,Android系统允许用户自定义文字尺寸大小(小、正常、大、超大等等),当文字尺寸是“正常”时,1sp=1dp=0.00625英寸,而当文字尺寸是“大”或“超大”时,1sp>1dp=0.00625英寸。类似我们在windows里调整字体尺寸以后的效果——窗口大小不变,只有文字大小改变。
asset目录与res目录的区别。
res 目录下面有很多文件,例如 drawable,mipmap,raw 等。res 下面除了 raw 文件不会被压缩外,其余文件都会被压缩。同时 res目录下的文件可以通过R 文件访问。
Asset 也是用来存储资源,但是 asset 文件内容只能通过路径或者 AssetManager 读取。
参考:
Android屏幕适配
动画
Android属性动画特性
Android 起初有两种动画:Frame Animation(逐帧动画) Tween Animation(补间动画),但是在用的时候发现这两种动画有时候并不能满足我们的一些需要,所以Google在Androi3.0的时候推出了(Property Animation)属性动画,至于为什么前边的两种动画不能满足我们的需要,请往下看:
Frame Animation(逐帧动画)
逐帧动画就是UI设计多张图片组成一个动画,然后将它们组合链接起来进行动画播放。该方式类似于早期电影的制作原理:具体实现方式就不多说了,你只需要让你们的UI出多张图片,然后你顺序的组合就可以(前提是UI给您做图)
Tween Animation(补间动画)
Tween Animation:是对某个View进行一系列的动画的操作,包括淡入淡出(Alpha),缩放(Scale),平移(Translate),旋转(Rotate)四种模式
Tween Animation(补间动画)的一些缺点:
- Tween Animation(补间动画)只是针对于View,超脱了View就无法操作了,这句话的意思是:假如我们需要对一个Button,ImageView,LinearLayout或者是其他的继承自View的各种组件进行动画的操作时,Tween Animation是可以帮我们完成我们需要完成的功能的,但是如果我们需要用到对一个非View的对象进行动画操作的话,那么补间动画就没办法实现了。举个例子:比如我们有一个自定义的View,在这个View中有一个Point对象用于管理坐标,然后在onDraw()方法中的坐标就是根据该Pointde坐标值进行绘制的。也就是说,如果我们可以对Point对象进行动画操作,那么整个自定义的View,那么整个自继承View的当前类就都有了动画,但是我们的目的是不想让View有动画,只是对动画中的Point坐标产生动画,这样补间动画就不能满足了。
- Tween Animation动画有四种动画操作(移动,缩放,旋转,淡入淡出),但是我们现在有个需求就是将当前View的背景色进行改变呢?抱歉Tween Animation是不能帮助我们实现的。
- Tween Animation动画只是改变View的显示效果而已,但是不会真正的去改变View的属性,举个例子:我们现在屏幕的顶部有一个小球,然后通过补间动画让他移动到右下角,然后我们给这个小球添加了点击事件,希望位置移动到右下角的时候点击小球能的放大小球。但是点击事件是绝对不会触发的,原因是补间动画只是将该小球绘制到了屏幕的右下角,实际这个小球还是停在屏幕的顶部,所以你在右下角点击是没有任何反应的。
Property Animatior(属性动画)
属性动画是Android3.0之后引进的,它更改的是动画的实际属性,在Tween Animation(补间动画)中,其改变的是View的绘制效果,真正的View的属性是改变不了的,比如你将你的Button位置移动之后你再次点击Button是没有任何点击效果的,或者是你如何缩放你的Button大小,缩放后的有效的点击区域还是只有你当初初始的Button的大小的点击区域,其位置和大小的属性并没有改变。而在Property Animator(属性动画)中,改变的是动画的实际属性,如Button的缩放,Button的位置和大小属性值都会发生改变。而且Property Animation不止可以应用于View,还可以应用于任何对象,Property Animation只是表示一个值在一段时间内的改变,当值改变时要做什么事情完全是你自己决定的。
Android动画框架实现原理
Animation 框架定义了透明度,旋转,缩放和位移几种常见的动画,而且控制的是整个View。实现原理:
每次绘制视图时,View 所在的 ViewGroup 中的 drawChild 函数获取该View 的 Animation 的 Transformation 值,然后调用canvas.concat(transformToApply.getMatrix()),通过矩阵运算完成动画帧,如果动画没有完成,继续调用 invalidate() 函数,启动下次绘制来驱动动画,动画过程中的帧之间间隙时间是绘制函数所消耗的时间,可能会导致动画消耗比较多的CPU资源,最重要的是,动画改变的只是显示,并不能相应事件。
参考:
图片缓存
图片三级缓存实现?自己设计一个图片加载框架
参考:
对 LruCache 的理解
参考:
DiskLruCache
参考:
Android DiskLruCache完全解析,硬盘缓存的最佳方案
缓存算法
Bitmap的分析与使用
参考:
Android 从具体实例分析Bitmap使用时候内存注意点
Loading Large Bitmaps Efficiently
Bitmap 压缩
public static Bitmap create(byte[] bytes, int maxWidth, int maxHeight) {
//上面的省略了
option.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(bytes, 0, bytes.length, option);
int actualWidth = option.outWidth;
int actualHeight = option.outHeight;
// 计算出图片应该显示的宽高
int desiredWidth = getResizedDimension(maxWidth, maxHeight, actualWidth, actualHeight);
int desiredHeight = getResizedDimension(maxHeight, maxWidth, actualHeight, actualWidth);
option.inJustDecodeBounds = false;
option.inSampleSize = findBestSampleSize(actualWidth, actualHeight,
desiredWidth, desiredHeight);
Bitmap tempBitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, option);
// 做缩放
if (tempBitmap != null
&& (tempBitmap.getWidth() > desiredWidth || tempBitmap
.getHeight() > desiredHeight)) {
bitmap = Bitmap.createScaledBitmap(tempBitmap, desiredWidth,
desiredHeight, true);
tempBitmap.recycle();
} else {
bitmap = tempBitmap;
}
}
return bitmap;
}
你这么做,decodeByteArray两次不是更占内存吗?😂😂😂
第一次设置inJustDecodeBounds = true 时候是不占内存的,因为返回的是null
一脸不相信我的说:噢,这地方我下去再看看。
吓得我回来了以后赶紧又看了看,还好没有记错,见源码注释
/**
* If set to true, the decoder will return null (no bitmap), but
* the out... fields will still be set, allowing the caller to query
* the bitmap without having to allocate the memory for its pixels.
*/
public boolean inJustDecodeBounds;
ARGB_8888格式图片占用内存大小
1 byte = 8 bit
8+8+8+8 = 32 32/8 = 4 byte 一个像素就占4byte
参考:
如何判断本地缓存的时候数据需要从网络端获取
图片加载机制
ListView
ListView的优化
ListView卡顿原因
- 在adapter中的getView方法中尽量少使用逻辑
- 尽最大可能避免GC
- 滑动的时候不加载图片
- 将ListView的scrollingCache和animateCache设置为false
- item的布局层级越少越好
- 使用ViewHolder
ListView 的实现原理
参考:
Android ListView工作原理完全解析,带你从源码的角度彻底理解
ViewHolder
参考:
ListView中convertView和ViewHolder的工作原理
ListView 下拉刷新、上拉加载更多实现原理
参考:
下拉刷新及滚动到底部加载更多的Listview使用 — 优先使用该开源实现
Android ListView下拉/上拉刷新:设计原理与实现
RecyclerView和ListView的异同
- RecyclerView 自带 ViewHolder;而 ListView 则需要自定义。
- RecyclerView 支持水平和垂直滚动;而 ListView 只支持垂直滚动。
- RecyclerView 提供默认的列表项动画实现,例如:添加、删除和移动列表项动画。
- ListView通过AdapterView.OnItemClickListener接口来监听点击事件。而RecyclerView则通过RecyclerView.OnItemTouchListener接口来监听触摸事件。它虽然增加了实现的难度,但是却给予开发人员拦截触摸事件更多的控制权限。
- ListView可以设置选择模式,并添加MultiChoiceModeListener;而 RecyclerView 没有该功能。
参考:
http://www.tuicool.com/articles/aeeaQ3J
http://blog.csdn.net/sanjay_f/article/details/48830311
能否讲讲你用过的adapter?
容器
SparseArray 和 HashMap 的区别
类 | cpu | 内存 | 适用场景 |
---|---|---|---|
HashMap | 增、删、查找速度较快 | 双倍扩容、不做空间整理,内存使用效率低 | 数据量较大或内存空间相对宽裕 |
ArrayMap | 增、删、查速度较慢 | size大于8扩容时,只增大当前数组大小的一半,做空间收缩整理 | 数据量小于1000时,速度相对差别不大,可替代HashMap |
SparseArray | 增、查速度较慢,由于延迟删除机制,删速度比ArrayMap快,比HashMap慢 | 矩阵压缩,大大减少了存储空间,节约内存 | 避免了key的自动装箱,空间压缩等机制,使得其在key是Integer、Long,且数据量较小场景下性能最优 |
参考: