Android面试题集锦(三)

2016.7.26更新...........................................................................

(56):ListView异步加载图片出现乱序的原因

        ListView在借助RecycleBin机制的帮助下,实现了一个生产者消费者的模式,不管有多少条数据需要显示,ListView中的子View其实来来回回就那么几个,移出屏幕的子View很快会被移入屏幕的数据重新利用起来,这就是RecycleBin机制,我们来说说产生乱序的原因,每当有新元素进入界面的时候就会回调getView方法,如果我们是异步加载图片的话,就会在getView方法中开启异步操作从网络上面获取图片,但是异步操作可能是比较耗时的,当我们快速滑动图片的时候可能会出现这样一种情况,某一位置上的元素进入屏幕之后开始从网络上请求图片,但是还没等图片加载到控件上的时候,它就已经被移出了屏幕,又根据ListView的工作机制,被移出屏幕的控件会很快被进入屏幕的元素重新利用起来,正是因为这个原因导致了图片乱序,因为如果这时候前面发起的网络请求正好得到回应的话,首先会将刚才请求的图片显示到控件上面,虽然他们的位置不同,但是共用同一个显示图片的控件,这时候新移进来的元素如果正好也发起一个网络请求获取图片的话,在图片下载结束就会将图片显示到当前控件上面,因此就出现了先显示一张图片后又显示一张图片的情况了;

        解决这个问题的方法有:

        在getView方法中对每个控件调用setTag方法,设置其标志,在需要用到控件的地方使用findViewWithTag获得控件,这种方式解决图片乱序的原理是:因为ListView中的控件都是可以重新的,当移出屏幕的控件重新被利用的时候会回调getView方法,在getView方法中会调用setTag方法为当前控件设置标志,那么这个标志将会覆盖掉原先设置在该控件上面的标志,这样每次使用findViewWithTag获取对应于旧的Tag的控件的时候就只能得到null了,而只有非null的时候我们才会显示图片到控件上面,这样就保证了不会显示旧的图片在控件上面的效果了;

(57):ListView优化方案

        关于ListView的优化是一个老生常谈的问题了,在这里我总结几点:

        (1):重用convertView,减少不必要view的创建,因为inflate操作是把xml实例化为相应的View实例,这个过程是属于IO操作的,相对来说比较耗时;

        (2):减少findViewById的次数,通过将xml文件中的元素封装成静态内部类的方式,通过view的setTag和getTag方法将view和对应的holder对象绑定在一起,减少不必要的findViewById次数,因为这个操作也是涉及到IO的;

        (3):如果涉及到加载图片之类比较耗时的操作,在用户快速滑动屏幕的时候禁止图片的加载,滑动停止的时候再去加载图片;

        (4):ListView中Item的布局层次越简单越好,主要为了避免布局太深带来的重绘太复杂问题;

        (5):尽量保证Adapter适配器的hasStables方法返回true,这样在调用notifyDataSetChanged()方法的时候,如果Item内容没有发生变化的话,ListView将不再重绘该ListView,以此达到优化;

        (6):使用RecycleView代替ListView,每个Item自己发生变动,ListView都会去调用notifyDataSetChanged方法更新所有的Item,未免有点太浪费性能了,而RecycleView可以实现每个Item的局部刷新,不再需要刷新所有的Item,同时引入了增加和删除的动画效果,在性能和定制上有很大改善;

        (7):尽量开启硬件加速功能;

(58):RecycleView与ListView的对比:

        (1):RecycleView是强制使用ViewHolder来减少findViewById的次数的,而ListView是推荐使用;

        (2):RecycleView没有OnItemClickListener,只设置了OnItemTouchListener监听器;

        (3):ListView做到了数据和视图的分离,RecycleView将视图和布局进一步分离,因而出现了LayoutManager,RecycleView只负责管理视图的重复利用,然后将布局的管理全权交给了LayoutManager,通过配置和切换LayoutManager就可以获得不同的布局效果,不像ListView那样被限制在垂直滚动布局样式;

        (4):在RecycleView中提供了ItemDecoration,可以在子视图基础上添加额外的视图,比如分割线等,而在ListView中就必须通过额外占用一个ViewType来提供视图了;

        (5):RecycleView支持子项目层次的动画效果,是通过ItemAnimation接口实现的,可以在Adapter中的数据发生变化的时候,通过调用Adapter的相关方法激活动画的产生;

