Android 面试常见问题汇总
1.Android的系统架构答:android系统架构分从下往上为Linux内核层、运行库、应用程序框架层和应用程序层。Linux内核层:负责硬件的驱动程序、网络、电源、系统安全以及内存管理等功能。运行库和androidruntion:运行库:即c/c++函数库部分,大多数都是开放源代码的函数库,例如webkit,该函数库负责android网页浏览器的运行;例如标准的c函数库libc、ope
1.Android的系统架构
答:android系统架构分从下往上为Linux内核层、运行库、应用程序框架层和应用程序层。
Linux内核层:负责硬件的驱动程序、网络、电源、系统安全以及内存管理等功能。
运行库和androidruntion:运行库:即c/c++函数库部分,大多数都是开放源代码的函数库,例如webkit,该函数库负责android网页浏览器的运行;例如标准的c函数库libc、openssl、sqlite等,当然也包括支持游戏开发的2dsgl和3dopengles,在多媒体方面有mediaframework框架来支持各种影音和图形文件的播放与显示,如mpeg4、h.264、mp3、aac、amr、jpg和png等众多的多媒体文件格式。Androidruntion负责解释和执行生成的dalvik格式的字节码
应用软件架构:java应用程序开发人员主要是使用该层封装好的api进行快速开发的。
应用程序层:该层是java的应用程序层,android内置的googlemaps、email、IM、浏览器等,都处于该层,java开发人员工发的程序也处于该层,而且和内置的应用程序具有平等的地位,可以调用内置的应用程序,也可以替换内置的应用程序
2. Handler机制是什么?
答:Handler是Android官方给我们提供的一套更新UI线程的机制,也是一套消息处理机制,可以通过Handler来处理消息,更新UI等。
然后Handler机制大约是这样的:首先Handler发送一个消息MessageQueue里面,然后通过Looper的loop方法进行无限循环,如果产生了一个新消息,那么就调用handler的handlermessage方法进行余下的操作。
android提供了handler和looper来满足线程间的通信。Handler先进先出原则。looper用来管理特定线程内对象之间的消息交换(message Exchange).
1)looper:一个线程可以产生一个looper对象,由它来管理此线程里的message queue(消息队列)
2)handler:你可以构造一个handler对象来与looper沟通,以便push新消息到messagequeue里;或者接收looper(从messagequeue里取出)所送来的消息。
3)messagequeue:用来存放线程放入的消息。
Handler获取当前线程中的looper对象,looper用来从存有Message的Message Queue里取出message,再由Handler进行message的分发和处理。
3.请解释下在单线程模型中Message、Handler、Message Queue、Looper之间的关系
拿主线程来说,主线程启动时会调用Looper.prepare()方法,会初始化一个Looper,放入Threadlocal中,
接着调用Looper.loop()不断遍历Message Queue,Handler的创建依赖与当前线程中的Looper,
如果当前线程没有Looper则必须调用Looper.prepare()。Handler , sendMessage到MessageQueue,Looper不断
从MessageQueue中取出消息,回调handleMessage方法。
Handler获取当前线程中的looper对象,looper用来从存放Message的MessageQueue中取出Message,再有Handler进行Message的分发和处理.
Message Queue(消息队列):用来存放通过Handler发布的消息,通常附属于某一个创建它的线程,可以通过Looper.myQueue()得到当前线程的消息队列
Handler:可以发布或者处理一个消息或者操作一个Runnable,通过Handler发布消息,消息将只会发送到与它关联的消息队列,然也只能处理该消息队列中的消息
Looper:是Handler和消息队列之间通讯桥梁,程序组件首先通过Handler把消息传递给Looper,Looper把消息放入队列。Looper也把消息队列里的消息广播给所有的
Handler:Handler接受到消息后调用handleMessage进行处理
Message:消息的类型,在Handler类中的handleMessage方法中得到单个的消息进行处理
在单线程模型下,为了线程通信问题,Android设计了一个Message Queue(消息队列), 线程间可以通过该Message Queue并结合Handler和Looper组件进行信息交换。
4. 只能在UI线程里面更新界面吗?
答:不一定,之所以子线程不能更新界面,是因为Android在线程的方法里面采用checkThread进行判断是否是主线程,而这个方法是在ViewRootImpl中的,这个类是在onResume里面才生成的,因此,如果这个时候子线程在onCreate方法里面生成更新UI,而且没有做阻塞,就是耗时多的操作,还是可以更新UI的。
5. Android子线程更新UI的方式有几种?
答:一般情况下,我们都采用Handler的方式进行更新UI,当然,代码层的实现有不同的方法,比如可以使用Handler的post方法进行更新UI,或者用Handler的sendMessage方法进行更新UI,或者通过View的post方法进行更新,还有一个是runOnUIThread也是可以进行更新的。但这些本质上还是通过Handler进行子线程的更新。
6.怎么在主线程中通知子线程?这样做有什么好处?
答:可以利用HandlerThread进行生成一个子线程的Handler,并且实现handlerMessage方法,然后在主线程里面也生成一个Handler,然后通过调用sendMessage方法进行通知子线程。同样,子线程里面也可以调用sendMessage方法进行通知主线程。这样做的好处比如有些图片的加载啊,网络的访问啊可能会比较耗时,所以放到子线程里面做是比较合适的。
7.异步处理有几种方式?
答:可以采用Handler的形式,利用官方提供的HandlerThread类进行声明一个子线程的Handler,然后在Handler里面就可以做耗时的操作了,注意,需要在子线程中提前准备好Looper对象,可以使用Looper.prepare方法,最后需要使用Looper.loop方法进行循环。还可以直接用AsyncTask进行操作,一般会构造函数有三个参数,一个是传入参数,一个是进度,还有一个是结果,然后一般会实现一些方法,比如:execute用来用来执行一个异步任务,就是实现的AsyncTask的类调用的,还有onPreExecute,就是调用后立即执行,doInBackground,在onPreExecute完成后立即执行,用于执行较为费时的操作,此方法将接收输入参数和返回计算结果。onProgressUpdate,可以直接将进度信息更新到UI界面上。onPostExecute,后台结束时候调用的方法,会返回结果。注意,不能执行多次,不然会报错,且必须在UI线程中调用,至于上面提到的方法都不要手动调用。doInBackground方法中不可以更新UI。
(因为Android3.0以后必须要求网络访问在子线程中,不然会抛NetworkOnMainThreadException异常,尝试ANR现象(5秒不响应现象))
8.Activity的启动模式有几种?
一个activity有四种启动模式:
Standed:标准模式,默认的启动模式是standard, 就是每创建一个Activity就会将其压入任务栈栈顶,不管是否存在。一调用startActivity()方法就会产生一个新的实例。
SingleTop:如果已经有一个实例位于activity栈顶,就不产生新的实例,而只是调用activity中的newInstance()方法。如果不位于栈顶,会产生一个新的实例。
singleTask:会在一个新的task中产生这个实例,以后每次调用都会使用这个,不会去产生新的实例了。
SingleInstance:这个和singleTask基本一样,只有一个区别:在这个模式下的activity实例所处的task中,只能有这个activity实例,不能有其他实例
9.为什么在Service中创建子线程而不是Activity中?
答:因为假如在Activity中创建子线程的话,当Activity销毁的时候,这个时候重新再调用该Activity就会重新走新的生命周期,这个时候就无法再重新获取到刚才的子线程,而且如果在一个Activity中创建子线程,另一个Activity也无法操作该子线程,但是Service就不一样,所有的Activity都可以和Service关联,即使是Activity被销毁了,只要再重新建立联系就好了,所以,一般后台任务都是通过Service去控制的。
10.ContentProvide的使用方法?
答:首先我们应该为数据创建一个类,集成ContentProvide,然后在配置文件里面配置这个ContentProvide,接着需要准备好数据库操作类和数据库访问类,初始化URI,URI的形式为:content://包名,然后便是在数据库操作类中实现操作方法,这个时候就可以提供数据共享服务了。
11.动画有哪两类?各有什么特点?
答:tween补间动画,通过指定View的初末状态和变化时间、方式,对View的内容完成一系列的图形变换来实现动画效果。 Alpha Scale Translate Rotate。
frame帧动画,AnimationDrawable 控制 animation-list xml布局。
PropertyAnimation 属性动画。
在Android3.0以前,动画有两种类型,一种是补间动画,即tween,他指的是通过自身的变形达到的效果,比如说透明度的变化,放大缩小等,还有一种是帧动画,即Frame,是通过一针一针的对图片进行连贯起来播放的,Android3.0的时候定义了一个属性动画,即PropertyAnimation,指的是控件的真实移动,就是不断的改变某些属性的值进行的。具体可以通过实现ValueAnimator等类进行实现。
两种,一种是tween动画,一种是frame动画。tween动画,这种实现方式可以使视图组件移动,放大或缩小以及产生透明度的变化。frame动画,传统的动画方法,通过顺序的播放排列好的图片来实现,类似电影。
12.Asset和res目录的区别?
答:Asset不会在R文件里面生成一个ID,所以它不能直接用R文件来调用,这就说明要读取Asset目录下的文件需要指定文件的目录,可以通过AssetManager类来访问。res会自动在R文件里面生成id,直接可以用R.的方式进行访问资源。
13. assest文件夹里放文件,对于文件的大小有没有限制?
assets目录更像一个附录类型的目录,Android不会为这个目录中的文件生成ID并保存在R类当中,
因此它与Android中的一些类和方法兼容度更低。同时,由于你需要一个字符串路径来获取这个目
录下的文件描述符,访问的速度会更慢。但是把一些文件放在这个目录下会使一些操作更加方便,
比方说拷贝一个数据库文件到系统内存中。要注意的是,你无法在Android XML文件中引用到assets
目录下的文件,只能通过AssetManager来访问这些文件。数据库文件和游戏数据等放在这个目录下
是比较合适的。另外,网上关于assets和raw的资料都千篇一律了,因此关于这两者中单个文件大小
不能超过1M的错误描述也在传播,即如果读取超过1M的文件
会报”Data exceeds UNCOMPRESS_DATA_MAX (1314625 vs 1048576)”的IOException,
还引申出种种解决方案。个人认为不应该有这样的限制,为了验证这个说法写了个Demo,
发现将近5M的压缩包在assets和raw中都能正常访问,因此在这里纠正一下,理论上只要打包不
超过Android APK 50M大小的限制都是没有问题的。当然了,不排除是Android很早期的时候因
为设备硬件原因aapt在编译的时候对这两个文件夹大小做出了限制,如果是这样,较新版的ADT
应该不会出现这种情况。
14.Android怎么优化启动速度?
答:因为Android启动应用程序一般分为两种,一种是冷启动,就是要启动的应用程序没有后台进程的启动,这个时候需要重新分配一个进程给他,所以这个时候会先初始化Application类,再创建和初始化MainAcitvity 类,最后显示到界面上,还有一种是热启动,就是后台还有该应用的进程,比如说按下的home键或者返回键,虽然表面上退出了,但是在任务栈里面仍然还存在的,这个时候就不需要再初始化Application类了,只要重新初始化MainActivity了。因为大多数应用的启动都是冷启动(用户习惯将应用程序在任务栈中删除),所以这个时候可以采取这几个步骤,比如尽量不在Application的构造器,attachBaseContext方法和onCreaete方法中做过多的耗时操作,将一些数据预取放在异步线程中,可以采Callback的方式。优化MainActivity,尽量不要在MainActivity的onCreate,onStart和onResume等方法里面做过多的耗时操作。
15.Android怎么加快Activity的显示速度?
答:首先因为Activiy的显示是在这几个生命周期之间的,onCreate,onStart,和onResume,这个时候我们需要将我们需要初始化的数据分类,比如说我们将一些只需要初始化的一次的数据放到onCreate中,尽量不要在onCreate中做耗时的操作,然后将需要加载比较长时间的数据放到onResume中,可以利用handler的机制进行更新UI,或者放到AsyncTask逐个显示,然后可以设置一些动画进行显示,如果这个时候有许多数据都是一次显示的, 那么可以在onCreate里面进行标记,并且在onResume里面判断是否需要初始化,初始化完成以后就立刻false掉,这样就可以避免多次初始化了,也可以提升Activity的显示速度。
16.产生Android内存泄漏的原因有什么?
- 资源对象没关闭。
如Cursor、File等资源。他们会在finalize中关闭,但这样效率太低。容易造成内存泄露。
SQLiteCursor,当数据量大的时候容易泄露 - 使用Adapter时,没有使用系统缓存的converView。
- 即时调用recycle()释放不再使用的Bitmap。适当降低Bitmap的采样率,如:
BitmapFactory.Options options = newBitmapFactory.Options();
options.inSampleSize = 2;//图片宽高都为原来的二分之一,即图片为原来的四分之一
Bitmap bitmap =BitmapFactory.decodeStream(cr.openInputStream(uri), null, options); preview.setImageBitmap(bitmap); - 使用application的context来替代activity相关的context。
尽量避免activity的context在自己的范围外被使用,这样会导致activity无法释放。 - 注册没取消造成内存泄露如:广播
- 集合中的对象没清理造成的内存泄露我们通常把一些对象的引用加入到了集合中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。
17.怎么处理Android内存泄漏?
答:可以使用更加轻量的数据结构,比如ArrayMap而不是HashMap,避免在Android里面使用enum,减小Bitmap对象的内存占用,可以采用缩放比例,或者使用缓存缩略图的方式。
18 图片加载
对于加载图片,大家都不陌生,一般为了尽可能避免OOM都会按照如下做法:
对于图片显示:根据需要显示图片控件的大小对图片进行压缩显示。
如果图片数量非常多:则会使用LruCache等缓存机制,将所有图片占据的内容维持在一个范围内。
其实对于图片加载还有种情况,就是单个图片非常巨大,并且还不允许压缩。比如显示:世界地图、清明上河图、微博长图等。
那么对于这种需求,该如何做呢?
首先不压缩,按照原图尺寸加载,那么屏幕肯定是不够大的,并且考虑到内存的情况,不可能一次性整图加载到内存中,所以肯定是局部加载,那么就需要用到一个类:
BitmapRegionDecoder
19.android dvm 的进程和Linux的进程,应用程序的进程是否为同一个概念
答:dvm是dalivk虚拟机。每一个android应用程序都在自己的进程中运行,都拥有一个dalivk虚拟机实例。而每一个dvm都是在linux的一个进程。所以说可以认为是同一个概念。
20.android view的刷新:
答:Android中对View的更新有很多种方式,使用时要区分不同的应用场合。我感觉最要紧的是分清:多线程和双缓冲的使用情况。
1).不使用多线程和双缓冲
这种情况最简单了,一般只是希望在View发生改变时对UI进行重绘。你只需在Activity中显式地调用View对象中的invalidate()方法即可。系统会自动调用 View的onDraw()方法。
2).使用多线程和不使用双缓冲
这种情况需要开启新的线程,新开的线程就不好访问View对象了。强行访问的话会报:android.view.ViewRoot$CalledFromWrongThreadException:Only the originalthread that created a view hierarchy can touch its views.
这时候你需要创建一个继承了android.os.Handler的子类,并重写handleMessage(Messagemsg)方法。android.os.Handler是能发送和处理消息的,你需要在Activity中发出更新UI的消息,然后再你的Handler(可以使用匿名内部类)中处理消息(因为匿名内部类可以访问父类变量,你可以直接调用View对象中的invalidate()方法 )。也就是说:在新线程创建并发送一个Message,然后再主线程中捕获、处理该消息。
3).使用多线程和双缓冲
Android中SurfaceView是View的子类,她同时也实现了双缓冲。你可以定义一个她的子类并实现SurfaceHolder.Callback接口。由于实现SurfaceHolder.Callback接口,新线程就不需要android.os.Handler帮忙了。SurfaceHolder中lockCanvas()方法可以锁定画布,绘制玩新的图像后调用unlockCanvasAndPost(canvas)解锁(显示),还是比较方便得。
21. Android中实现view的更新有几种方法?
你知道吗?其实android中实现view的更新有两种方法,一种是invalidate,另一种是postInvalidate,
其中前者是在UI线程自身中使用,而后者在非UI线程中使用。
Android提供了Invalidate方法实现界面刷新,但是Invalidate不能直接在线程中调用,
因为他是违背了单线程模型:Android UI操作并不是线程安全的,并且这些操作必须在UI线程中调用。
鉴于此,如果要使用invalidate的刷新,那我们就得配合handler的使用,使异步非ui线程转到ui线程中调用,
如果要在非ui线程中直接使用就调用postInvalidate方法即可,这样就省去使用handler的烦恼。
22. requestLayout在什么时候用呢?
当view确定自身已经不再适合现有的区域时,该view本身调用这个方法要求parent view(父类的视图)
重新调用他的onMeasure onLayout来重新设置自己位置。特别是当view的layoutparameter发生改变,
并且它的值还没能应用到view上时,这时候适合调用这个方法。
22. 说说mvc模式的原理,它在android中的运用
Android的官方建议应用程序的开发采用mvc模式。何谓mvc?
mvc是model,view,controller的缩写,mvc包含三个部分:
模型(model)对象:是应用程序的主体部分,所有的业务逻辑都应该写在该层。
视图(view)对象:是应用程序中负责生成用户界面的部分。也是在整个mvc架构中用户唯一可以看到的一层,接收用户的输入,显示处理结果。
控制器(control)对象:是根据用户的输入,控制用户界面数据显示及更新model对象状态的部分,控制器更重要的一种导航功能,想用用户出发的相关事件,交给m哦得了处理。
Android鼓励弱耦合和组件的重用,在android中mvc的具体体现如下:
1)视图层(view):一般采用xml文件进行界面的描述,使用的时候可以非常方便的引入,当然,如何你对android了解的比较的多了话,就一定 可以想到在android中也可以使用javascript+html等的方式作为view层,当然这里需要进行java和javascript之间的通 信,幸运的是,android提供了它们之间非常方便的通信实现。
2)控制层(controller):android的控制层的重 任通常落在了众多的acitvity的肩上,这句话也就暗含了不要在acitivity中写代码,要通过activity交割model业务逻辑层处理, 这样做的另外一个原因是android中的acitivity的响应时间是5s,如果耗时的操作放在这里,程序就很容易被回收掉。
3)模型层(model):对数据库的操作、对网络等的操作都应该在model里面处理,当然对业务计算等操作也是必须放在的该层的。
23.什么是ANR,如何避免?
答:ANR的定义:
在android上,如果你的应用程序有一段时间响应不移灵敏,系统会向用户提示“应用程序无响应”(ANR:application Not Responding)对话框。因此,在程序里对响应性能的设计很重要,这样,系统不会显示ANR给用户。
如何避免:
首先来研究下为什么它会在android的应用程序里发生和如何最佳构建应用程序来避免ANR.
android应用程序通常是运行在一个单独的线程(例如:main)里,这就意味你的应用程序所做的事情如果在主线程里占用了大长时间的话,就会引发ANR对话框,因为你的应用程序并没有给自己机会来处理输入事件或者Intent广播。
因此,运行在主线程里的任何访求都尽可能少做事情。特别是,activity应该在它的关键生命周期方法(onCreate()和onResume())里尽可能少的去作创建操作。潜在的耗时操作,例如网络或数据库操作,或者高耗时的计算如改变位图尺寸,应该在子线程里(或者以数据库操作为例,通过异步请求的方式)来完成。然而,不是说你的主线程阻塞在那里等待子线程的完成—也不是调用Thread.wait()或者Thread.sleep()。替代的方法是:主线程应该为子线程提供一个Handler,以便完成时能够提交给主线程。以这种方式设计你的应用程序,将能保证你的主线程保持对输入的响应性并能避免由5秒输入事件的超时引发的ANR对话框。这种做法应该在其它显示UI的线程里效仿,因为它们都受相同的超时影响。
IntentReceiver执行时间的特殊限制意味着它应该做:在后台里做小的、琐碎的工作,如保存设定或注册一个Notification。和在主线程里调用的其它方法一样,应用程序应该避免在BroadcastReceiver里做耗时的操作或计算,但也不是在子线程里做这些任务(因为BroadcastReceiver的生命周期短),替代的是,如果响应Intent广播需要执行一个耗时的动作的话,应用程序应该启动一个Service。顺便提及一句,你也应该避免在Intent Receiver里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。如果你的应用程序在响应Intent广播时需要向用户展示什么,你应该使用Notification Manager来实现。
一般来说,在应用程序里,100到200ms是用户能感知阻滞的时间阈值,下面总结了一些技巧来避免ANR,并有助于让你的应用程序看起来有响应性。
如果你的应用程序为响应用户输入正在后台工作的话,可以显示工作的进度(ProgressBar和ProgressDialog对这种情况来说很有用)。特别是游戏,在子线程里做移动的计算。如果你的程序有一个耗时的初始化过程的话,考虑可以显示一个Splash Screen或者快速显示主画面并异步来填充这些信息。在这两种情况下,你都应该显示正在进行的进度,以免用户认为程序被冻结了。
24.如何将SQLite数据库(.db文件)与apk文件一起发布?
答:可以将.db文件复制到Eclipse Android工程中的res aw目录中。所有在res aw目录中的文件不会被压缩,这样可以直接提取该目录中的文件。可以将.db文件复制到res aw目录中
25.如何将打开res aw目录中的数据库文件?
答:在Android中不能直接打开res aw目录中的数据库文件,而需要在程序第一次启动时将该文件复制到手机内存或SD卡的某个目录中,然后再打开该数据库文件。复制的基本方法是使用getResources().openRawResource方法获得res aw目录中资源的 InputStream对象,然后将该InputStream对象中的数据写入其他的目录中相应文件中。在Android SDK中可以使用SQLiteDatabase.openOrCreateDatabase方法来打开任意目录中的SQLite数据库文件。
26.android 中有哪几种解析xml的类?官方推荐哪种?以及它们的原理和区别:
答:XML解析主要有三种方式,SAX、DOM、PULL。常规在PC上开发我们使用Dom相对轻松些,但一些性能敏感的数据库或手机上还是主要采用SAX方 式,SAX读取是单向的,优点:不占内存空间、解析属性方便,但缺点就是对于套嵌多个分支来说处理不是很方便。而DOM方式会把整个XML文件加载到内存 中去,这里Android开发网提醒大家该方法在查找方面可以和XPath很好的结合如果数据量不是很大推荐使用,而PULL常常用在J2ME对于节点处 理比较好,类似SAX方式,同样很节省内存,在J2ME中我们经常使用的KXML库来解析。
27.谈谈Android的IPC机制:
答:IPC是内部进程通信的简称,是共享”命名管道”的资源。Android中的IPC机制是为了让Activity和Service之间可以随时的进行交互,故在Android中该机制,只适用于Activity和Service之间的通信,类似于远程方法调用,类似于C/S模式的访问。通过定义AIDL接口文件来定义IPC接口。Servier端实现IPC接口,Client端调用IPC接口本地代理。
28.NDK
答:NDK是一系列工具的集合
NDK提供了一系列的工具,帮助开发者迅速的开发C/C++的动态库,并能自动将so和java应用打成apk包
NDK集成了交叉编译器,并提供了相应的mk文件和隔离cpu,平台等的差异,开发人员只需简单的修改mk文件就可以创建出so
29.Application类的作用
API里的第一句是:
Base class for those who need to maintain global application state
如果想在整个应用中使用全局变量,在java中一般是使用静态变量,public类型;而在android中如果使用这样的全局变量就不符合Android的框架架构,但是可以使用一种更优雅的方式就是使用Application context。
首先需要重写Application,主要重写里面的onCreate方法,就是创建的时候,初始化变量的值。然后在整个应用中的各个文件中就可以对该变量进行操作了。
启动Application时,系统会创建一个PID,即进程ID,所有的Activity就会在此进程上运行。那么我们在Application创建的时候初始化全局变量,同一个应用的所有Activity都可以取到这些全局变量的值,换句话说,我们在某一个Activity中改变了这些全局变量的值,那么在同一个应用的其他Activity中值就会改变
30.说明onSaveInstanceState() 和 onRestoreInstanceState()在什么时候被调用
Activity的 onSaveInstanceState() 和 onRestoreInstanceState()并不是生命周期方法,它们不同于 onCreate()、onPause()等生命周期方法,它们并不一定会被触发。当应用遇到意外情况(如:内存不足、用户直接按Home键)由系统销毁一个Activity时,onSaveInstanceState()才会被调用。但是当用户主动去销毁一个Activity时,例如在应用中按返回键,onSaveInstanceState()就不会被调用。因为在这种情况下,用户的行为决定了不需要保存Activity的状态。通常onSaveInstanceState()只适合用于保存一些临时性的状态,而onPause()适合用于数据的持久化保存。
另外,当屏幕的方向发生了改变, Activity会被摧毁并且被重新创建,如果你想在Activity被摧毁前缓存一些数据,并且在Activity被重新创建后恢复缓存的数据。可以重写Activity的 onSaveInstanceState() 和 onRestoreInstanceState()方法。
31.横竖屏切换时候的activity的生命周期
新建一个activity,并把各个生命周期打印出来
运行activity,得到如下信息:
onCreate() ==> onStart() ==> onResume()切换成横屏时
onSaveInstanceState() ==> onPause() ==> onStop() ==> onDestroy() ==> onCreate() ==> onStart()
onRestoreInstanceState() ==> onResume()再切换成竖屏时,发现打印了两次相同的Log
onSaveInstanceState() ==> onPause() ==> onStop() ==> onDestroy ==> onCreate() ==> onStart()
onRestoreInstanceState() ==> onResume() ==> onSaveInstanceState() ==> onPause()
onStop() ==> onDestroy() ==> onCreate() ==> onStart() ==> onRestoreInstanceState() ==> onResume()修改AndroidManifest.xml,把该Activity添加android:configChanges=“orientation”,执行步骤3切换成横屏
onSaveInstanceState() ==> onPause() ==> onStop() ==> onDestroy() ==> onCreate()
onStart() ==> onRestoreInstanceState() ==> onResume()修改AndroidManifest.xml,把该Activity添加android:configChanges=“orientation”,执行步骤4切换成竖屏,发现不会再打印相同信息,但多打印了一行onConfigChanged
onSaveInstanceState() ==> onPause() ==> onStop() ==> onDestroy()
onCreate() ==> onStart() ==> onRestoreInstanceState() ==> onResume()
onConfigurationChanged()把步骤5的android:configChanges=“orientation”改成
android:configChanges=“orientation|keyboradHidden”,执行步骤3,就只打印onConfigChanged
onConfigurationChanged()把步骤5的android:configChanges=“orientation”改成
android:configChanges=“orientation|keyboradHidden”,执行步骤4
onConfigurationChanged()
onConfigurationChanged()
总结:
1) 不设置activity的android:configChanges时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次。
2) 设置activity的android:configChanges=“orientation”时, 切屏会重新调用各个生命周期,切横屏、竖屏时都只会执行一次,但是竖屏最后多打印一条onConfigurationChanged()
3) 设置activity的android:configChanges=“orientation|keyboardHidden”时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged(),横屏一次,竖屏两次
再总结下整个activity的生命周期:
1) 当前activity产生事件弹出Toast和AlertDialog的时候Activity的生命周期不会有改变
2) Activity运行时按下HOME键(跟被完全覆盖一样的)
onSavaInstanceState ==> onPause ==> onStop ==> onRestart ==> onStart => onResume
3) 未被完全覆盖,只是失去焦点:
onPause ==> onResume
32. AIDL的全称是什么?如何工作?能处理哪些类型的数据?
AIDL全称Android Interface Definition Language(Android接口描述语言); 编译器可以通过aidl文件生成一段代码,通过预先定义的接口达到两个进程内部通信进程跨界对象访问的目的. 它使用代理类在客户端和实现层间传递值. 如果要使用AIDL, 需要完成2件事情: 1. 引入AIDL的相关类.; 2. 调用aidl产生的class. 理论上, 参数可以传递基本数据类型和String, 还有就是Bundle的派生类, 不过在Eclipse中
具体实现步骤如下:
1、创建AIDL文件, 在这个文件里面定义接口, 该接口定义了可供客户端访问的方法和属性。
2、编译AIDL文件,
3、在Java文件中, 实现AIDL中定义的接口. 编译器会根据AIDL接口, 产生一个JAVA接口。这个接口有一个名为Stub的内部抽象类,它继承扩展了接口并实现了远程调用需要的几个方法。接下来就需要自己去实现自定义的几个接口了.
4、向客户端提供接口ITaskBinder, 如果写的是service,扩展该Service并重载onBind ()方法来返回一个实现上述接口的类的实例。
5、在服务器端回调客户端的函数. 前提是当客户端获取的IBinder接口的时候,要去注册回调函数, 只有这样, 服务器端才知道该调用那些函数
实现接口时有几个原则:
.抛出的异常不要返回给调用者. 跨进程抛异常处理是不可取的.
.IPC调用是同步的。如果你知道一个IPC服务需要超过几毫秒的时间才能完成地话,你应该避免在Activity的主线程中调用。也就是IPC调用会挂起应用程序导致界面失去响应. 这种情况应该考虑单起一个线程来处理.
.不能在AIDL接口中声明静态属性。
IPC的调用步骤:
1. 声明一个接口类型的变量,该接口类型在.aidl文件中定义。
2. 实现ServiceConnection。
3. 调用ApplicationContext.bindService(),并在ServiceConnection实现中进行传递.
4. 在ServiceConnection.onServiceConnected()实现中,你会接收一个IBinder实例(被调用的Service). 调用
YourInterfaceName.Stub.asInterface((IBinder)service)将参数转换为YourInterface类型。
5. 调用接口中定义的方法。你总要检测到DeadObjectException异常,该异常在连接断开时被抛出。它只会被远程方法抛出。
6. 断开连接,调用接口实例中的ApplicationContext.unbindService()
33. ListView优化
1.在adapter中的getView方法中尽量少使用逻辑
2.尽最大可能避免GC
3.滑动的时候不加载图片
4.将ListView的scrollingCache和animateCache设置为false
5.item的布局层级越烧越好
6.使用ViewHolder
34. Android View刷新机制(百度、美团)
由ViewRoot对象的performTraversals()方法调用draw()方法发起绘制该View树,值得注意的是每次发起绘图时,并不会重新绘制每个View树的视图,而只会重新绘制那些“需要重绘”的视图,View类内部变量包含了一个标志位DRAWN,当该视图需要重绘时,就会为该View添加该标志位。
调用流程 :
mView.draw()开始绘制,draw()方法实现的功能如下:
绘制该View的背景
为显示渐变框做一些准备操作(见5,大多数情况下,不需要改渐变框)
调用onDraw()方法绘制视图本身 (每个View都需要重载该方法,ViewGroup不需要实现该方法)
调用dispatchDraw ()方法绘制子视图(如果该View类型不为ViewGroup,即不包含子视图,不需要重载该方法)值得说明的是,ViewGroup类已经为我们重写了dispatchDraw ()的功能实现,应用程序一般不需要重写该方法,但可以重载父类函数实现具体的功能。
35.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嵌套对性能的影响会更大一些。
36.优化自定义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法则,直接设置它们的大小。
更多推荐
所有评论(0)