论述题1. View动画与属性动画有什么区别?
①View动画,顾名思义,只能作用在View上,而属性动画可以作用在所有对象上;
②View动画只改变View的显示效果,不会改变View的属性,所以在使用View实现动画后,它的位置虽然改变了,但它的事件区域位置并没有发生改变,这样就缺乏交互性。而属性动画则真正改变了对象的属性(位置和宽高等)。
③View动画只能实现位移、缩放、旋转和透明度4种动画效果或它们的集合操作,而属性动画则能几乎实现所有的动画效果,能使用自身封装好的动画和自定义的动画效果。
2. 如下代码输出结果是______。
class Super
{
public int f()
{
return 1;
}
}
public class SubClass extends Super
{
public float f()
{
return 2f;
}
public static void main(String[] args)
{
Super s=new SubClass();
System.out.println(s.f());
}
}
编译错误。因为方法不能以返回值来区分,虽然父类与子类中的方法有着不同的返回值,但是它们有着相同的函数签名,因此无法区分。
3. Math.round(11.5)等于多少?Math.round(-11.5)等于多少?
4. RecyclerView的缓存机制与ListView的缓存机制有什么区别?
①ListView是二层缓存,缓存View,每次刷新都需要绑定数据,复用时候全部都要绑定数据,先缓存再复用;
②RecyclerView是四级缓存,缓存ViewHolder,ViewHolder保存的是View,不需要每次刷新都要绑定数据,直接使用即可,先复用再缓存。
5. 对于下述代码结果强制类型转换后,变量a和b的值分别是多少?
short a=128;
byte b=(byte)a;
a=128,b=-128。short类型变量占两个字节,a对应的二进制为:00000000 10000000,由于byte只占一个字节,在强制转换为byte的时候只截取低字节:10000000,因此b的值为-128。
6. 你能简单说一下你对Rxjava的认识吗?
①通过观察者模式实现异步调用。
观察者模式可以这么说,如果把用户界面作为观察者,那么业务数据是被观察者,用户界面观察业务数据的变化,一旦发现数据变化后,就会把相应的响应动作显示在界面上。平时在开发中常见的View的onClick()事件模型就是采用了观察者模式,当Button持有OnClickListener对象之后,Button被点击之后会自动触发OnClickListener中的OnClick方法。再看Rxjava,当Observable的状态发生变化时,内部会通过一系列事件触发Observer中的方法,从而做出相应的操作。
②Rxjava的观察者模式。
Rxjava有Observable(被观察者)、Observer(观察者)、subscribe(订阅)、事件。Observable和Observer通过subscribe()方法实现订阅关系,从而Observable可以根据情况回调通知Observer。
Rxjava常用的回调方法有3种:
●onNext:完成队列中的一个事件。
●onComplete:完成队列中所有的事件。
●onError:事件发生错误时,并且后续的事件终止。
观察者模式在模块之间划定了清晰的界限,降低了模块耦合性,提高了代码的可维护性和重用性。
7. 说一说Android的两种序列化方式以及它们的区别。
①将对象进行序列化后,它可以存储到硬盘本地上,还可以在网络上进行传输;
②Android有两种序列化方式,一种是Serializable,创建的实体类直接实现Serializable接口:
public class Pingred implements Serializable {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name=name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age=age;
}
}
这样Pingred类就实现了序列化,它可以作为参数直接传给Intent去操作。
而另一种方式Parcelable则要复杂点,实体类实现Parcelable接口后,还要重写writeToParcel()和describeContents(),然后创建Parcelable.Creator接口,在里面重写createFromParcel()和newArray()方法:
public class Pingred implements Parcelable {
private String name;
private int age;
@Override
public int describeContents() {
//一般都为0,只有当该对象具有文件描述时返回1
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
//写入属性
dest.writeString(name);
dest.writeInt(age);
}
public static final Parcelable.Creator<Pingred> CREATOR=new Parcelable.Creator<Pingred>0 {
@Override
public Pingred createFromParcel(Parcel source) {
Pingred pingred=new Pingred();
//读取写入的数据
pingred.name=source.readString();
pingred.age=source.readInt();
return pingred;
}
@Override
public Pingred[] newArray(int index) {
return new Pingred[index];
}
};
}
这样Pingred也同样实现了序列化,可以进行传递对象的操作。
③Serializable使用简单,但效率相比Parcable来说有点低,而且会在序列化的时候创建许多的临时对象,容易触发GC。而Parcelable虽然实现起来有点复杂,但序列化效率更高。
[解析] Android的两种序列化是Serializable和Parcelable。
8. 如何实现拦截一条短信?
①首先添加接收短信的权限:
<uses-permission android:name="android.permission.RECEIVE_SMS"/>;
②在AndroidManifest.xml中注册广播接收器,设置该广播接收器优先级,尽量设高一点;
③创建一个BroadcastReceiver来实现广播的处理,并设置拦截器abortBroadcast()。
[解析] 其实很简单,短信接收方式是通过广播来接收,而且这个广播是有序广播。所以可以利用有序广播拦截的方法来拦截短信。
9. 为什么不能在子线程中更新UI?
Google在设计Android时设定了UI控件线程是不安全的,所以在多线程中并发访问可能会导致UI控件处于不可预期的状态。如果像Java那样,给UI控件的访问加上锁机制,会让UI控件变得复杂和低效,也可能会阻塞某些进程的执行。
所以当一定要在子线程中对UI进行操作时,就要使用异步消息机制如Handler和AsyncTask等。
10. 谈一谈你对广播BroadcastReceiver的理解。
①广播是四大组件之一,相当于一个监听器的作用。Android广播按角色可以分为广播发送者和广播接收者。按种类来分又可以分为有普通广播、有序广播和本地广播。
②它就是监听与接收系统或其他应用发出的广播消息,并做出响应。这就使得应用程序间可以互相通信。
③广播接收器的注册。
注册的方式分为两种:静态注册、动态注册:
静态注册:在AndroidManifest.xml里通过<receive>标签声明。
特点:常驻,不受任何组件的生命周期影响,缺点是耗电、占内存,应用在需要时刻监听广播的情况。
动态注册:在活动中创建一个内部类一自定义广播接收器,继承BroadcastReceiver,重写onReceiver()方法;调用registerReceiver(内部类对象,IntentFilter对象)注册,调用unregisterReceiver(内部类对象)注销。
特点:非常驻,灵活,跟随组件的生命周期变化(组件结束=广播结束,在组件结束前,必须移除广播接收器)。
④动态广播最好在Activity的onResume()里注册,在onPause()里注销。因为对于动态广播,有注册就必然有注销,否则会导致内存泄漏;另外重复注册、重复注销也是不允许的。
[解析] 这种问的比较广的题可以按照BroadcastReceiver是什么,有什么用,怎么使用的思路去答即可。
11. 谈一谈你对SQLite中的事务操作的认识。
事务,即Transaction,对数据库执行工作的单元。是以逻辑顺序完成的工作单位或序列,可由用户手动操作完成,也可以由某种数据库程序自动完成。例如现在正在创建一个记录或者从表中删除一个记录,那就代表正在该表上执行事务。重要的是要控制好事务以确保数据的完整性和控制事务处理数据库错误。
事务具有4个标准属性:
●原子性:确保工作单位内的所有操作都顺利完成,否则事务会在出现故障时终止,之前的操作会回滚到以前的状态;
●一致性:确保数据库能在成功提交的事务上正确地改变状态;
●持久性:确保己提交事务的效果或结果在系统发生故障时仍能存在;
●隔离性:使得事务操作之间互相独立和透明。
现在,使用SQLite的SQL语句去完成一条操作,并使用事务:
//往pingred表里面插入一条数据
sqliteDatabase.execSQL ("insert into pingred (name,age)values(?,?)",new Object[]{"James",25});
//开启事务
sqliteDatabase.beginTransaction();
try
{
sqliteDatabase.execSQL ("insert into pingred (name,age)values(?,?)",new Object[]{"James",25});
//事务成功,提交事务
sqliteDatabase.setTransactionSuccessful();
}
finally
{
//不成功,则回滚
sqliteDatabase.endTransaction();
sqliteDatabase.close();
}
在try方法块里,可以看到如果插入成功,则事务成功,提交事务。而如果同时操作多条记录:
//往pingred表里面插入一条数据
sqliteDatabase.execSQL ("insert into pingred (name, age) values(?,?)",new Object[] {"James", 25});
//开启事务
sqliteDatabase.beginTransaction();
try
{
sqliteDatabase.execSQL ("insert into pingred (name, age) values(?,?)", new Object[]{"James", 25});
sqliteDatabase.execSQL ("insert into pingred (name, age) values(?,?)", new Object[]{"Paul", 20});
sqliteDatabase.execSQL ("update pingred set name=? where pingredid=?", new Object[]{"Pingred",30});
//事务成功,提交事务
sqliteDatabase.setTransactionSuccessful();
}
finally
{
//不成功,则回滚
sqliteDatabase.endTransaction();
sqliteDatabase.close();
}
当有一条记录操作失败,sqliteDatabase.setTransactionSuccessful()方法不会执行,一直到finally方法块结束,事务回滚不提交。
所以综上所述,可以知道事务的作用,如果没有事务,在需要修改三条数据的场景,假如其中一条操作失败,另外两条成功,只能通过找出操作失败的数据进行手动的修复。而相反,如果使用了事务,那么当其中一条记录操作失败,就会回滚,也就是说其他两条记录会还原为修改之前的状态。
12. ContentProvider是如何实现数据共享的?
创建自定义ContentProvider并继承ContentProvider类,定义好调用方要访问的数据对应的Uri内容:
public class MyContentProvider extends ContentProvider {
//在ContentProvider中注册Uri
private static UriMatcher mMatcher;
//表pingred中的所有数据
public static final int TABLE_PINGRED=1;
//表pingred中的单条数据
public static final int TABLE_PINGRED_ITEM=2;
static{
mMatcher=new UriMatcher(UriMatcher.NO_MATCH);
//若Uri路径跟authority匹配,则返回注册码TABLE_PINGRED
mMatcher.addURI("com.example.pingred.provider","pingred",TABLE_PINGRED);
//若Uri路径跟authority匹配,则返回注册码TABLE_PINGRED_ITEM
mMatcher.addURI("com.example.pingred.provider","pingred/#",TABLE_PINGRED_ITEM);
}
}
很明显,这里定义的访问数据是pingred表的所有数据和pingred表的一条数据。接着再重写那6个方法:
public class MyContentProvider extends ContentProvider {
……
@Overide
public boolean onCreate() {
//内容提供器初始化触发。在这里进行数据库的创建与升级,
//当访问数据时才开始初始化return false;
}
@Override
public Cursor query(Uri uri, String[] projection,String selection, String[] selectionArgs, String sortOrder) {
/*在内容提供器中查询数据。同SQLite数据中的query()差不多,
只是第一个参数不一样,这里的第一个参数是内容Uri*/
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
//在内容提供器中添加一条数据。第一个参数同样是内容Uri,
//最后返回的是该新添加进来的数据的Uri
return null;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
//更新内容提供器的数据。同样,第一个参数是内容Uri,
//最后返回的是被更新的行数
return 0;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
//删除内容提供器的数据。第一个参数是Uri,最后返回的是被删除的行数
return 0;
}
@Override
public String getType(Uri uri) {
//参数是Uri,返回的是相应的MIME类型
return null;
}
}
这里以查询功能为例,在里面通过UriMather的match()方法对内容Uri进行匹配,然后就能得出调用方要查找的是什么数据了:
public class MyContentProvider extends ContentProvider {
……
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Cursor cursor=null;
switch(mMatcher.match(uri)) {
case TABLE_PINGRED:
//查询表pingred的所有数据
break;
case TABLE_PINGRED_ITEM:
//查询表pingred的某条数据
break;
default:
break;
}
return cursor;
}
……
}
剩下的insert()、update()和delete()方法的实现也差不多跟query()方法一样,根据传入的Uri对象去进行匹配,就能知道调用方要操作的数据位于哪张表、哪一行。最后,在getType()方法里获取Uri对象对应的MIME类型:
public class MyContentProvider extends ContentProvider {
……
@Override
public String getType(Uri uri) {
switch(mMatcher.match(uri)) {
case TABLE_PINGRED:
//匹配任意表的内容Uri
return "vnd.android.cursor.dir/vnd.com.example.pingred.provider.pingred";
case TABLE_PINGRED_ITEM:
//匹配pingred表中任一行数据的内容Uri
return "vnd.android.cursor.item/vnd.com.example.pingred.provider.pingred";
}
return null;
}
}
以上的整个过程其实就是ContentProvider能实现数据共享的原理。
[解析] 把在创建自定义ContentProvider的过程给描述一遍即可,接下来用代码来分析。
13. ListView和RecyclerView之间在性能上有什么区别?
①因为RecyclerView有布局管理器,它比ListView支持的布局类型更多;
②RecyclerView有ViewHolder,对于控件的复用会好过ListView,在编写规范上也会清晰明了;
③RecyclerView有封装好的动画机制,例如ItemAnimator,直接使用即可;
④如果要对特定的item项进行刷新,ListView会比RecyclerView实现起来复杂;
⑤RecyclerView可以对自己的item项里的控件实现点击事件监听,而ListView则不能,它只可以对自己的item项监听点击事件;
⑥ListView可以直接调用其封装好的方法处理空数据,而RecyclerView要开发者自己写;
⑦RecyclerView支持嵌套机制。
14. 谈一谈你对Android动画框架实现原理的认识。
一个DecorView(根View)里有ParentView,而ParentView又有ChlidView,当一个ChildView要重新绘制时,它会调用invalidate()方法,通知其ParentView要重新绘制。这个过程一直向上遍历到ViewRoot,当ViewRoot收到这个通知后就会调用onDraw()方法完成绘制。onDraw()有画布参数Canvas,Android会为每一个View设置好画布,View就可以调用Canvas的drawXXX()等方法去画内容。每一个ChildView的画布是由其ParentView设置的,ParentView根据ChildView在其内部的布局来调整Canvas。而Android动画就是通过ParentView来不断调整ChildView的画布坐标系来实现的。
例如现在要实现一个View控件的动画,将它向右移到50厘米的位置,那它将是慢慢滑动地平移到50厘米处的位置,而这个滑动的过程其实就是View不断调用setTranslate (50)去更新自己位置的过程。
15. 知道布局动画吗?谈一谈你对它的认识。
布局动画是针对ViewGroup的,用来给ViewGroup添加View时添加一个动画过渡效果,可以通过xml文件直接定义一个简单(也就是系统默认)的布局动画:
android:animateLayoutChanges="true"
设置了true后,当ViewGroup要增加子View时,子View会以一个逐渐呈现的过渡效果显示出来。
如果想要自定义该过渡效果,可以通过LayoutAnimationController来实现:
//定义过渡效果
AlphaAnimation animation=new AlphaAnimation(0,1);
animation.setDuration(3000);
//设置显示属性
LayoutAnimationController controller=Controller(animation, 1);
controller.setOrder(LayoutAnimationController.ORDER_RANDOM);
//为ViewGroup设置布局动画
viewGroup.setLayoutAnimation(controller);
在上述代码中,自定义了一个透明度动画,所以当子View出现的时候,会有一个透明度动画效果。而LayoutAnimationController的构造方法中第一个参数是定义的动画,第二个参数则是每个子View出现的延时时间,而当延时时间不为0的时候,可以设置子View显示的顺序,顺序有3种:
●LayoutAnimationController.ORDER_NORMAL:顺序;
●LayoutAnimationController.ORDER_RANDOM:随机;
●LayoutAnimationController.ORDER_REVERSE:反序。
[解析] 还是一样的思路,可以从布局动画是什么?有什么用?怎么使用这3个方面来解答。
16. 怎么使用OkHttp的拦截器?
创建OkhttpClient对象:
OkHttpClient client=new OkHttpClient();
OkHttpClient client=new OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.writeTimeout(2000, TimeUnit.SECONDS)
.readTimeout(2000, TimeUnit.SECONDS)
.build();
添加应用拦截器、网络拦截器以及设置缓存对象。
①准备Request对象:
Request.Builder().build();
②如果需要构建表单则执行如下代码:
new FormBody.Builder().build()
③发送请求:
选择同步发送:
Response response=client.newCall(request).execute();
选择异步发送:
Response response=client.newCall(request).enqueue(callback);
[解析] OkHttp的拦截器是一个强有力的机制,能够监控、重写以及重试调用。
17. onStart()和onResume()有什么区别?onPause()和onStop()有什么区别?
onStart()是在活动从不可见变成可见时触发的,但此时活动还不能与用户交互,而onResume()则是代表活动可以跟用户进行交互,也就是用户此时可以通过触碰可见页面上的各种按钮,此时活动属于运行状态;onPause()是活动因为一些原因(例如对话框形式的活动弹出)导致当前活动处于一种“半透明”显示的暂停状态,此时触发onPause(),而onStop()则是代表活动,此时变为完全不可见。
[解析] 分析它们被调用的时候活动所处于的状态是怎样的。
18. Activity启动模式的标记位有哪些?
经常用到的标记位有以下:
●FLAG_ACTIVITY_NEW_TASK:相当于为Activity指定“singleTask”启动模式;
●FLAG_ACTIVITY_SINGLE_TOP:相当于为Activity指定“singleTop”启动模式;
●FLAG_ACTIVITY_CLEAR_TOP:此模式下的Activity启动时,在同一个任务栈中的所有位于它之上的Activity都要出栈。该标记位一般和singleTask一起出现,此时Activity启动,如果实例存在,则直接使用该实例,并触发onNewIntent();
●FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:该Activity不会在最近启动的Activity的历史列表中保存。
[解析] 标记位的作用就是指定活动的启动模式,它跟之前在配置文件AndroidManifest.xml中用属性android:launchMode来指定的方式不一样,标记位是通过代码在Intent中设置来指定Activity的启动模式的:intent.addFlags(Intent.标记位)。
19. 谈一谈你对插值器的认识。
插值就是根据时间流逝程度去计算它对应的动画完成度,所以插值器就是控制动画变换速率的一个工具类。
①AccelerateDecelerateInterpolator。资源id是@android:anim/accelerate_decelerate_interpolator,它的函数表达式是:y=cos((t+1)π)/2+0.5,所以它的模型图如图1所示。
图1 AccelerateDecelerateInterpolator模型图 ②AccelerateInterpolator。资源id是@android:anim/accelerate_interpolator,它的函数表达式是:y=t^(2f),所以它的模型图如图2所示。
图2 AccelerateInterpolator模型图 ③AnticipateInterpolator。资源id是@android:anim/anticipate_interpolator,它的函数表达式是:y=(T+1)×t^3-T×t^2,所以它的模型图如图3所示。
图3 AnticipateInterolator模型图 ④AnticipateOvershootInterpolator。资源id是@android:anim/anticipate_overshoot_interpolator,由于它的函数表达式比较复杂,所以这里就不展示出来,感兴趣的读者可以自行查找,它的模型图如图4所示。
⑤BounceInterpolator。资源id是@android:anim/bounce_interpolator,由于它的函数表达式同样很复杂,这里就不展示出来,它的模型图如图5所示。
图4 AnticipateOvershootInterpolator模型图 图5 BounceInterpolator模型图 ⑥CycleInterpolator。资源id是@android:anim/cycle_interpolator,函数表达式是y=sin(2π×C×t),它的模型图如图6所示。
图6 CycleInterpolator模型图 ⑦DecelerateInterpolator。资源id是@android:anim/decelerate_interpolator,函数表达式是y=1-(1-t)^(2f),它的模型图如图7所示。
图7 DecelerateInterpolator模型图 ⑧LinearInterpolator。资源id是@android:anim/linear_interpolator,它的函数表达式是y=t,它的模型图如图8所示。
图8 LinearInterpolator模型图 ⑨OvershootInterpolator。资源id是@android:anim/overshoot_interpolator,它的函数表达式是:y=(T+1)x(t1)^3+T×(t1)^2+1,所以它的模型图如图9所示。
图9 OvershootInterpolator模型图
20. 如何启动其他应用的Activity?
启动的是其他应用的Activity,所以就该使用singleInstance模式了,因为singleInstance模式下的Activity是单独占用一个任务栈,具有全局唯一性,这样其他应用也是复用该实例;当然,也可以使用singleTask模式,这样就要在配置文件AndroidManifest.xml中指定该Activity的taskAffinity值,让启动方活动与被启动方活动处在不同的任务栈,这样就能达到同样的效果。
[解析] 该题也是考查Activity的启动模式。