论述题1. 说一说Activity与Fragment两者生命周期的区别。
Fragment与Activity之间的生命周期:
●Fragment: onAttach();
●Fragment: onCreate();
●Fragment: onCreateView();
●Activity: onCreate();
●Fragment: onActivityCreated();
●Activity: onStart();
●Fragment: onStart();
●Activity: onResume();
●Fragment: onResume();
●Fragment: onPause();
●Activity: onPause();
●Fragment: onStop();
●Activity: onStop();
●Fragment: onDestroyView();
●Fragment: onDestroy();
●Fragment: onDetach();
●Activity: onDestroy()。
相对于Activity来说,Fragment多了几个方法:
●onAttach():当Fragment与Activity建立关联时触发;
●onCreateView():当Fragment创建视图(布局)时触发;
●onActivityCreated():当与Fragment相关联的Activity已经创建完时触发;
●onDestroyView():当Fragment的视图被移除时触发;
●onDetach():当Fragment和相关联的Activity解除关联时触发。
[解析] 只要把它们的生命周期都描述清楚,然后再说出Fragment那几个Activity没有的方法即可。
2. Service的两种启动方式有什么区别?
①首先以生命周期来讲:
如果启动方式为startService(),那服务的生命周期为:
onCreate()--onStartCommand()--onDestroy();
如果启动方式为bindService(),那服务的生命周期为:
onCreate()--onBind()--onDestroy();
②startService()启动服务,那服务就会马上执行onStartCommand()方法自行处理逻辑,而启动它的活动根本无法与服务进行交互,也无法控制它的相关方法;而bindService()启动,服务会执行onBind()方法,返回Binder对象,所以可以在服务里自定义一个Binder类,定义变量和方法,这样当活动一旦调用方法绑定了服务后,可以通过创建ServiceConnection对象来调用onServiceConnected()方法,拿到服务的变量与调用其方法。
[解析] 该题也是老生常谈了,分别以它们的生命周期和特点来阐述区别即可。
3. 自定义View或ViewGroup时要注意什么?
①因为直接继承View或ViewGroup的控件默认不支持wrap_content,所以需要在重写onMeasure()方法中进行设置;
②如果是继承View的控件,则要在onDraw()方法里设置padding,否则控件的padding是不起作用的。而如果是继承ViewGroup的控件,那么也要处理好自身的padding以及它的子控件的margin,否则也是不起作用的;
③因为View内部已经有post一类的方法,所以没必要一定要使用Handler;
④为了防止内存泄漏,要及时在onDetachedFromWindow()或者onAttachedToWindow()里结束动画和线程;
⑤注意避免滑动冲突的情况。
4. 描述下Message、Handler、Message Queue、Looper之间的关系。
①Message:线程间传递的消息,可在消息里添加一些字段,用于识别不同的线程;
②Handler:消息处理者,用于发送和处理消息。用sendMessage()发送消息,用handleMessage()接收消息并处理;
③MessageQueue:消息队列,存储所有发送的消息,等待着被处理,每个线程只有一个MessageQueue对象;
④Looper:相当于管理者,执行loop(),就会进入循环状态,每发现MessageQueue里有一条消息,就取出该条消息将它传递给Handler的handleMessage()中。
[解析] 考查的核心是Handler机制,所以,只要把Handler机制以及要怎么使用描述清楚即可。
5. 请简单对比一下Android的各个数据库框架,说一说它们的特点与区别。
①SQLite。Android中自带的数据库,它是关系型数据库,原生的,运算速度也很快,不过还是要使用SQL语句。
②GreenDao。对象关系映射(ORM)框架。它封装了对象到关系型数据库SQLite的相应接口供开发者直接调用。要使用GreenDao,需要创建另一个“生成器”工程,它的作用是在工程域里生成具体的代码。因此与其他ORM框架对比其性能更好。
③LitePal。对象关系映射(ORM)框架。在使用上非常简单,可以不用写SQL语句就可以完成大部分数据库操作,包括创建表、更新表、约束操作、聚合功能等,让开发者的开发效率提高。
④ORMLite。提供了一些轻量级持久化Java对象到SQL数据库,避免更多的标准的ORM包的开销。支持SQL数据库使用JDBC的数量以及允许原生的Android操作系统数据库API调用sqlite。
6. ViewGroup的事件分发流程是怎样的?
判断自身是否需要拦截,如果需要,调用自己的onTouchEvent()处理事件。不需要拦截则询问ChildView(是调用手指触摸位置的ChildView)。如果子ChildView不需要拦截则调用onTouchEvent()处理事件。
7. 请简单说一下View的绘制流程。
①View的测量,计算View的大小。从MeasureSpec中获取测量模式和大小:
int msMode=MeasureSpec.getMode(measureSpec);
int msSize=MeasureSpec.getSize(measureSpec);
说到MeasureSpec的测量模式,它有3种:
●EXACTLY:精确测量模式,当视图的layout_width或者layout_height指定为具体数值或者match_parent时,表示父视图已经决定了子视图的精确大小,此时View的测量值就是SpecSize的值;
●UNSPECIFIED:不指定测量模式,不限制大小,可以是任何大小。开发中很少使用到,用于绘制自定义View;
●AT_MOST:最大值模式,当前视图的layout_width或者layout_height指定为wrap_content时,控件大小随子控件或内容来改变,大小不超过父控件运行的最大尺寸。
当从MeasureSpec中获取到测量模式和大小后,可根据不同的测量模式来给出不同的测量值:
public int getMeasureHeight(int measureSpec) {
int resultHeight=0;
int msMode=MeasureSpec.getMode(measureSpec);
int msSize=MeasureSpec.getSize(measureSpec);
if(msMode=MeasureSpec.EXACTLY) {
resultHeight=msSize;
} else {
resultHeight=100;
if(msMode=MeasureSpec.AT_MOST) {
//大小不超过父控件运行的最大尺寸
resultHeight=Math.min(resultHeight,msSize);
}
}
return resultHeight;
}
②View的布局,确定View在父控件里的布局位置:
public void layout(int l, int t, int r, int b) {
onLayout(changed, l, t, r, b);
}
……
//当子类是ViewGroup 类型,重写方法,实现ViewGroup中所有View布局
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
③View的绘制,绘制背景、canvas图层、内容、子view、fading边缘和装饰:
public void draw(Canvas canvas) {
……
//Step 1, draw the background, if needed
if(!dirtyOpaque) {
drawBackground(canvas);
}
……
//Step 2, save the canvas' layers
saveCount=canvas.getSaveCount();
……
//Step 3, draw the content
if(!dirtyOpaque)onDraw(canvas);
//Step 4, draw the children
dispatchDraw(canvas);
//Step 5, draw the fade effect and restore layers
canvas.drawRect(left,top,right,top+length,p);
……
canvas.restoreToCount(saveCount);
……
//Step 6, draw decorations(foreground,scrollbars)
onDrawForeground(canvas);
}
③View的绘制,绘制背景、canvas图层、内容、子view、fading边缘和装饰:
public void draw(Canvas canvas) {
……
//Step 1, draw the background, if needed
if(!dirtyOpaque) {
drawBackground(canvas);
}
……
//Step 2, save the canvas' layers
saveCount=canvas.getSaveCount();
……
//Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
//Step 4, draw the children
dispatchDraw(canvas);
//Step 5, draw the fade effect and restore layers
canvas.drawRect(left,top,right,top+length,p);
……
canvas.restoreToCount(saveCount);
……
//Step6, draw decorations(foreground,scrollbars)
onDrawForeground(canvas);
}
[解析] 以View的测量、布局和绘制的思路去阐述。
8. 谈一谈你对SVG动画的认识。
SVG是可伸缩矢量图形,图像在放大尺寸的情况下质量不会有所损失,与Bitmap相比,优点在于不用为不同分辨率设计多套图标,放大不失真。
而Android为了支持SVG,通过标签<path>所支持的指令让画笔画出不同的东西出来,常用指令有L、M、A等。
要实现SVG动画并不难,Android已经封装好了工具类,直接使用即可。使用方法为:首先定义静态的SVG图形,在XML文件里设置Vector,通过<vector>标签定义好SVG图形的具体大小,然后再用<group>和<path>标签来绘制SVG图形,其中<path>里的android:pathData属性就是绘制指令,这里假设要在(10,10)处画一条直线,则可以先使用M指令,然后再使用L指令:
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="50dp"
android:width="100dp"
android:viewportHeight="100dp"
android:viewportWidth="100dp">
<group
android:name="svgtest"
android:pivotX="35"
android:pivotY="35"
android:rotation:"0.0">
<path
android:name="pathchangeGroup"
android:fillColor="#000000"
android:pathData=
"
M10,10 //M指令代表将画笔移到某个坐标点
L60,20 //L指令代表绘制一条直线
"/>
</group>
</vector>
最后就可以使用AnimatedVectorDrawable给静态SVG图形添加动画效果了,在XML文件里声明<animated-vector>组件后引入静态SVG图形。也就是通过刚刚定义了的VectorDrawable中的android:name中的值就能引用到,最后就是添加objectAnimator组件,设置好动画效果就可以了,也就是通过<animated-vector>组件中的android:animation的值来映射。
9. 描述一下Activity、Window、View三者的关系与联系。
为了便于理解,下面首先了解它们之间的关系,如下图所示。
UI架构 Activity有一个Window对象,由PhoneWindow来实现。而PhoneWindow对象下有一个DecorView对象,它就是整个应用界面的根View了,也就是顶层视图。DecorView包含了布局内容,就是用户看到的UI界面。PhoneWindow对其进行管理,监听它所有View的事件。DecorView之所以包含了全部布局内容,是因为它将屏幕分成两部分,一部分是TitleView,另一部分是ContentView。而平时Activity的布局文件xml就是设置在ContentView里。
所以,当执行setContentView()方法后,ActivityManagerService会调用onResume()方法,然后系统就把DecorView添加到PhoneWindow中让其管理并显示出布局内容出来。
[解析] 该题就是考查对Android的UI界面架构的认识,所以得先想起架构图,然后根据图中的层层架构去描述Activity、Window和View。
10. 怎样使用SQLite去升级数据库?(例如要新增一个字段)
在升级数据库时,首先判断当前数据库版本号是否小于指定的版本号,如果符合条件则进行升级操作:
①如果新增的是普通字段,则直接执行sql语句即可;
②如果新增的是主键,因为SQLite限制了ALTER TABLE的一些功能,所以不能用联合主键的方法增加字段。只能将列添加到表的末尾,或者更改表的名称。
[解析] 该题主要是考核新增的字段是普通字段还是主键的升级数据库操作,回答时按照两种情况的思路去回答即可。
11. Fragment之间传递数据的方式有哪些?
①可以在FragmentA中定义一个接口以及对应的set方法,然后在接口里面定义一个方法dataChange(),参数data是我们要传递的数据:
public interface OnDataChangeListener {
public void dataChange(String data);
}
public void setOnDataChangeListener(OnDataChangeListener mListener) {
this.mListener=mListener;
}
然后采用回调方式进行数据传递:
mListener.dataChange(data);
此时就能在Activity中实现FragmentA接口和里面的方法了,最后在FragmentB中定义供Activity调用的方法,参数的类型跟FragmentA中参数data的类型一样,也为String类型,这样就能达到FragmentA与FragmentB数据传递的效果了:
aFragmentsetOnDataChangeListener(new FragmentA.OnDataChangeListener() {
@Override
//这里的data就是FragmentA的data
public void dataChange(String data) {
//调用FragmentB定义的方法,把data传进去
bFragment.changeData(data);
}
});
②用EventBus传值,在FragmentA中调用EventBus.getDefault().post()传递数据,然后在FragmentB中调用onEvent()接收与处理传递过来的数据。
[解析] 可以采用最传统的方法就是接口回调,也可以使用现有的框架如EventBus。
12. 下面程序能否编译通过?如果把ArithmeticException换成IOException呢?
public class ExceptionTypeTest {
public void doSomething()throws ArithmeticException {
System.out.println();
}
public static void main() {
ExceptionTypeTest ett=new ExceptionTypeTest();
ett.doSomething();
}
}
能编译通过。由于ArithmeticException属于运行时异常,编译器没有强制对其进行捕获并处理,因此编译可以通过。但是如果换成IOException后,由于IOException属于检查异常,编译器强制去捕获此类型的异常。因此如果不对异常进行捕获将会有编译错误。
13. 事件分发中的onTouch()和onTouchEvent()有什么区别?
onTouch()方法是View的OnTouchListener接口中定义的方法。当一个View绑定了OnTouchListener后,有Touch事件触发时,就会调用onTouch()方法:
view setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
//onTouch按下
break;
case MotionEvent.ACTION_UP:
//onTouch抬起
break;
case MotionEvent.ACTION_MOVE:
//onTouch移动
break;
}
}
});
onTouchEvent()是重写后,当屏幕有Touch事件时,此方法会被调用:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//按下
break;
case MotionEvent.ACTION_UP:
//抬起
break;
case MotionEvent.ACTION_MOVE:
//移动
break;
}
return super.onTouchEvent(event);
}
onTouch()方法的优先级比onTouchEvent()高,会先调用。假如onTouch()方法返回false,会接着触发onTouchEvent(),如果onTouch()返回true,则onTouchEvent()方法不会被调用。还有类似的系统封装好的click事件的实现都基于onTouchEvent()方法,如果onTouch()返回true,这些事件将不会被触发。
14. 为什么属性动画能真正改变View的位置?
要被改变的对象首先需要创建它的属性的get和set方法,然后属性动画会传递要改变的属性的初始值和最终值,多次去调用set方法,每次传递给set方法的值都不一样,而随着这一过程的渐变变化,所传递的值越来越接近最终值,View就会以动画效果展现:
public class MyView extends View {
//自定义属性
float pingredIndex;
/**
*getter方法
*@return
*/
public float getPingredIndex() {
return pingredIndex;
}
/**
*setter方法
*@param pingredIndex
*/
public void setPingredIndex(float pingredIndex) {
this.pingredIndex=pingredIndex;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(2,2,paint);
……
}
}
[解析] 其实就是考核属性动画的原理。
16. 谈一谈你对JSON解析器的认识。
JSON解析器其实就是根据JSON文法规则将接收到的JSON字符串进行解析,最后输出JSON对象。解析过程包括词法分析和语法分析两个阶段。
①词法分析:该阶段是要按照构词规则将JSON字符串解析成Token流。当词法解析器读入某个词句时,它就会把这个词句进行分析,根据JSON规定的数据类型来判断是否符合规则,符合则生成相应的Token。JSON规定的数据类型如下:
//JSON规定的数据类型
BEGIN_OBJECT({),
END_OBJECT(}),
BEGIN_ARRAY([),
END_ARRAY(]),
NULL(null),
NUMBER(number),
STRING(string),
BOOLEAN(true/false),
SEP_COLON(:),
SEP_COMMA(.),
END_DOCUMENT();
解析非常简单,只需要通过每一个词句的第一个字符就可判断出这个词句的Token。
②词法分析结束后,得到的是Token序列,还要进行语法分析。语法分析是根据JSON文法来检查Token序列的JSON结构是否正确,如果正确则输出JSON对象,错误则报错。JSON文法如下:
object={} | {members}
members=pair|pair, members
pair=string:value
array=[]|[elements]
elements=value |value, elements
value=string|number|object|array|true|false|null
语法分析也很好理解,假如现在要选择以键值对形式的文法来分析经词法分析后得到的Token序列,则当Token是以“key,value”形式出现,那么语法分析器就会认为它是错误的,正确的形式应该是“key:value”。
[解析] 在实际开发中,发送网络请求到服务端去,然后请求成功后获取到数据,但是这些数据还不是开发者最终想要的数据,因为还要根据需求对其进行解析后才能获取最终想要的那些数据。而相对于XML格式,明显JSON有着更好的易读性与简洁性,所以现在各种关于JSON解析的第三方框架也有很多。所以面试官往往想更多地考核面试者会不会自定义实现JSON解析器,所以JSON解析器的解析流程与实现原理读者应该要了解。
17. 有什么好的方法使用SQLite做批量操作?
①使用事务,数量级地提高批量操作的速度;
②使用好Statement:
prepare(运行,开始处理第一条记录)-->bind(填充第一列)-->bind(填充第二列)-->execute(执行插入)
-->clearBind(清除填充的数据)-->(然后处理第二条记录)-->bind(填充第一列)-->bind(填充第二列)
-->execute-->clearBind(清除填充数据)-->(处理第三条记录,以此类推)。
Statement的prepare()比较耗时,所以在批量插入的时候调用一次即可。
18. 用过MVP构建过项目吗?谈一谈你对它的认识。
MVP就是在MVC上增加了一个接口,因此也降低一层耦合度,这样View就不会直接访问Model了。
Presenter完全将Model和View解耦,主要逻辑都集中在Presenter中。和View没有直接关联,因为它能通过View中定义好的接口进行交互。这样当View需要修改的时候,就不用去Presenter里改动。View里就只需处理跟UI有关的逻辑即可。
MVP低耦合、重用方便,而且测试也方便,但使用了接口去设计逻辑复杂的页面时也会导致代码中接口过多或过大。
19. 同样都是滑动控件,说一下RecyclerView和ListView的区别。
①ViewHolder。作为保存控件的工具类,ListView是要开发者自己定义的,而且不需要一定要使用它,当然如果不使用它但是每次都要调用findViewByld(id)会导致性能降低。而RecyclerView则已经封装好了ViewHolder,直接使用即可。
②动画。在一般情况下要使用动画框架则会用属性动画或者View动画,而RecyclerView有封装好的ItemAnimator,通过它可以轻松实现RecyclerView在添加、删除和滑动item项时的动画效果。
③Adapter。ListView有3个系统写好的Adapter,分别是ArrayAdapter、CursorAdapter和SimpleCursorAdapter,可以直接使用,也可以自定义,通过getView()方法来绑定控件。而RecyclerView则是自定义Adapter,使用观察者模式,将数据传给定义好的Adapter。
④滑动方式。ListView只可以在垂直方向上进行滑动,而RecyclerView可以直接通过LinearLayoutManager设置水平或者垂直方向的滑动,可以通过StaggeredGridLayoutManager设置瀑布流排列的风格来滑动,也可以通过GridLayoutManager设置成网格排列风格来滑动。
⑤点击事件。ListView因为实现的接口是OnItemClickListener,所以只能监听子项的点击事件。RecycleView的点击事件也是由开发者根据需求去自行注册,不仅仅是子项的各种点击事件,连子项中的其他控件的点击事件也能实现。这样就不会出现像ListView那样只能识别子项,而子项中的控件不能实现点击事件的情况。
[解析] 可以从ViewHolder、动画、Adapter、滑动方式和点击事件等方面去阐述。
20. 使用SQLite时有什么可以优化的地方?
虽然使用SQLite去对数据进行操作比较简单,但一旦数据量大且复杂,也会出现查询数据缓慢以及插入数据耗时等低效问题。所以这时候就要从各方面去进行优化。
①创建索引:
CREATE INDEX INDEX_NAME ON TABLE_NAME;
但并不是任何情况下创建索引都会加快检索数据表的速度,如果是对数据进行添加、更新和删除操作,使用索引会变慢。因为创建了索引就意味着数据库也会变大,这无疑也增加了数据库表的页数,而且维护索引也有一定的代价,所以如果遇到数据量小的情况就会弄巧成拙,所以创建索引要结合实际情况来使用。
②使用显式事务。将批量的数据库更新得到的journal文件打开关闭降到1次,不要出现多次打开文件和读写再关闭的操作,从而完成原子操作和回滚功能。
③查询数据时可以按照需要来查询对应的列信息,也会提升查询效率。
④数据库操作通常比较耗时,最好使用异步机制去处理。
⑤将sql语句编译成对应的SQLiteStatement,而如果是批量操作就重用SQLiteStatement。
⑥注意要及时关闭Cursor。