(59):屏幕适配方案学习

        (1):为了确保你的布局能够自适应各种不同屏幕大小,应该在布局中使用"wrap_content"和"match_parent"来确定他的宽高,"wrap_content"相应的视图宽高会被设置成刚好能包括视图中内容的最小值,而使用"match_parent"则会让视图的宽高延伸至整个父布局;

        (2):使用RelativeLayout可以准确的控制子视图之间的位置关系,而LinearLayout只能简单的一个挨着一个排列,RelativeLayout允许布局的子控件之间使用相对定位的方式控制控件的位置;

        (3):使用Size限定符,能够根据屏幕的配置加载不同的布局文件,比如横屏和竖屏状态下,屏幕大小不同加载不同的布局文件,具体方法可以通过配置限定符来实现,配置限定符允许程序在运行时根据当前设备的配置自动加载合适的资源,比如在小屏幕状态下加载res/layout/....下的布局文件,在大屏幕下加载res/layout-large/.....下的布局文件,大屏幕下加载的是包含有large限定符的目录下的布局文件;

        (4):使用Size限定符有个比较头疼的问题就是里面的large到底多大才算大呢?这个大小我们是没法决定的,因而这时候我们可以通过Smallest-width限定符,Smallest-width限定符允许设定一个具体的最小值,这样就可以做到我们自己指定到底什么情况下进行布局切换了,具体实现是通过res/layout/.....和res/layout-sw600dp/.....来实现的,当屏幕大小超过600dp的时候就会调用res/layout-sw600dp/.....下面的布局文件,而小于600dp的时候则会调用res/layout/......下的布局文件了;

        (5):另外,我们可以使用Orientation限定符来让在同一屏幕大小的情况下,在横屏和竖屏的状态下可以加载不同的布局文件,具体实现就是使用Orientation限定符了,比如横屏状态可以使用res/layout-port/.....在竖屏的状态下使用res/layout-land/.....,当然为了简化,我们可以使用布局别名的方式来实现;

        (6):支持不同屏幕的大小往往意味着你的图片也要有自适应能力,我们可以通过使用Android提供的draw9patch工具来指定哪些区域在屏幕适配的时候可以被拉伸,哪些区域在适配的时候不可以被拉伸,使用draw9patch工具会生成.9.png类型的图片,他是特殊的png图片;

(60):内存泄露监测方法

        使用DDMS中的中的内存监测工具Heap,使用步骤如下:

        (1):启动eclipse,切换到DDMS视图,并且确保Devices视图和Heap视图都已经打开;

        (2):连接设备,确保设备处于"USB调试状态";

        (3):选中想要监测的进程

        (4):点击Devices视图最上面一排图标中的"Update Heap"图标;

        (5):点击Heap视图中的"Cause GC"按钮;

        (6):此时Heap视图中就会显示当前选中进程内存使用的详细情况了;

        说明:

        (1):点击"Cause GC"按钮相当于向虚拟机请求一次GC操作;

        (2):如何才能知道我们程序是否存在内存泄漏的可能性呢?方法是Heap视图中有一个Type叫做data object,即数据对象,也就是我们程序中存在的大量的类类型的对象,在data object一行中有一列"Total Size",其值就是当前进程中所有Java数据对象的内存总量,一般情况下,我们可以通过这个值来查看是否发生了内存泄漏;不断的操作当前应用,同时注意观察Total Size的值,正常情况下,这个值是维持在一个有限的范围的,如果代码存在没有被释放引用的情况,则data object的Total Size值在每次GC之后不会有明显的回落,随着操作次数的增多,Total Size的值会越来越大;

(61):Dalvik虚拟机的主要作用是什么呢?

        Dalvik虚拟机主要完成对象生命周期管理、内存回收、堆栈管理、线程管理、安全管理等等功能;

