1,Handler机制和底层实现
答:处理程序机制的原理
Android的Handler机制(也有人叫消息机制)目的是为了跨线程通信,也就是多线程通信。之所以需要跨线程通信是因为在Android中主线程通常只负责UI的创建和修改,子线程负责网络访问和耗时操作,因此,主线程和子线程需要经常配合使用才能完成整个Android功能。Handler机制可以近似用图1展示.MainThread代表主线程,newThread代表子线程。
MainThread 是Android系统创建并维护的,创建的时候系统执行了Looper.prepare();方法,该方法内部创建了MessageQueue消息队列(也叫消息池),该消息队列是消息的消息的容器,用于存储通过处理程序发送过来的Message.MessageQueue是Looper对象的成员变量,Looper对象通过ThreadLocal绑定在MainThread中。因此我们可以简单的这么认为:MainThread拥有唯一的一个Looper对象,该Looper对象有用唯一的MessageQueue对象,MessageQueue 对象可以存储多个
Message。MainThread中需要程序员手动创建Handler对象,并覆写Handler中的handleMessage(Message msg)方法,该方法将来会在主线程中被调用,在该方法里一般会写与UI修改相关的代码。
MainThread创建好之后,系统自动执行了Looper.loop();方法,该方法内部开启了一个“死循环”不断的去之前创建好的MessageQueue中取消息。如果一有消息进入MessageQueue,那么马上会被Looper.loop();取出来,取出来之后就会调用之前创建好的句柄对象的handleMessage(Message)方法.newThread线程是我们程序员自定新出来的子线程。在该子线程中处理完我们的“耗时”或者网络访问任务后,调用主线程中的处理程序对象的sendMessage(msg)方法,该方法一被执行,内部将就绪msg添加到了主线程的 MessageQueue队列中,这样就成为了Looper。 loop()的盘中餐了,等待着被消费。这是一个很复杂的过程,但是Android显然已经将这种模式给封装起来了,就叫Handler机制。我们使用时只需要在主线程中创建Handler ,并覆写handler中的handleMessage方法,然后在子线程中调用处理程序的se ndMessage(MSG)方法即可
2,Handler、Thread和HandlerThread的区别
3,handler发消息给子线程,looper怎么启动?
4,关于Handler,在任何地方new Handler都是什么线程下?
答:Looper.prepare()方法是将当前线程绑定一个looper实例,并存储在TreadLocal中,一个线程只有一个looper对象。
5,ThreadLocal原理,实现及如何保证Local属性?
6,请解释下在单线程模型中Message、Handler、Message Queue、Looper之间的关系
答:简单的说,Handler获取当前线程中的looper对象,looper用来从存放Message的MessageQueue中取出Message,再由Handler进行Message的分发和处理.
Message Queue(消息队列):
用来存放通过Handler发布的消息,通常附属于某一个创建它的线程,可以通过Looper.myQueue()得到当前线程的消息队列Handler:
可以发布或者处理一个消息或者操作一个Runnable,通过Handler发布消息,消息将只会发送到与它关联的消息队列,然后处理该消息队列中的消息
是Handler和消息队列之间通讯桥梁,程序组件首先通过Handler把消息传递给Looper,Looper把消息放入队列。Looper也把消息队列里的消息广播给所有的
Handler:Handler接受到消息后调用handleMessage进行处理
Message:
消息的类型,在Handler类中的handleMessage方法中得到单个的消息进行处理。
在单线程模型下,为了线程通信问题,Android设计了一个Message Queue(消息队列), 线程间可以通过该Message Queue并结合Handler和Looper组件进行信息交换。
7,请描述一下View事件传递分发机制
8,Touch事件传递流程
9,事件分发中的onTouch 和 onTouchEvent有什么区别,又该如何使用?
10,View和ViewGroup分别有哪些事件分发相关的回调方法
11,View刷新机制
答:在Android 的布局体系中,父查看负责刷新,布局显示子查看;而当子查看需要刷新时,则是通知父查看来完成。这种处理逻辑在查看的代码中明确的表现出来:
12,View绘制流程
答:View的绘制基本分为onMeasure,onLayout,onDraw过程
13,自定义控件原理
答:如何把自定义控件的原理落实到代码上
步骤
1)自定义属性的声明与获取
分析需要的自定义属性;
在res/values/attrs.xml中进行声明;
在Layout.xml文件中进行使用
在View的构造方法中获得我们自定义的属性/声明的属性;
TypedArray 获取所有的自定义属性的集合,然后进行遍历;
2)测量onMeasure()
测量模式和测量值,有三种测量模式
EXACTLY:一般是设置了明确的值或者是MATCH_PARENT
AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT
UNSPECIFIED:表示子布局想要多大就多大,ListView ScrollView
MeasureSPEC
主要用于计算视图的大小,即视图的宽度和长度;
子类可以覆写onMeasure()方法实现自己的计算视图大小的方式,并通过setMeasuredDimension(width, height)保存计算结果;
3)布局onLayout()
自定义ViewGroup才有的方法,继承自VierGroup,自定义View没有的;编写继承自View/VierGroup的子类(TextView、LinearLayout等)
用于控制控件在屏幕中显示的位置;
4)绘制onDraw()调用Canvas里面的api进行绘制操作;
draw操作利用前两部得到的参数,将视图显示在屏幕上;
5)onTouchEvent()
触摸事件的实现,响应用户操作。
6)onInterceptTouchEvent()
事件分发和拦截,只有自定义ViewGroup才有的方法;
在布局文件中使用时,使用全类名;
14,自定义View如何提供获取View属性的接口?
答:
15,Android代码中实现WAP方式联网
16,AsyncTask机制
17,AsyncTask原理及不足
18,如何取消AsyncTask?
19,为什么不能在子线程更新UI?
答:1、在子线程中是不能进行UI更新的,而可以更新的结果只是一个幻像:因为子线程代码执行完毕了,又自动进入到了主线程,执行了子线程中的UI更新的函数栈,这中间的时间非常的短,就让大家误以为分线程可以更新UI。如果子线程一直在运行,则子线程中的UI更新的函数栈 主线程无法获知,即无法更新
2、只有极少数的UI能,因为开辟线程时会获取当前环境,如点击某个按钮,这个按钮响应的方法是开辟一个子线程,在子线程中对该按钮进行UI 更新是能及时的,如换标题,换背景图,但这没有任何意义
PS:个人认为其他的原因。
3.UI是非线程安全的,主线程和子线程同时更新UI的话会导致错误,如UI错乱之类的。
4.UI更新是很耗性能的,更别说为了线程安全加锁了,最简单的方法就是更新UI的操作放到一个线程中,即主线程;
20,ANR产生的原因是什么?
答:1.在5秒内没有响应输入的事件(例如,按键按下,屏幕触摸)
2.BroadcastReceiver在10秒内没有执行完毕
3.service是20秒
造成以上两点的原因有很多,比如在主线程中做了非常耗时的操作,比如说是下载,io异常等。
第二:如何避免ANR?
1、运行在主线程里的任何方法都尽可能少做事情。特别是,Activity应该在它的关键生命周期方法(如onCreate()和onResume())里尽可能少的去做创建操作。(可以采用重新开启子线程的方式,然后使用Handler+Message的方式做一些操作,比如更新主线程中的ui等)
2、应用程序应该避免在BroadcastReceiver里做耗时的操作或计算。但不再是在子线程里做这些任务(因为 BroadcastReceiver的生命周期短),替代的是,如果响应Intent广播需要执行一个耗时的动作的话,应用程序应该启动一个 Service。(此处需要注意的是可以在广播接受者中启动Service,但是却不可以在Service中启动broadcasereciver,关于原因后续会有介绍,此处不是本文重点)
3、避免在Intent Receiver里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。如果你的应用程序在响应Intent广 播时需要向用户展示什么,你应该使用Notification Manager来实现。
21,ANR定位和修正
答:根本原因是:主线程被卡了,导致应用在5秒时间未响应用户的输入事件。
很多种ANR错误出现的场景:
1) 主线程当中执行IO/网络操作,容易阻塞。
2) 主线程当中执行了耗时的计算。----自定义控件的时候onDraw方法里面经常这么做。
(同时聊一聊自定义控件的性能优化:在onDraw里面创建对象容易导致内存抖动---绘制动作会大量不断调用,产生大量垃圾对象导致GC很频繁就造成了内存抖动。)内存抖动就容易造成UI出现掉帧卡顿的问题
3) BroadCastReceiver没有在10秒内完成处理。
4) BroadCastReceiver的onReceived代码中也要尽量减少耗时的操作,建议使用IntentService处理。
5) Service执行了耗时的操作,因为service也是在主线程当中执行的,所以耗时操作应该在service里面开启子线程来做。
6) 使用AsyncTask处理耗时的IO等操作。
7) 使用Thread或者HandlerThread时,使用Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)或者java.lang.Thread.setPriority(int priority)设置优先级为后台优先级,这样可以让其他的多线程并发消耗CPU的时间会减少,有利于主线程的处理。
8) Activity的onCreate和onResume回调中尽量耗时的操作。
22,oom是什么?
答:程序申请内存过大,虚拟机无法满足我们,然后自杀了。这个现象通常出现在大图片的APP开发,或者需要用到很多图片的时候。通俗来讲就是我们的APP需要申请一块内存来存放图片的时候,系统认为我们的程序需要的内存过大,及时系统有充分的内存,比如1G,但是系统也不会分配给我们的APP,故而抛出OOM异常,程序没有捕捉异常,故而弹窗崩溃了
23,什么情况导致oom?
答:一、 Acitivity没有对栈进行管理,如果开启过多,就容易造成内存溢出
二、加载大的图片或者同时数量过多的图片的时候
三、数据库或者资源没有关闭
四、静态成员变量持有类的应用
五、非静态内部类持有外部类的引用,使用非静态内部类创建静态变量
六、单例引起内存泄露
七、Handler造成内存泄露
八、线程周期不可控
九、无线循环的属性动画引起内存泄露
十、listView等没有getView方法没有复用
十一、递归次数过多,也会导致内存溢出.
十二、频繁的内存抖动,也会造成OOM异常的发生,大量小的对象被平凡的创建,导致内存碎片,从而当需要分配内存的时候,虽然总体上还有内存分配,但是由于这些内存不是连续的,导致无法分配,系统就直接返回OOM了
24,有什么解决方法可以避免OOM?
答:
25,Oom是否可以try catch?为什么?
答:只有在一种情况下,这样做是可行的:
在try语句中声明了很大的对象,导致OOM,并且可以确认OOM是由try语句中的对象声明导致的,那么在catch语句中,可以释放掉这些对象,解决OOM的问题,继续执行剩余语句。
但是这通常不是合适的做法。
Java中管理内存除了显式地catch OOM之外还有更多有效的方法:比如SoftReference, WeakReference, 硬盘缓存等。
在JVM用光内存之前,会多次触发GC,这些GC会降低程序运行的效率。如果OOM的原因不是try语句中的对象(比如内存泄漏),那么在catch语句中会继续抛出OOM
26,内存泄漏是什么?
答:内存泄漏的定义: 对象不再被应用程序使用,但是垃圾回收器却不能移除它们,因为它们正在被引用。
27,什么情况导致内存泄漏?
答:1.对象内存过大
保存了多个好用内存过大的对象,造成内存超出限制。
2.资源释放
程序代码的问题,长期保持某些资源,如Context,Cursor,IO流的引用,资源得不到释放造成内存泄露。
3.static关键字的使用
static 是Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例,就可能会造成内存的泄露。
针对static的解决方案:
应该尽量避免static成员变量引用资源耗费过多的实例,比如Context.
Context尽量使用ApplicationContext的生命周期比较长,引用它不会出现内存泄露。
使用WeakReference代替强引用。比如可以使用WeakReference<Context> mContext;
4.线程导致内存溢出
线程产生内存泄露的主要原因在于线程生命周期的不可控。如当我们切换横竖屏的时候,一般会重新创建Activity,老的Activity应该被销毁。但是此时我们在子线程中正在进行耗时的操作,老的Activity不会被销毁,这个时候就会出现内存泄露。
28,如何防止线程的内存泄漏?
29,内存泄漏场的解决方法
30,内存泄漏和内存溢出区别?
答:
内存泄漏
内存泄漏(memory leak): 是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
一般我们所说的内存泄漏是指堆内存的泄漏,堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完成之后必须显示释放内存。应用程序一般使用malloc、realoc、new等函数从堆中分配到一块内存块,使用完成后,程序必须负责相应的释放。在C中使用free(),C++中delete、delete[]、free()。而Java中由于垃圾回收机制不用手动释放。
如果内存不能释放,这块内存就不能再次使用,我们就说这块内存泄漏了。
内存溢出
内存溢出(out of memory):程序要求的内存,超出了系统所能分配的范围。如:我们用一个int型4字节的数据来装一个float型8字节的数据,就会产生内存溢出。不过我们在编程是可以强制类型转换int XX = (int)float;只取float 4字节数据给int.
以发生的方式来分类,内存泄漏可以分为4类:
1. 常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。
2. 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。
3. 一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄漏只会发生一次。
4. 隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。
从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到
31,LruCache默认缓存大小
32,ContentProvider的权限管理(解答:读写分离,权限控制-精确到表级,URL控制)
33,如何通过广播拦截和abort一条短信?
34,广播是否可以请求网络?
35,广播引起anr的时间限制是多少?
36,计算一个View的嵌套层级
答:
37,activity栈
38,android线程有没有上限?
答:没有上限的,因为资源都限制在这个进程里,你开多少线程都最多用这些资源。至于开多少最好,完全取决你的需求,合理开线程,不卡,高效是最终目标。
39,线程池有没有上限?
答:
40,ListView重用的是什么?
41,android为什么引入Parcelable?
42,有没有尝试简化Parcelable的使用?