面试技术汇总
这份文档讲解了面试的精华,希望大家认真对待
目录
四、面试技术04: Android中的消息机制与异步任务 10 二十、面试技术20:技术站点与优秀Blog 36 |
一、面试技术01:自定义View知识
1. View是什么? 1). View类是所有用来构建用户界面的组件的基类 2). ViewGroup是View的一个子类,是各种布局的基类, 它是包含其它View或ViewGroup和定义这些孩子布局参数的容器 3). 一个View(ViewGroup)占用屏幕上的一个矩形区域,它负责界面的绘制和事件处理 4). 手机屏幕上所有看得见摸得着的都是View(包括ViewGroup) 2. View的分类 1). 不能包含子View的View:一般的View TextView: 显示文本(它也可以带图片) Button: 按钮 ImageView: 显示图片 2). 可以包含子View的View: ViewGroup FrameLayout RelativeLayout LinearLayout ScrollView ListView GridView 3. ViewGroup 1). ViewGroup extends View implements ViewParent, ViewManager 2). ViewManager: 用来添加、删除、更新布局 addView(View v) removeView(View v) updateViewLayout(View v) 3). ViewParent: 提供了一系列操作子View的方法 4. 关于View和ViewGroup 1). 整个界面只会有一个根View a. 得到它: window.getDecorview() PhoneWindow$decorView b. 本质类型: FrameLayout c. setContentView()添加的视图不是整个界面的根view FrameLayout(id=content) 2). 一个View只会有一个父View(ViewGroup),一个ViewGroup可以有多个子View a. 得到父视图: view.getParent(),可以将返回的ViewParent强转为指定的ViewGroup b. 不是所有的View都能添加子view,只有ViewGroup及其子类才能添加 5. 区别View与Activity? 1). Activity是四大组件中唯一能与用户进行交互的组件 2). Activity只是控制和管理View,真正显示和处理事件的是View本身来完成 3). Activity内部有一个window对象, window对象(Phonewindow)中包含一个FramLayout类型的decorView 在Activity中setContentView(view)实质上是将view添加到decorView中 手机屏幕中显示的就decorView 6. 显示ContentView的3种方式: 1). setContentView(R.layout.activity_main); 2). View view = View.inflate(this, R.layout.activity_main, null); setContentView(view); 3). MyTextView tv = new MyTextView(this); setContentView(tv); 7. View的生命周期过程: 1). 创建View对象 a. 存在2种创建的方式: 加载布局文件创建和直接new构造方法创建 b. 布局文件方式的本质:解析布局xml,根据标签名创建对应的View对象,并将标签属性设置到对象中 c. 布局文件方式:在所有的View对象都创建好了后,会调用onFinishInflate() d. 在视图对象都创建好后,调用onAttachedToWindow(),表明view对象关联到window上了 2). 测量(多大?) a.方法执行: measure()--->onMeasure()--->setMeasuredDimension() b. measure(): 用来确定当前View的大小,它是final的方法不能重写,它内部调用了onMeasure() c.onMeasure(): 一般重写此方法,根据我们的实际需求,对View进行测量 d. setMeasuredDimension(): onMeasure()中必须调用些方法保存当前View测试出的宽度和高度 3). 布局(在哪?) a. 方法执行: layout()--->onLayout() b. layout(l, t, r, b): ①. 指定View的左上角点的坐标和右下角点的坐标 ②. 比较原有的l,t,r,b与当前的l,t,r,b,如果有变化了或请求重新layout,就会调用onLayout(l,t,r,b) ③.不会重写些方法,只会调用些方法来指定当前View在父View中的位置 c. onLayout(changed, l, t, r, b) ①. 决定当前view在父ViewGroup中的位置 ②. 在View类中只是空实现,因为一个View的位置是由父View来指定的 ③. 在ViewGroup类中用来指定子View的位置,但是abstract FrameLayout LinearLayout RelativeLayout ④. LinearLayout的实现: 判断自身是横屏还是竖屏? 8. 总结: creation |
二、面试技术02: 自定义View应用
功能一: 快速索引列表 1. 好友列表功能: 1). 对好友List集合按拼音首字母进行排序(需要用到pinyin4j.jar) 2). 显示好友列表时,显示拼音首字母 2. 列表右侧A到Z列表功能: 1). 从上至下均匀显示A到Z的所有字母 2). 在按下或移动到某个字母时,字体大小和颜色都有变化 3). 在按下或移动到某个字母时,正中间区域会显示当前选择的字母,且过一会自动消失 4). 在按下或移动到某个字母时,列表会移动到对应的列表项区域 功能二: 滑动删除ListView的某项 功能描述: 水平滑动列表的某一项可以打开或关闭 实现过程 1. 如何得到contentView和deleteView对象? 1). 重写onAttachedToWindow() 2). 调用viewGroup.getChildAt(index) 2. 如何得到contentView和deleteView的宽高? 1). 重写onMeasure() 2). 调用getMeasuredWidth()和getMeasuredHeight() 3. 如何给contentView和deleteView进行初始化布局定位? 1). 重写onLayout() 2). 调用layout()对contentView与deleteView进行布局定位 4. 如何拖动contentView与deleteView? 1). 使用ViewDragHelper这个视图拖拽的帮助类 2). 创建ViewDragHelper对象,并指定其回调对象,并重写其中的一些方法 boolean tryCaptureView(View child): 判断child是否是需要拖拽的子View, down时调用 int clampViewPositionHorizontal(): 限制view在水平方向真正移动的距离, move时调用 onViewPositionChanged(View child): 当子view的位置发生改变时回调,需要在其中去移动其它的子View onViewReleased(View releasedChild): 当子View被释放时回调,即up时调用 int getViewHorizontalDragRange(View child): 返回子View在水平方向最大移到的距离,此方法在用于ListView时才需要重写 3). 重写onTouchEvent(),在其中让dragHelper对象来处理event对象 5. 如何解决手指滑动时ListView垂直方向滚动和SwipeView水平滑动的冲突问题? 1). 在move事件时,获取x轴和y轴方向的移动距离distanceX, distanceY 2). 如果distanceX>distanceY,执行requestDisallowInterceptTouchEvent(true),使ListView不能拦截event 6. 如何解决SwipeView滑动与ContentView/DeleteView点击事件的冲突问题? 1). 重写onInterceptTouchEvent() 2). 返回dragHelper.shouldInterceptTouchEvent(ev),由dragHelper来决定是否拦截event 7. 如何只让ListView中只有一个SwipeView能被打开? 1). 为SwipeView定义枚举类型状态类 2). 在SwipeView中定义监听接口,在接口中定义回调方法 3). 定义设置监听接口对象的方法 4). 在Activity的Adapter中为每个SwipeView对象设置监听器对象,在回调方法中做处理 8. 如何让swipeView的自动打开和关闭是平滑的效果? 1). 调用下面的方法打开/关闭 dragHelper.smoothSlideViewTo(contentView, -deleteWidth, 0); ViewCompat.postInvalidateOnAnimation(this); 2). 重写computeScroll() if (dragHelper.continueSettling(true)) { ViewCompat.postInvalidateOnAnimation(this); } 9. ListView滑动时,关闭打开的SwipeView 在ListView的Scroll监听中,一旦滑动,关闭打开的swipeView |
三、面试技术03: View的触摸事件机制
1. 触摸事件的基本类型 1). down: 按下 2). move: 移动 3). up: 离开 2. 事件对象产生的顺序 1). down-->move-->move-->....-->up 2). 每个事件对象产生后,都会找到一个消费者来消费处理此事件 3. 事件相关API 1). MotionEvent: 代表对UI的操作单元的类,它的对象在用户触摸UI时系统自动创建基对象,并将相关的数据保存在此对象中 ACTION_DOWN=0 : down类型值 ACTION_UP=1 : up类型值 ACTION_MOVE=2 : move类型值 int getAction() : 得到事件类型值 float getX() : 得到事件的X轴坐标(相对于当前View的左顶点) float getRawX() : 得到事件的X轴坐标(相对于屏幕的左顶点) float getY() : 得到事件的Y轴坐标(相对于当前View的左顶点) float getRawY() : 得到事件的Y轴坐标(相对于屏幕的左顶点) 2). Activity boolean dispatchTouchEvent(MotionEvent event) : 分发事件 boolean onTouchEvent(MotionEvent event) : 处理事件的回调 3). View boolean dispatchTouchEvent(MotionEvent event): 分发事件
setOnClickListener(OnClickListener l) : 设置触摸事件监听器对象 private OnTouchListener mOnTouchListener; //触摸事件监听器对象变量 public interface OnTouchListener { //事件监听器接口 boolean onTouch(View v, MotionEvent event); // 监听器对象的回调方法 } boolean onTouchEvent(MotionEvent event) : 事件监听回调方法 setOnclickListener(OnclickListener listener) : 设置点击监听器 setOnLongClickListener(OnLongClickListener listener) : 设置长按事件监听器 4). ViewGroup boolean dispatchTouchEvent(MotionEvent ev) : 重写View的此方法,如果当前ViewGroup不拦截,会分发给对应的子View处理事件 boolean onInterceptTouchEvent(MotionEvent ev) : 拦截触摸事件, 返回值如果为true表示拦截,后面的事件就会交给当前View来处理,默认为false requestDisallowInterceptTouchEvent(boolean disallowIntercept) :如果参数为true,使当前View及其外层的所有父View不能拦截后面的事件 4. View的事件处理 1). Touch事件的方法执行顺序: ①. dispatchTouchEvent() ②. setOnTouchListener的onTouch() ③. onTouchEvent() 2). 执行的详细过程 ①.在dispatchTouchEvent()会判断是否设置了Touch监听器? 如果没有直接进入② 如果有,调用监听器的onTouch()方法,如果onTouch方法返回true到此结束,如果返回false进入② ②.调用onTouchEvent() 在down时, send一个延时500ms的消息准备触发长按事件监听回调) 如果0.5s内在产生了up事件,此时就会移除长按的延时消息,就会去执行点击事件监听回调 如果0.5内没有产生up事件,也没有离开,就会调用长按事件监听回调方法,如果返回的值是true就不可能再触发点击监听回调了,否则还会触发. 3). 说明: ①.如果view的onTouch()(监听器回调)或onTouchEvent(监听回调)在down时返回true,那第一个move事件就会交给当前View处理, 否则后面的所有事件都不会到达此View了 ②.如是move事件处理返回true,下一个move/up事件就会交给当前View处理,否则就会找父View或Activity处理 ③.整体原则:每个Event对象创建后,最终肯定会有一个消费者:可能是View,也可能是ViewGroup,实在不行就交给Activty消费处理 5. ViewGroup的事件处理 1). 相关方法执行顺序: ①. dispatchTouchEvent() ②.onInterceptTouchEvent() ③.对应子View的dispatchTouchEvent() 2). 执行的详细过程: ①. 在dispatchTouchEvent()中, ACTION_DOWN时, 判断是否拦截,如果没有拦截,则找到包含当前x,y坐标的子View,赋值给mMotionTarget, 然后调用mMotionTarget.dispatchTouchEvent()处理down事件 ②. 在dispatchTouchEvent()中, ACTION_MOVE时, 判断是否拦截,如果没有拦截,则直接调用mMotionTarget.dispatchTouchEvent(ev) ③. 在dispatchTouchEvent()中, ACTION_UP时, 判断是否拦截,如果没有拦截,则直接调用mMotionTarget.dispatchTouchEvent(ev) ④. 如果没有找到合适的子View来消费当前event,则将自己当成View来处理event 3). 关于拦截: ①. 如何拦截?: ViewGroup中onInterceptTouchEvent()默认返回false,也就是不拦截,如果想拦截就重写此方法,并返回true,这样事件就不会分发给子View处理 ②. 如何不被拦截?:如果子View不希望父View(也就是当前ViewGroup)拦截event,子View可以执行: getParent().requestDisallowInterceptTouchEvent(true) 6. 扩展blog:
Android事件分发机制完全解析,带你从源码的角度彻底理解(上) Android事件分发机制完全解析,带你从源码的角度彻底理解(下) |
四、面试技术04: Android中的消息机制与异步任务
1. 相关基础知识: 1). 在Android中,运行的线程有两种类型: UIThread(主线程,一个)和WorkerThread(分线程,多个) 2). 在Android中,只有在UIThread中才能直接更新界面,如果在分线程直接更新界面,会抛出如下异常: android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. 3). 在Android中, 很多长时间处理数据的工作(联网)都需要在workerThread中执行,否则会抛出异常/操作反应慢 4). 在分线程得到数据后,需要去更新界面,但在分线程中直接更新界面是不允许的,如何解决此矛盾? 5). 联网相关功能的三步: ①.显示提示视图:可能是ProgressBar,也可能是ProgressDialog. ----在主线程执行 ②.请求服务器,得到服务器返回的数据. ----在分线程执行 ③.更新界面:移除提示视图,显示得到的数据. ----在主线程执行 2. 消息机制: 1). Android中设计的一套API和完善的运行工作体系,它能解决如何情况 ①.分线程与主线程之间的多线程间通信 ②.在一个线程内,工作的统一处理 ③.轻松实现延迟工作, 循环工作, 定时工作 2). 相关API: ①. Message: 消息类 Message.obtain(what) : 创建消息对象 public int what : 标识值 public int arg1 : 携带Int类型数据 public int arg2 : 携带Int类型数据 public Object obj : 携带任意对象类型数据 public long when : 标识当前Message什么时刻应该被处理 public Handler target : 当前message由哪个handler来分发处理 ②. Handler:处理器类 sendMessage(Message msg) : 发送即时消息 sendMessageDelayed(Message msg, long delayMillis) : 发送延时消息 sendEmptyMessage(int what): 发送即时空消息 sendEmptyMessageDelayed(int what, long delayMillis) : 发送延时空消息 boolean post(Runnable r) : 发送即时带回调的空消息 boolean postDelayed(Runnable r, long delayMillis): 发送延时带回调的空消息 handleMessage(Message msg) : 处理消息的回调方法
removeMessages(int what) : 根据what删除还未处理的对应消息 removeCallbacksAndMessages(null) : 删除所有未处理的消息
dispatchMessage(Message msg) : 分发消息 message的回调 handler的callback监听器的回调方法 handler的回调方法 ③. MessageQueue:消息队列类(我们不需要操作) enqueueMessage(Message msg, long when): 将消息添加到消息队列 无论发送的是即时消息还是延时消息,都是立即将message对象保存到了MessageQueue对象中 Message next() : 取出一个需要处理的消息,如果没有就会进入等待状态,但不会导致UIThread阻塞 thread.wait() ④. Looper:循环器类(我们不需要操作) loop() : 使用无限for循环获取message,并调用对应的handler分发处理此消息 3). 原理
3. 异步任务: AsyncTask 1). 在使用AsyncTask之前,我们可以使用Handler+Thread的方式实现异步任务的功能 2). AsyncTask的优势: 编码上:更简洁,使用更方便 效率上:由于AsyncTask内部使用的是线程池,能反复使用Thread对象进行分线程的处理工作,而原生的方式每次都是新建Thread对象启动分线程 3). 相关API: class AsyncTask<Params, Progress, Result> Params 启动任务执行的输入参数,比如HTTP请求的URL。 Progress 后台任务执行的百分比。 Result 后台执行任务最终返回的结果,比如String。 execute(Params... params) : 启动任务,开始任务的执行流程 void onPreExecute() : 在分线程工作开始之前在UIThread中执行,一般用来显示提示视图 Result doInBackground(Params... params) : 在workerThread中执行,完成任务的主要工作,通常需要较长的时间 void onPostExecute(Result result) 在doInBackground()执行完后在UIThread中执行,一般用来更新界面 publishProgress(Progress... values) : 在分线程中,发布当前进度 void onProgressUpdate(Progress... values) : 在主线程中更新进度 4). 原理: execute()
|
五、面试技术05:ListView
0. 第0层:不优化: 每次执行getView(),都会执行: converterView = View.inflate(R.layout.xxx); 问题:效率太低,在快速滑动时会有卡顿,在数据很多时甚至会内存溢出 1. 第一层:复用converterView if(converterView==null) { converterView = View.inflate(R.layout.xxx); 10-->11 } 问题:每次执行getView()都需要执行converterView.findViewById()得到子View 2. 第二层:使用ViewHolder,减少findViewById()的次数 Viewholder holder = null; if(converterView==null) { converterView = View.inflate(R.layout.xxx); holder = new ViewHolder(); holder.imageView = (ImageView)converterView.findViewById(xxx); holder.textView = (TextView)converterView.findViewById(yyy); converterView.setTag(holder); } else { holder = (Viewholder )converterView.getTag(); } static class ViewHolder { ImageView imageView; TextView textView; } 问题1:对于联网获取列表数据,如果数据量太大(比如超过100条甚至更多),一次获取出来显示,太慢太耗流量 问题2: 对于联网获取列表数据,如果包含图片数据,每次都请求获取显示, 太慢太耗流量 3. 第三层:对数据列表进行分页加载显示 1). 自己做:通过Scroll监听listView.setonScrollListener(scrollListener),当到达底部时加载下一页列表数据并显示 2). 使用第三方开源框架: Android-PullToRefresh或其它 4. 第四层优化:图片三级缓存处理 参见图片三级缓存机制 |
六、面试技术06: 图片的三级缓存机制
1. 什么是三级缓存? 一级:内存中的缓存图片对象(Bitmap),用Map<url, Bitmap> 二级:手机sd卡的files或手机内部的files中缓存图片文件(xxx.jpg/png) 2. 如何应用三级缓存? 例如: 如何根据url根据图片显示? 1). 根据url从一级缓存(Map<url, Bitmap>)中取图片对象,如果取到了,直接显示 注意:真实项目中使用LruCache<String, Bitmap>来缓存图片(查看<<面试技术11.图片处理优化>>) 2). 如果没有,根据url中包含图片名称(文件名),手机的sd卡或内部找对就图片文件加载成bitmap 3. 在ListView中应用图片三级缓存的问题? 问题: 1). 在快速滑动时,会出现显示错误图片 2). 图片显示后,过一会又会变为另一张图片(图片闪动) 原因: 在分线程加载图片的过程中, converterView已经被复用 解决: 1). 将url字符串保存到imageView对象的tag中 2. 在加载图片的线程开始干活前,查看imageView的tag中的url是否变化,如果变化不加载图片 3). 在加载到图片准备显示前,再次查看imageView的tag中的url是否变化,如果变化不显示图片 |
七、面试技术07: Android的四大应用组件
1. Android的四大应用组件是哪些? 1). Activity 2). Service 3). BroadcastReceiver 4). ContentProvider 2. 对应用组件的理解 1). Java是面向对象的,而Android是面向组件的(包括应用组件与视图组件),我们在写项目时,都是从写各种组件类开始的 2). 作为应用组件都会有一些特点: ①.都需要继承系统定义好的某个组件类 ②.需要进行注册(配置文件/代码) ③.对象的创建和管理都是由系统帮我们完成 ④.都有一定的生命周期方法,我们要去实现或重写它们来做一些我们的工作 3. 对各个应用组件的理解 1). Activity: 活动 描述:提供能与用户进行交互的用户界面 主要工作:加载布局,为视图设置监听,在监听回调中完成工作, 利用Activity的生命周期回调方法做一些特定的工作 重要知识点:命周期方生法, launchMode, BackStack, Activity的启动与停止 2). Service: 服务 描述: 后台为应用做一些时间跨度比较大的任务 长时间的任务 区别: Service与Activity, Service与Thread 重要知识点: Service的生命周期
3). BroadcastReceiver: 广播接收器 描述:广播机制是Android中实现不同应用间(进程间)通信的一种手段(应用级的事件机制) 重要知识点:区别注册接收器的两种方式 区别一般广播与有序广播 常见的系统广播 4). ContentProvider: 内容提供者 描述:用来将当前应用表数据的操作暴露给其它应用 重要知识点: 为什么要用ContentProvider? ContentResolver, ContentProvider与ContentObsolver的关系 注意:每个都可以说一个项目中的例子来说明 优秀Blog: http://blog.csdn.net/liuhe688/article/details/9494411 |
八、面试技术08: Android中的数据存储
1. SP存储 存储位置: 特点: 相关API: 2. Sqlite数据库存储 存储位置: 特点: 相关API: 3. 文件存储 手机内部文件 存储位置: 特点: 相关API: 手机外部(sd)谁的 存储位置: 特点: 相关API: 4. netWork 1). Http协议 请求方式: 请求过程: 请求的格式 响应的格式 2). 相关API: 原生的 : HttpUrlConnection 封装的: HttpClient 第三方框架 : Volley |
九、面试技术09: 性能优化
1. 性能相关的两个概念 1). 响应时间 指从用户操作开始到系统给用户以正确反馈的时间 系统处理时间 +网络传输时间(可能没有) +展现时间 2). TPS(Transactions Per Second) 每秒处理的事务数,是系统吞吐量的一个指标 2. 什么是性能问题? 响应时间过长,系统吞吐量过低,高并发下内存泄漏 (ANR) 3. 性能调优的方式: 1). 降低执行时间 缓存(对象缓存,IO缓存,网络缓存, DB缓存) 数据存储类型优化 算法优化 逻辑优化 JNI 需求优化 2). 同步改异步 利用多线程提高TPS 3). 提前或延迟操作 错开时间段提高TPS 4. 调优点: 1). Java代码部分优化 2). 布局优化、 3). 数据库优化 5. Java代码优化之:缓存优化: 1). 线程池 2). 图片缓存 3). Message缓存 4). ListView缓存 5). layout缓存 6). 文件IO缓存 6. Java代码优化之:数据存储优化 1). 数据类型选择: StringBuilder/StringBuffer代替String SoftRefrence代替强引用 LocalBroadcastManager代替普通BroadcastReceiver,效率和安全性都更高 2). 数据结构选择 ArrayList与LinkedList的选择 List与Map的选择 Android中推荐用SparseArray代替Map<Integer,Object>, SparseArray内部使用二分法查找数据,效率更高 7. Java代码优化之:算法优化 查询考虑hash和二分,尽量不用递归 必要时可以考虑:使用空间换时间 8. 逻辑优化 主要是理清程序逻辑,减少不必要的操作。 9. 异步,利用多线程提高TPS 充分利用多核Cpu优势,利用线程解决密集型计算、IO、网络等操作,分线程处理完成后可以通过Handler与主线程交互 10. 提前或延迟操作,错开时间段提高TPS 1) 延迟操作: 不在Activity、Service、BroadcastReceiver的生命周期等对响应时间敏感函数中执行耗时操作,可适当delay 2). 提前操作: 对于第一次调用较耗时操作,可统一放到初始化中,将耗时提前 11. 网络优化 a. 图片必须缓存,最好根据机型做图片做图片适配 b. 所有http请求必须添加httptimeout i. api接口服务器端响应时间不超过100ms 12. 布局优化: 1). 对于布局的选择: FrameLayout>RelativeLayout>LinearLayout 2). <include>标签 3). <viewstub>标签 4). <merge>标签 5). 去除不必要的嵌套和View节点 6). 减少不必要的infalte, infalte布局后缓存起来 7). 多使用布局调优工具来查看布局情况并做出合理的优化 |
十、面试技术10. 内存溢出与内存泄露
1. 定义: 1). 内存溢出:即为out of memory,当你要求分配的内存超过了系统给你的内存时, 系统就会抛出out of memory的异常(每个Android能用的内存是有限的) 比如:当前应用只剩下4M的空间可用,但你却加载得到一个需要占用5M空间的图片Bitmap对象,就会抛出溢出的异常 2). 内存泄露:即为memory leak,一个对象被创建后,你不再使用它了,但因为某种原因它又没有成为垃圾对象,这块内存不能再被分配置使用 比如:查询数据库得到的cursor对象在使用完后没有关闭, Activity中使用Handler发延迟消息,但退出前不移除未处理的消息 3). 内存泄露不多时没有太大影响,但积累得多了就会导致应用运动缓慢,到最后就会内存溢出 2. 内存泄漏的分类: 1). 常发性内存泄漏:发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏 2). 偶发性内存泄漏:发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。 对于特定的环境,偶发性的也许就变成了常发性的 3). 一次性内存泄漏:发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏 说明:危害性大小顺序为: 1)>2)>3) 3. 造成内存泄露的几种场景 1). 长生命周期的对象持有短生命周期对象的引用: Activity中使用Handler 2). 资源数据连接相关对象不关闭: cusor, stream, connection 3). HashSet中的对象或HashMap中的Key对象,基内部与hash值相关的属性被修改 4). 一些对象产生后不会自动释放或需要完全执行完了才释放.比如: Bitmap, Thread, AsyncTask 4. 避免内存泄露 1). 尽早释放无用对象的引用 2). 使用字符串处理,避免使用String,应大量使用StringBuffer,每一个String对象都得独立占用内存一块区域 3). 尽量少用静态变量,因为静态变量存放在永久代(方法区),永久代基本不参与垃圾回收 4). 避免在循环中创建对象 5. 造成内存溢出的的场景: 1). 申请了太多的对象.比如:使用ListView时,不复用convertView,当数据项多时就会出现内存溢出 2). 创建的对象内存太大.比如: 不经过压缩直接加载大图片文件 3). 内存泄露积累一定的时间后就可能出现 6. 避免内存溢出: 1). 通过复用对象的方式,减少产生的对象 2). 大对象需要先压缩后创建 3). 避免或减少内存泄露的情况 注意: 双击全屏查看 自己也多百度一下看看多积累 |
十一、面试技术11.
图片处理优化
1. 大图片的加载显示(避免OOM问题):
1). 问题:如果将大图片加载到内存中来显示,可能会导致内存溢出(OOM)
2). 解决思路:对图片进行压缩加载(本质上只是读取了图片文件的部分数据)
3). 具体办法:
①.得到图片的宽高的方式:
BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; //设置只读取图片文件的边框,这样就不会加载整个图片文件 BitmapFactory.decodeResource(getResources(), R.id.myimage, options); int imageHeight = options.outHeight; //图片的高 int imageWidth = options.outWidth; //图片的宽 String imageType = options.outMimeType; |
注意:为了避免OOM异常,最好在解析每张图片的时候都先检查一下图片的大小,
除非你非常信任图片的来源,保证这些图片都不会超出你程序的可用内存
②.计算取样比例的方式:
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { // 源图片的高度和宽度 final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { // 计算出实际宽高和目标宽高的比率 final int heightRatio = Math.round((float) height / (float) reqHeight); final int widthRatio = Math.round((float) width / (float) reqWidth); // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高 // 一定都会大于等于目标的宽和高。 inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; } return inSampleSize; } |
注意:如果inSampleSize=3,表示 宽和高上只读取原来数据的1/3,这样整体大小压缩为原来的1/9
③.整体处理:
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { // 第一次解析将inJustDecodeBounds设置为true,只获取图片宽,高大小 final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; //不会加载整个图片文件 BitmapFactory.decodeResource(res, resId, options); // 调用上面定义的方法计算inSampleSize值 options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // 使用获取到的inSampleSize值再次解析图片 options.inJustDecodeBounds = false; //会根据inSampleSize加载图片的部分数据到内存 return BitmapFactory.decodeResource(res, resId, options); } |
调用代码如下:
mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100)); //将任意一张图片压缩成100*100的缩略图,并在ImageView上展示 |
2. 缓存图片对象
1). 在内存中缓存图片对象(Bitmap), 不要直接用Map<String, Bitmap>类型的容器,因为这样会导致bitmap对象太多太大时而没有去释放,最终导致OOM
2). 在Android2.3之前,一般都用Map<String, SoftReference<Bitmap>>结构容器来缓存Bitmap对象,这样在内存不太充足时,垃圾回收器会将软引用对象释放.
但从2.3开始,垃圾回收器可能在正常情况下就回收软引用对象,这样会降低缓存的效果
3). Android的v4兼容包中提供了LruCache来做缓存容器,它的基本原理为:
把最近使用的对象用强引用存储在 LinkedHashMap中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除
private LruCache<String, Bitmap> mMemoryCache; //缓存Bitmap的容器 @Override protected void onCreate(Bundle savedInstanceState) { // 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。 // LruCache通过构造函数传入缓存值,以KB为单位。 int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); // 使用最大可用内存值的1/8作为缓存的大小。 int cacheSize = maxMemory / 8; mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { // 重写此方法来衡量每张图片的大小,默认返回图片数量。 return bitmap.getByteCount() / 1024; } }; } public void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (getBitmapFromMemCache(key) == null) { mMemoryCache.put(key, bitmap); } } public Bitmap getBitmapFromMemCache(String key) { return mMemoryCache.get(key); } |
3. 主动释放bitmap对象
if(!bmp.isRecycle() ){ bmp.recycle() // 回收图片所占的内存 system.gc() //提醒系统及时回收} |
4. 设置加载图片的色彩模式:
Android中有四种,分别是:
ALPHA_8:每个像素占用1byte内存
ARGB_4444:每个像素占用2byte内存
ARGB_8888:每个像素占用4byte内存(默认)
RGB_565:每个像素占用2byte内存
默认的模式显示的图片质量是最高的,但也是占用内存最大的,而如果使用ARGB_4444模式,占用的内存就会减少为1/2,但显示效果差别不明显
BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.ARGB_4444; Bitmap img = BitmapFactory.decodeFile("/sdcard/1.png",options); |
5. 相关优秀Blog
1. Android高效加载大图、多图解决方案,有效避免程序OOM
4. 图片优化实例: Android照片墙应用实现,再多的图片也不怕崩溃
十二、面试技术12. 单例模式与工厂模式
1. 单例模式
1). 懒汉式
2). 饿汉式
2. 工厂模式
1). 简单工厂
2). 工厂方法模式与抽象工厂模式:可以在网上找找了解一下就可以了
十三、面试技术13. 常用的三种排序算法
三、面试技术13.常用的三种排序算法
1. 冒泡排序
1)基本思想:
在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,
让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。
2). 实例:
3). 代码实现:
2. 选择排序
1). 基本思想:
在要排序的一组数中,选出最小的一个数与第一个位置的数交换;
然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止。
2). 实例:
3). 代码实现:
3. 插入排序
1). 基本思想:
在要排序的一组数中,假设前面(n-1) [n>=2]个数已经是排好顺序的,现在要把第n个数插到前面的有序数中,
使得这n个数也是排好顺序的。如此反复循环,直到全部排好顺序。
2). 实例:
3). 代码实现:
十四、面试技术14. 常用的几种数据结构
1. 数组(Array)与链表(Link)的区别:
1). 都是内存真实的数据存储结构
2). 数据结构特点:
数组是一块连续的内存,通过下标来索引数组中的某个元素数据
链表并不是一块连续的内存黄区域,它是通过链表中的一个元素对象保持着下一个元素对象的引用来关联的
3). 创建结构对象:
数组必须指定初始化大小,而且不能自动扩容
链表不用指定大小,它的大小是操作元素对象时动态产生的
4). 添加/删除数据
数组在添加/删除时,很可能导致移动复制拷贝的问题,效率不太高
链表在添加/删除时,只需要修改引用就可以,效率很高
5). 查询
数组是通过下标来得到对应位置的数据的
链表只能通过从一端开始查找的方式获取数据
2. 队列(Queue)与栈(Stack)的区别:
1). 都是根据数组或链表来定义出的抽象数据结构
2). 队列先进先出,栈先进后出
3). 对插入和删除操作的"限定"
栈是限定只能在表的一端进行插入和删除操作的线性表。
队列是限定只能在表的一端进行插入和在另一端进行删除操作的线性表。
面试官:队列和栈有什么区别?
程序员:吃多了拉就是队列,吃多了吐就是栈.
十五、面试技术15. RAM与ROM
1). RAM: Random Access Memory : 随机存取存储器,即"运行时内存"
可以在RAM中对数据进行直接的读写操作(CRUD)
存取速度较快
停止运行后内存中的数据就会被清理
Android应用程序就是在RAM中运行的
2). ROM: Read Only Memory: 只读存储器
可以在ROM中保存和读取数据,但不能直接更新,如果需要更新,得先将数据加载到RAM中进行更新后写入ROM中
存取速度较慢
停止运行/断电后信息不丢失
Android的系统文件, APK安装文件等文件都是保存在ROM中(SD卡不是ROM)
3). 例子: Moto X的ROM与RAM数据
十六、面试技术16: Android中的进程与线程
1. 基本知识:
如果某个应用程序组件是第一次被启动,且这时应用程序也没有其他组件在运行,则Android系统会为应用程序创建一个包含单个线程的linux进程。默认情况下,同一个应用程序的所有组件都运行在同一个进程和线程里(叫做“main”主线程)。如果组件启动时,已经存在应用程序的进程了(因为应用程序的其它组件已经在运行了),则此组件会在已有的进程和线程中启动运行。不过,可以指定组件运行在其他进程里,也可以为任何进程创建额外的线程。
2. 进程的分类(根据进程的重要性,从高到低)和生命周期
1). 前台进程
用户当前操作所必须的进程。满足以下任一条件时,进程被视作处于前台:
o其中运行着正与用户交互的Activity(Activity对象的onResume()方法已被调用)。
o其中运行着被正与用户交互的activity绑定的服务Service。
o 其中运行着“前台”服务Service——服务以startForeground()方式被调用。
o其中运行着正在执行生命周期回调方法(onCreate()、onStart()或onDestroy())的服务Service。
o其中运行着正在执行onReceive()方法的BroadcastReceiver。
绝大情况下不会终止此类进程, 当内存不足以维持它们同时运行时——才会被终止
2). 可见进程
没有前台组件、但仍会影响用户在屏幕上所见内容的进程
o 其中运行着不在前台的Activity,但用户仍然可见到此activity(onPause()方法被调用了)
o 其中运行着被可见(或前台)activity绑定的服务Service。
可见进程被认为是非常重要的进程,除非无法维持所有前台进程同时运行了,它们是不会被终止的。
3). 服务进程
此进程运行着由startService()方法启动的服务
除非内存不足以维持所有前台、可见进程同时运行,系统会保持服务进程的运行。
4). 后台进程
包含目前用户不可见activity(Activity对象的onStop()方法已被调用)的进程
这些进程对用户体验没有直接的影响,系统可能在任意时间终止它们,以回收内存供前台进程、可见进程及服务进程使用
5). 空进程
不含任何活动应用程序组件的进程
保留这种进程的唯一目的就是用作缓存,以改善下次在此进程中运行组件的启动时间。
为了在进程缓存和内核缓存间平衡系统整体资源,系统经常会终止这种进程
3. 关于线程
1). 应用程序启动时,系统会为它创建一个名为“main”的主线程,也称为"UI Thread"
2). UI Thread特点重要,组件的回调方法,事件的分发和回调, UI的更新都是在线程执行的
3). 一些长时间的工作(如联网)不能在UI Thread中执行,只能在分线程(worker Thread)中执行
十七、面试技术17:JSON
1. 什么是JSON?
1. JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式
2. 本质就是具有特定格式的字符串
3. JSON数据已经是客户端与服务器端交互的最常用的选择,已经很少使用xml来进行数据交互了
2. 格式
①.整体结构:
Json数组 : [ ]
Json对象: { }
②. Json数组的结构: [value1, value2, value3]
③. Json对象的结构: {key1:value1, key2:value2, key3:value3}
④. key的数据类型:字符串
⑤. value的数据类型:
数值
字符串
null
json数组
json对象
⑥.例子:
[{"name":"tom"}]: 对
["name":"tom"]: 不对
{"name":"Tom", "age":12}: 对
{"name":"Tom", "age":12, 23}: 不对
3. 解析:
①.解析技术:
1). Android原生API:编码麻烦
2). Gson框架:解析更方便,更高效
③.解析方向:
1). 将java对象(包含集合)转换为json格式字符串) [一般在服务器端用]
2). 将json格式字符串转换为java对象(包含集合) [一般在客户端用]
4. 比较json相对于xml的优势
①.同一数据量,使用json表达更小,这样能省流量,响应速度更快
②.解析json数据,编码更方便,效率更高
十八、面试技术18: ANR
理解:
Application Not Responding(应用没有响应)
原因:
程序在UI线程中对用户的操作响应执行的时间过长
具体原因分类:
按键或触摸事件在特定时间内无响应(大概5S在上)
BroadcastReceiver在特定时间内无法处理完成(大概10S以上)
Service在特定的时间内无法处理完成(大概20S以上)
解决
不要在UI线程中做长时间的事
耗时的操作放入单独的线程中处理
服务和广播接收器的生命周期回调方法都是UI线程中执行
十九、面试技术20_Android开发中的MVC模式
1. 什么是MVC?
1). 软件工程中的一种软件架构,全称为"Mode--View--Controller",即为"模型--视图--控制器"
2). MVC将交互式的应用程序分为3个组件:
①. Mode(模型) :模型层,包括实体模型与业务模型,主要作用是完成业务逻辑处理和数据封装
在Android中,对应实体类/业务类/DAO类
②. View(视图) :显示层(表示层),向用户显示信息
在Android中,对应布局和自定义控件类
③. Controller(控制器) :控制层,处理用户输入,并实现View层与Mode层的协同处理
在Android中,对应Activity
2. 理解MVC
1). 基本结构图
2). Android中的MVC结构:
①. M模型层:
com.atguigu.ms.bean: 实体类包
com.atguigu.ms.utils: 工具包(简单的业务逻辑处理)
com.atgiugu.ms.dao: 数据库操作
com.atguigu.ms.net: 网络请求
②. V视图层:
res下的layout, drawable, values等
自定义View类
③. C控制层:
Activity
其它应用组件
3. 使用MVC模式的好处/如果不用MVC会有什么问题?
1). 如果不用View层(布局)?
问题: 每个Activity都需要创建整个布局视图对象,一个是代码量更大,
另一个不易实现复用(布局是可以轻松实现复用的),还有就是修改更麻烦
2). 如果不用Mode层(实体Mode)?
问题: 当应用一个界面需要展示或处理很多数据时, 对应的实体类是必不可少的
3). 如果不用Mode层(非实体Mode)?
问题: 那需要将所有的逻辑处理的代码都写到Activity中,一个使Activity变得太臃肿,
另一个无法复用业务逻辑功能代码
二十、面试技术01_技术站点
1. 需要关注的技术网站
https://github.com/ : 开源项目托管中心, 开源项目集中营
https://github.com/xfzhang : 张晓飞老师的github
https://github.com/zxfjd3g/android-open-project : 开源项目分类列表
http://stackoverflow.com/ : 国外知名分类问答网站
http://www.csdn.net/ : CSDN(汇集众多牛人博客)
http://www.eoeandroid.com/ : eoe安卓社区
http://www.apkbus.com/ : 安卓巴士
http://www.oschina.net/ : 开源中国社区
http://www.23code.com/ : Android经典开源项目收集站点
2. 不错的个人博客站点
http://blog.csdn.net/xiaanming
http://blog.csdn.net/guolin_blog
http://blog.csdn.net/lmj623565791/article/list/1
http://blog.csdn.net/singwhatiwanna
http://blog.csdn.net/bboyfeiyu
http://blog.csdn.net/aigestudio
http://blog.csdn.net/androiddevelop?viewmode=contents