(62):什么是线程池,线程池的作用以及分类

        线程池的基本思想其实可以理解为是对象池,开辟一块内存空间,里面存放众多的线程,池中的线程调度由池管理器来处理,当有任务到来的时候,池管理器查看线程池中是否有可用线程存在,有的话按某种策略取出一个,执行完成之后将线程对象放回池中,这样可以避免反复创建线程对象所带来的性能开销,节省系统资源;

        单线程的弊端:(1):每次都要通过new Thread的方式创建线程,性能差;(2):线程缺乏统一管理,相互之间竞争,极有可能导致占用过多的系统资源;(3):缺乏更多可控功能,比如定时执行、定期执行、线程中断;

        线程池的好处:(1):重用存在的线程,减少线程对象的创建、消亡的开销;(2):能对线程数量进行有效控制,避免过多的线程进行系统资源的竞争,避免拥塞;(3):提供了定时执行、定期执行,并发数量控制功能;

        java通过Executor提供了四种线程池:

        (1):CachedThreadPool

        可缓存线程池,通过Executor的newCachedThreadPool方法创建,他是一个线程数量不定的线程池,最大线程数量可以无限大,当线程池中的线程都处于活动状态的时候,线程池会创建新的线程来处理新任务,否则会利用空闲的线程来处理新任务,线程池中的空闲线程都有超时机制,这个超时时长为60s,超过60s的闲置线程就会被回收;

        (2):FixedThreadPool

        固定大小线程池,可以通过Executor的newFixedThreadPool方法来创建,他是一种固定大小的线程池,当线程处于空闲状态时,他们不会被回收,除非线程池被关闭,当所有的线程都处于活动状态时,新任务会处于等待状态,等待线程池中的线程空闲出来;

        (3):ScheduledThreadPool

        定时或者固定周期线程池,可以通过Executor的newScheduledThreadPool方法创建,他的核心线程数目是固定的,但是非核心线程的数量却没有限制,并且当非核心线程闲置时会被立即回收的,这种线程池主要用于执行定时任务和具有固定周期的重复任务;

        (4):SingleThreadPool

        单线程的线程池,通过Executor的newSingleThreadExecutor方法创建,这类线程池中只有一个核心线程,他确保所有的任务都在一个线程中按顺序执行,它存在的意义在于同一所有的外界任务到一个线程中,这使得这些任务之间不需要处理线程同步问题;

(63):自定义View的步骤

        (1):继承View或者View的子类;
        (2):在res/values/attrs.xml中新增节点自定义属性;
        (3):将自定义View放到布局文件中,注意命名空间的格式;
                xmlns:custom="http://schemas.android.com/apk/res/[自定义View所在的包路径]"

        (4):在xml文件中设定指定属性值;

        (5):获取自定义属性,并且为View设置必要的事件;

        (6):进行自定义的绘制,实现onDraw方法,当然你也可以实现onMeasure进行自定义测量;

        (7):覆写onTouch等事件相关方法;

        (8):优化你自定义的View,比如为了减低刷新频率你可以在onDraw中调用四个参数的invalidate方法,因为无参的invalidate方法刷新的是整个View树,而四个参数的invalidate方法刷新的是指定部分的View;

(64):Android布局优化建议

        (1):尽量多的使用RelativeLayout,不要使用绝对布局AbsoluteLayout;

        (2):将可复用的组件抽离出来,并且通过<include />标签使用;

        (3):使用<ViewStub />标签来加载一些不常用的布局;

        (4):使用<merge />标签减少布局的嵌套层次;

(65):Scrollview怎么判断是否滑到底部

        可以通过两种方式来实现,可以在onScrollChanged监听器中进行监听,也可以在onTouchListener监听器中进行监听,但是判断是否滑动到底部的代码是一致的:

        View contentView = getChildAt(0);

        contentView.getMeasureHeight() <= getScrollY()+getHeight();//表示没有滑动到底部的判断条件

        首先获得ScrollView的child View,因为ScrollView只允许有一个child view,接着通过contentView.getMeasureHeight()得到子View的高度,通过getScrollY()得到Y轴的滚动距离,getHeight()得到ScrollView的高度,当子View的高度小于等于Y轴滚动距离+ScrollView自身高度的时候,说明没有滑动到底部,大于的时候说明滑动到底部;

        滑动到顶部的判断条件是:getScrollY == 0

        那么判断是否滑动到底部有两种判断方式:onScrollTouchListener和onTouchListener,两者有什么区别呢?

        第一种方式精确度比较高,但是因为他的回调方法onScrollChanged总是不停的被调用,那么会导致判断多次被执行;

        第二种方式虽然不会频繁的多次调用,但是可能会一次也不执行,比如你快速滑动,在还未达到底部的时候手指已经松开,但是由于惯性会滚动到底部,因为你是在ACTION_UP里面进行是否到达底部的判断,那么可能就会导致本来已经到底部却判断出来没到的误判;

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值