面试题基础储备
1、Activity相关
a、Activity的特点
1、可见 2、可交互
他之所以可交互,是因为他同时实现了Window.Callback和KeyEvent.Callback, 可以处理与窗体用户交互的事件和按键事件.这两个特点,是他和service最大的区别。一个Activity在创建与销毁的过程中,会经历一些生命周期。
b、Activity的生命周期
结论1、这个界面只要看不到了,它就一定执行了onStop方法
结论2、只要这个界面显示出来了,它就一定执行了onResume方法
结论3、onPause、onStop的情况下这个activity都有可能会系统回收。(简单 来说,只要这个activity不处于活跃状态,那么它就有可能被回收)
c、页面跳转必然会执行的方法
onResume,所以可以在onResume方法里面进行数据刷新,保证当前activity显示的都是最新状态
d、对话框的activity
启动activity默认是占满整个屏幕,如果想让这个activity以对话框的方式展示,则需要配置:android:theme="@android:style/Theme.Dialog",此时它下面的activity并不会执行onStop方法,而是仅执行onPause方法
e、横竖屏切换
默认情况下,横竖屏切换的时候会重新创建新的activity,新的那个activity会执行onCreate方法。可以通过AndroidManifest文件进行配置,让它横竖屏切换的时候不重新创建Activity,配置方法为
android:configChanges="orientation|keyboardHidden|screenSize",配置之后就不会创建新的Activity,而是执行当前activity的onConfigurationChanged。写死屏幕的方向:在AndroidManifest中配置
android:screenOrientation="landscape"
f、保存数据
除了在栈顶的activity,其他的activity在系统资源匮乏的时候,都有可能会被系统回收。如果activity被系统没有onDestory的情况下就被系统回收了,这时候系统会调用onSaveInstanceState方法,我们可以往bundle里面存放数据。在activity onCreate里面我们先判断一下bundle是不是为空,如果不为空,就代表这个activity之前被系统回收掉,应该恢复一下现场。我们就可以从bundle里面取值。
g、栈相关
概念:Android是用栈来管理Activity的,service是没有栈的。所以在service 启动activity一般要加上
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)来开辟一个 新的栈。四大组件中只有Activity是有栈的。
启动模式:android:launchMode,
standard:默认的模式,没有限制,栈里爱有几个有几个
singleTop:栈顶只有一个,栈内可以有多个
singleTask:整个栈只有一个
singleInstance:霸道,整个栈只有自己,单实例
h、onNewIntent
当启动某一个activity,这个activity在栈中存在,并且需要被复用的情况下,(也就是配置了singleTop、singleTask、singleInstance的 属性),会调用此方法。如果需要传递参数,需要在onNewIntent里面setIntent,这样才能更新这个activity的intent
2、Service相关
a、Service的特点
不可见、不可交互,在后台运行的四大组件之一。
并不是所有的功能都需要界面。比如音乐播放。
b、Service的启动方式
startService、bindService
c、Service的生命周期
a、通过startService
Service会经历 onCreate 到onStart,然后处于运行状态,stopService的时候调用onDestroy方法。
这种方式,activity和service是相互独立的。如果是调用者自己直接退出而没有调用stopService的话,Service会一直在后台运行
b、通过bindService
Service会运行onCreate,然后是调用onBind, 这个时候调用者和Service绑定在一起。调用者解绑了,Service就会调用onUnbind->onDestroyed方法。
这种方式,activity就和service相互捆绑在一起了。所谓绑定在一起就共存亡了。调用者也可以通过调用unbindService方法来停止服务,这时候Service就会调用onUnbind->onDestroyed方法。
简单来说,startService的方式启动之后,Activity是调用不到Service里面的方法。
bindService方式启动之后,可以得到Service的实例,进而调用Service里面的方法。startService和bindService可以结合使用。无论怎么结合,Service的实例只有一个。
d、为什么使用Service
普通的线程也可以达到在后台做事情的功能,那么为什么使用 Service呢?是因为Service是系统的组件,它的优先级比普通的线程要高,不容易被系统回收。而且线程不好控制,Service相对好控制一些。运行在前台的Activity是不会被系统回收的,而Service如果不想被系统回收,就需要在Service中设置一下
startForeground(int,Notification)
具体的使用场景有:
a、拥有长连接QQ
b、定时轮询
c、服务里面注册广播接收者。有些广播接收者只能通过代码注册,比如屏幕锁屏、 屏幕解锁、电量发生变化等。
e、IntentService
普通的service ,默认运行在ui main 主线程.Sdk给我们提供的方便的、带有异步处理的service类,我们可以在OnHandleIntent() 处理耗时的操作
四大组件都是运行在主线程中
3、数据相关
a、存储
在Android中数据可以存储在四个地方
内存:读写速度最快,临时的值,程序退出或者界面退出就没有了
SharedPreference:持久存储,主要保存一些配置信息,如一个功能的开关
Sqlite:持久存储,关系型数据库,针对数据之间有很强关系的情况
文件:持久存储,针对数据量较大、并且没啥关联的数据
关于如何使用Sqlite来存储数据,传统的方式我们会使用到SQLiteOpenHelper ,然后会自己写一些sql语句,在真实的开发当中,我们往往会借助于一些第三方框架帮我们处理数据相关的逻辑,这样可以帮助我们帮主要精力花在其他功能实现上。我比较常用的是LitePal,它可以帮我们很轻松的进行数据库操作。
b、传递
a、通过intent传递
基本的数据类型:String、int、float等
对象:这个对象必须继承Parcelable接口。继承Parcelable接口代表这个对象可以 被序列化到内存中,和Serializable类似,只不过Serializable是将对象写到 文件当中。
b、通过Application传递
Application的特点:一个应用程序运行的时候只有一个,它的生命周期是最长的,比Activity、Service都长,只要这个程序在运行,无论是否在前台,它都会有一个Application对象。往Application存某一些对象,在页面跳转的时候会非常方便。
c、通过View来传递数据。在给View设置点击事件的时候,可以通过view.setTag来传递所需要传递的数据
c、解析
XML
效率低,体积大
JSON
效率高,体积小,使用广,可以使用Gson解析,也可以使用JSONObject进行解析,fastjson\其他第三方框架
复杂的结构:使用Gson,创建javabean来进行解析
简单的结构:{"success":"true"},直接使用JSONObject进行解析. 如果使用JSONObject进行解析的话,有几点需要记忆:
{}---JSONObject
[]---JSONArray
无符号:基础数据类型
d、ContentProvider
目的:将自己应用的数据提供给其他应用
把自己的数据通过uri的形式共享出去,这个uri是事先约定好的。
android 系统下不同程序 数据默认是不能共享访问,通过ContentProvider可以将自己应用的数据提供给别的应用。
我们在写内容提供者的时候,需要写一个类继承ContentProvider,然后实现里面的增删改查方法
query(Uri, String[], String, String[], String)
insert(Uri, ContentValues)
update(Uri, ContentValues, String, String[])
delete(Uri, String, String[])
工作中用的很少,我们几乎不需要写ContentProvider,因为没有这个需求。可能会用到的是调用系统的ContentProvider,比如获取系统联系人,系统短信。
4、广播相关
a、广播发送
广播的发送有两类,一种是系统本身就有的,一种是我们自己写的广播。
发送方式:
有序广播sendOrderedBroadcast
按顺序依次的发送给每一个接收者,而每一个接收者在收到这个广播的时候,可以将这个广播abort掉,也可以将这个广播继续传递到下一个广播接收者。广播接收者在注册的时候可以指定优先级,用于提高接收到广播的顺序。
无序广播sendBroadcast
不能被abort掉
b、广播接收
广播接收者注册的方式也有两种,一种是动态注册,一种是静态注册。有一些系统 的广播只能使用动态注册,这种广播产生的频率是比较高的。比如电量变化的广播, 屏幕解锁的广播。电量变化的广播。
c、广播的作用
广播一般是用于跨进程通讯的时候。两个进程间要进行通讯的情况下,使用广播显 得非常的方便。如打电话的时候状态栏颜色变了,此时就是可以使用广播在电话的进 程中发送一个广播,然后再状态栏的进程当中进行接收。接收到之后改变状态栏颜色。 onReceive方法也是在主线程当中运行的。在广播中启动Activity也是需要配置
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)来开辟一个新的栈。
5、跨进程访问
a、Android中跨进程访问的方式:
主要有三种:1、广播 2、AIDL 3、intent启动其他应用activity、内容提供者
b、什么是AIDL:
AIDL的全称是android interface definition language 接口定义语言。它所做的事情就是跨进程调用另外一个服务里面的方法的。所以aidl就得使用到bindService方法。在手机卫士中挂断电话用到过AIDL。我们要获取手机系统的电话服务,用到了aidl 它其实工作的原理就是绑定到一个远程的服务上。然后这个远程服务会返回回来一个代理对象。这个代理对象里面的方法,就是我们定义的aidl里面的方法。
aidl的写法也非常简单,主要分两种。调用系统服务的话,直接找到那个服务的aidl拷贝到我们的项目中就可以了。另外一种是我们自己写aidl,主要注意的一点是aidl是没有访问修饰符的。因为aidl本身就是公开的,就是为了给别人调用的,不需要私有的private方法。
其实,在实际的开发过程中,比较少会写这个aidl,但是你得懂它相关的知识点。
在面试的时候,这个也是经常被问的。所以大家要熟悉这个问题的回答方法。
我以前面试别人的时候,只要他能回答上几个关键点,我就会让他过我这关噢。
1、跨进程 2、aidl在服务端和客户端都需要有一份 3、通过绑定bindService的方式获取远程服务对象的代理IBinder,就可以调用相关的方法。
c、跨进程的底层原理
广播、aidl 的底层实现机制是Binder,Binder是基于共享内存的一种IPC方式
如何自己实现进程间的通信?
6、Handler相关
a、为什么使用Handler
Handler是用来进行线程间的通信。因为Android系统有一个特性,ui操作必须得 在主线程当中进行。所以在子线程进行完耗时操作的时候,如何告诉主线程进行ui 界面更新,这就需要使用到线程间的通讯了。
b、Handler、Looper、MessageQueue、Message的关系
任何一个线程,无论是子线程还是主线程,都维护着一个消息队列,这个消息队列 就是MessageQueue,有了这个消息队列之后,是需要不断的从这个消息队列里面取 出消息进行处理的,这个就是Looper做的事情。那么,谁往这个消息队列里面丢消 息呢?那就是Handler了。
Looper是用来管理所属线程的消息队列MessageQueue的。
每一个线程都需要有一个looper,每一个looper管理一个MessageQueue.
Handler.sendMessage的意思是将某一个message放到MessageQueue中去,looper 是个死循环,不断的读MessageQueue中的新消息。
要让looper的死循环运行起来,得调用Looper.loop()方法。
我们通常都会在子线程中,发一个消息到主线程中的messagequeue中去。
Handler到底是往主线程的MessageQueue发送消息呢还是往子线程的 MessageQueue发送消息呢?这取决于Handler在哪里创建。如果Handler在主线程 中创建,那么这个Handler就会把消息发到主线程的消息队列,如果Handler是在子 线程中创建,这个Handler就会把消息发到子线程的消息队列。这里需要注意的是, 子线程的Looper需要我们自己手动启动,要调用Looper.prepare()和Looper.loop()方 法,主线程的Looper系统已经帮我们启动了,因此我们不需要为主线程的 Looper调用loop()方法
c、子线程-->主线程 主线程-->子线程?
view.post(r)或者activity.runOnUiThread(r)都是将runnable对象丢到了主线程的消息队列中。
7、ANR相关
a、什么是ANR:
ANR是android not response 安卓无响应,这里指的是主线程无响应。将耗时的 操作放在子线程中进行可以有效的避免ANR。
b、出现的原因:消息的超时信息
出现ANR的根本原因是主线程堵塞了,来不及处理消息。任何UI操作都是一个消 息,比如触摸、按键,弹框,setText等,当这些消息得不到及时的处理,就会出现 ANR了。每一个发往主线程的消息其实都带有一个超时时间,超过了这个时间,这 个消息还没有处理,就会出现ANR
Activity的超时:5s
BroadcastReceiver超时:10s
Service超时:20s
c、解决的方法
如果出现了ANR,我们可以通过log信息以及traces.txt文件进行分析。
traces.txt里面记录的是stack信息。
traces.txt的路径:/data/anr/traces.txt
8、ListView的相关
a、ListView为什么要优化?
因为不优化的话,滑动ListView会卡顿。卡顿的原因在于getView太耗时了。
getView是在主线程执行的,如果这里面太耗时的话,ui肯定卡顿。耗时主要集中在两个方面:加载布局文件、findViewById
b、ListView怎么优化
减少加载布局文件的次数:使用缓存convertView
减少findViewById的次数:使用ViewHolder
c、数据错乱的问题
i、正是因为使用了缓存,所以才有可能造成数据错乱。所以在刷新数据的时候,一定要考虑周全。
ii、消息处理时机引起的数据错乱
d、ListView展示多种布局类型
i、告诉系统你有几种布局类型getViewTypeCount
ii、告诉系统,什么情况下显示哪种布局类型getItemViewType(int position)
iii、在getView中根据getItemViewType的返回值加载对应的布局文件。在刷新数据的时候也得根据getItemViewType的返回值进行数据刷新。
9、自定义控件相关
a、自定义属性
步骤:
1、起一个名字
在values的目录下需要建立一个文件,叫做attrs.xml 这个文件是定义属性规则的。一个属性的名字、取值类型、取值范围
2、用这个属性
在布局文件中需要加入命名空间
3、得到这个属性的值
最后在自定义控件中获取这些值。
b、绘制流程
涉及的方法:
onMeasure:计算大小
onLayout:计算位置
onDraw:开始绘制
调用顺序是onMeasure-->onLayout--->onDraw
这三个方法是任何一个View在屏幕上呈现出来的时候都一定会调用的三个方法,三个方法的作用不一样。
其他知识:
int widthMeasureSpec---封装了两个信息,一个是具体大小的值,一个是模式
模式就是父控件对你的限制。
int widthSize = MeasureSpec.getSize(widthMeasureSpec);// 获取宽度
int widthMode = MeasureSpec.getMode(widthMeasureSpec);// 宽度的模式
// MeasureSpec.AT_MOST;--->wrap_content至多的模式。
// MeasureSpec.EXACTLY;--->确定的模式,match_parent,写死成多少dp
// MeasureSpec.UNSPECIFIED;--->未确定的模式 ScrollView:不对孩子的高度进行限定
10、动画相关
分类
a、帧动画
帧动画,就像GIF图片
b、补间动画tween
只是改变了View对象绘制的流程,而没有改变View对象本身。AlphaAnimation、 RotateAnimation、ScaleAnimation、TranslateAnimation
c、属性动画
Android 3.0中才引进,更改的是对象的实际属性。如果要在2.x版本也要支持 属性动画,则需要加入兼容包nineoldandroids.jar
ValueAnimator、ObjectAnimator的使用
具体面试题:59
11、设计模式相关
a、单例:保证就只有一个实例
单例的写法:
1、私有的构造方法
2、私有的,静态的对象instance
3、公开的,静态的getInstance方法
注意事项:在getInstance的方法前加上线程同步synchronized
b、适配器:数据和View的桥梁。
c、观察者:本质就是回调,一堆回调
d、MVC:MVC 是 Model、View、Controller 三部分组成的。其中 View 主要由 xml 布局文件,或者用代码编写动态布局来体现。Model 是数据模型,其实类似 javabean,不过这些 JavaBean 封装了对数据库、网络等的操作。Controller 一般由 Activity 负责,它根据用户的输入,控制用户界面数据的显示及更新model 对象的状态,它通过控制 View 和 Model 跟用户进行交互。
MVC在Android当中体现的并不是很明显,在web当中体现的明显些。
回答设计模式相关的问题时,应该结合自身的开发经历进行开展。
12、屏幕适配相关
a、概述:
用dp 不用px 这仅仅是适配的一部分而已。这里需要注意的一点是,android是不可能做到完全适配每一个屏幕的,只能达到大体相近。如果你要达到完全适配,你就必须在每一个drawable-目录下都放入整套切图,而且要建立不同分辨率的layout-854*480的布局文件,这简直是不可想象。没有一个公司会这么做。大部分的人是怎么做的呢?
b、切图适配:
一般都是找一个使用比较广泛的分辨率当作标准。比如1280x720 密度为2,然 后设计师就会根据这个分辨率进行设计,然后切图。我们就需要将设计师出的切图 放到drawable-xhdpi目录下就可以了。一般情况下,我们不需要在drawable-hdpi 目录、drawable-mdpi目录、drawable-xxhdpi目录下放切图了,只在drawable-xhdpi下放一套就够了。在真正运行的时候,系统会自动对图片进行处理。这时候,绝大部分切图显示都是正常的。如果你觉得哪几张图片显示不清晰了,就只要再出这几张切图放到drawable-xxhdpi目录或者其他目录即可。
c、布局适配:
尽量用一些确定的东西来确定不确定的东西。多用RelativeLayout、LinearLayout
整体是一个LinearLayout,垂直方向。中间的edittext是match_parent.最底下整体布局应该是RelativeLayout,OK按钮是layout_alignParentRight,确定好OK之后,在确定cancel的值,切勿不要先确定cancel的位置再确定ok的位置。
按照上诉布局做法,在超大的屏幕上显示的时候发现,edittext变长了点,但是整个的位置结构是没变的,这个布局的适配就算是过关的。
其实屏幕适配总结起来就几个方面:布局适配、图片适配、单位适配、代码适配
13、图片相关
a、图片的缓存(多图片的加载)
任何一种缓存的目的是都是为了加载更快,那么存在哪里呢?存在内存中是最好的, 因为内存的读取速度最快,但是内存的大小是受限制的。所以,我们只能在内存中 加载一部分图片。使用LRUCache可以对内存中的图片进行有效管理。
三级缓存:内存缓存--本地缓存--网络缓存
b、大图片的加载
如果一张图片有几十M,那么这一张图片一加载肯定就OOM了,根本就不需要使 用LRUCache,所以加载大图片的时候需要注意:
1、仅请求图片的大小,inJustDecodeBounds = true,仅请求图片大小,而不会 加载图片到内存;
2、得到图片的大小,再得到所需要显示控件的大小,根据这两个值,合理设置 BitmapFactory.Options的inSampleSize值,减少图片内存占用
14、View的事件传递
View:只有dispatchTouchEvent、onTouchEvent
ViewGroup:dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent
事件传递dispatchTouchEvent 为的就是找到响应的那个View 这个方法的返回值会使用 onInterceptTouchEvent的返回值,true代表找到响应的View,false代表没有 找到响应的View
事件拦截onInterceptTouchEvent 返回值为true代表将事件拦截在此ViewGroup中,此View 就是响应的那个ViewGroup
事件响应onTouchEvent true代表消费了这个事件,这个事件就不往上传递,false代表没有消费此事件,此事件继续向上传递。
可以以爷爷、父亲、孩子吃苹果的例子加强记忆。一个苹果到了爷爷的手中,爷爷可以决定自己吃,也可以决定把苹果给父亲吃。如果爷爷吃了这个苹果,父亲肯定就得不到这个苹果。如果爷爷把苹果给了父亲,那父亲得到苹果之后也有两个选择,自己把苹果吃了,或者把苹果给孩子。孩子拿到苹果了,可以选择吃,也可以选择不吃。如果不吃的话,孩子没办法把苹果向下传递,他就会把苹果又还给父亲。父亲拿到苹果之后,可以吃了它,也可以再把它还给爷爷。爷爷拿到苹果之后也同样有两种选择。
苹果从爷爷-父亲-孩子,代表事件传递
苹果从孩子-父亲-爷爷,代表事件消费
事件传递的方向是由父控件到子控件,事件响应的方向是从子控件到父控件。
15、内存泄露相关
垃圾回收机制:垃圾回收器仅会回收没有人引用的对象。
内存泄露
内存泄漏本身不会产生什么危害,真正有危害的是内存泄漏的堆积。Android应用内存泄漏的的原因有以下几个:
1、register之后没有unregister
2、查询数据库后没有关闭游标cursor file没有close
3、构造Adapter时,没有使用 convertView 重用 (内存的溢出)
4、Bitmap对象不在使用时调用recycle()释放内存
5、对象被生命周期长的对象引用,如activity被静态集合引用导致activity 不能释放
内存泄漏如何解决:
通过内存分析工具 MAT(Memory Analyzer Tool),找到内存泄露的对象
生成hprof文件可以通过adt的工具也可以通过代码生成。debug的dump方法
、
16、提高ui流畅度
1、优化布局层次。不要不断的嵌套LinearLayout ,多使用RelativeLayout 尽可能的减少布局的层次。左边是图片,右边是文字的,可以就使用一个TextView来完成。
详见:提高UI流畅度_第6期.pdf文件
2、合理使用控件
ListView嵌套GridView
ScrollView嵌套ListView
ListView嵌套ListView
这些情况都应该避免。
17、优化性能
内存:
a、内存溢出(主要是图片,其次是无用的对象太多)
b、内存泄露
布局:
a、尽可能减少布局的嵌套层级
尽量多用RelativeLayout可以很有效的减少布局的嵌套层级。也可以使用hierarchyviewer这个工具来检查布局层次
b、不用设置不必要的背景,避免过度绘制
比如父控件设置了背景色,子控件完全将父控件给覆盖的情况下,那么父控件就没有必要设置背景。
c、使用<include>标签复用相同的布局代码
d、使用<merge>标签减少视图层次结构
e、通过<ViewStub>实现 View 的延迟加载
资源:
f、线程池的使用
18、使用过的一些工具
monkey、traceview、mat、beyond compare、hierarchyviewer
monkey:可以使用Monkey测试工具帮助我们完成程序的健壮性。
命令如下:adb shell monkey -p 包名 --ignore-crashes --ignore-timeouts 10000
具体工作的使用场景将会和logcat命令结合起来。
adb logcat -v time > D:\log.txt
traceview:分析程序执行的方法效率,可以图形化的表现出方法的执行时长。从而帮助开发者优化性能。
hierarchyviewer:分析布局视图
来源于www.itcast.cn 之zheng_teacher