论述题1. 整个自定义View流程里有很多方法,例如requestLayout()、onLayout()、onDraw()和onDrawChild(),请你谈一谈它们之间的联系与区别。
requestLayout():调用onMeasure()和onLayout()的过程,然后根据标记位来判断是否调用onDraw()。
onLayout():通常该方法是针对ViewGroup,因为调用onLayout()是让ViewGroup的子控件分布好位置。
onDraw():绘制控件本身(背景、canvas图层、内容、子view、fading边缘和装饰)。
onDrawChild():回调每个子控件的onDraw()方法。
2. 谈一谈你对广播注册的认识及其使用场景。
①静态注册:直接用AndroidStudio快捷方式创建BroadcastReceiver,自动会注册,然后就可在创建的类中的onReceiver()方法中写要处理的逻辑;静态注册一般用于像开机之后就要收到广播的场景,这样就不会依赖于程序;
②动态注册:在活动中创建一个内部类(该类就是自定义广播接收器),这个类继承BroadcastReceiver,然后在该类中重写onReceiver()方法。最后在活动中调用registerReceiver(),把刚刚定义好的内部类对象作为参数传进该方法,除此之外,还要同时传入另一个参数IntentFilter对象,通过使用IntentFilter对象来添加相应的action,从而让广播接收器知道自己监听的是什么广播,如果匹配则进行接收。把这两个参数传到registerReceiver()方法中,这样就实现了注册,而注销广播接收器则调用unregisterReceiver()方法,只传一个参数进去,该参数就是广播接收器对象。动态注册比较灵活,可以控制注册与注销,但是要依赖程序启动后才能接收到广播。一般会用于跟程序有关的通知功能,即当收到广播后就触发的功能。
3. Activity与Fragment之间如何通信?
Fragment可以调用getActivity()方法来获得Activity的实例,获得实例后就能调用Activity的各种方法及其数据,当然,这里的Fragment和Activity是互相关联的。
在Fragment中定义一个接口以及对应的set方法,接口里面定义一个方法,参数是我们要交互的数据,然后我们采用回调方式进行数据传递,最后在Activity中实现Fragment接口和里面的方法。其中,我们Activity要获得Fragment的实例,所以调用FragmentManager的findFragmentById()或者findFragmentByTag()来获取。
[解析] 该题切换到了Activty与Fragment的交互,可以理解为它们两者怎么获取到对方的实例,这就是关键所在。
4. 下面程序的运行结果是什么?
class Base
{
public Base()
{
System.out.println("Base");
}
}
class Sub extends Base
{
public Sub()
{
System.out.println("Sub");
super();
}
}
public class Test
{
public static void main(String[] args)
{
Base s=new Sub();
}
}
编译错误。当子类构造方法需要显示调用父类构造方法的时候,super()必须为构造方法中的第一条语句。因此正确的写法应该是:
public Sub()
{
super();
System.out.println("Sub");
}
5. 说一说Service的启动方式以及分别对应的生命周期。
Service的启动有两种:一种是调用Context的startService()方法,然后服务就会依次执行onCreate()、onStartCommand(),当然,如果下次再执行启动方法,则服务只执行onStartCommand()方法;然后当调用Context的stopService()方法后,此时服务会执行onDestroy()方法;而另外一种启动方式就是绑定服务,就是调用bindService()方法,然后服务会依次执行onCreate()、onBind()方法,其中onCreate()也是当第一次调用启动方法才会执行,要解绑就调用unbindService()方法,然后服务就会调用onDestroy()方法。
这个生命周期可以用Google官方文档对Service的生命周期总结的概念图很好地描述出来,如图1所示。
图1 Service生命周期 图1左边就是启动方式为startService()的生命周期,而右边则是启动方式为bindService()的生命周期。
[解析] ①如果启动方式为startService(),那服务的生命周期为:
onCreate()--onStartCommand()--onDestroy()
②如果启动方式为bindService(),那服务的生命周期为:
onCreate()--onBind()--onDestroy()
6. 赋值语句float f=3.4是否正确?
不正确。3.4在默认情况下是double类型,即双精度浮点数,将double类型数值赋值给float类型的变量,会造成精度损失,因此需要强制类型转换,即将3.4转换成float类型或者将3.4强制写成float类型。所以,float f=(float)3.4或者float f=3.4F写法都是可以的。
7. 说到服务启动,那其中绑定启动中的Activity是怎样和Service绑定的?
①首先,自定义一个服务类继承Service,把该重写的方法重写,然后再定义一个内部类继承Binder,在这个类中定义变量与方法,最后在onBind()方法中返回Binder对象:
public class TestService extends Service {
…
private MyBinder myBinder=new MyBinder();
class MyBinder extends Binder{
public void doSomething() {
Log.d(TAG,"doSomething:");
}
}
@Override
public IBinder onBind(Intent intent) {
return myBinder;
}
@Override
public void onCreate() {
super.onCreate();
}
…
}
②现在回到Activity中去,创建刚刚在服务里定义好的Binder对象,然后再创建ServiceConnection对象,在它的onServiceConnected()方法里就能获取IBinder对象,从而也就能拿到服务的变量与调用其方法。当然最后,还要调用bindService(serviceConnection),这样就绑定成功了:
public class BActivity extends AppCompatActivity implements View.OnClickListener{
private static final String TAG="BActivity";
private TestService.MyBinder myBinder;
private ServiceConnection serviceConnection=
new ServiceConnection() {
@Override
public void onServiceConnected (
ComponentName componentName,IBinder iBinder) {
myBinder=(TestService.MyBinder) iBinder;
//这样就能调用服务的方法
myBinder.doSomething();
}
@Override
public void onServiceDisconnected(ComponentNamecomponentName) {
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_b);
Button bindBtn=findViewById(R.id.bind_service);
bindBtn.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch(view.getId()) {
case R.id.bind_service:
Intent intent=new Intent(BActivity.this, TestService.class);
//绑定服务
bindService(intent,serviceConnection, BIND_AUTO_CREATE);
}
}
}
以上就是就是完整的BActivity里跟自定义服务TestService的交互过程了。
[解析] 考查对绑定服务的启动方式是否熟悉,所以在这里用代码去分析。
8. 说一下你对RecycleView的认识(使用、原理、优化等)。
①RecycleView的概述。RecycleView是一个滚动控件,可以看作是ListView的升级版,因为RecycleView不仅能轻松简便实现ListView同样的效果,还封装优化了ListView的缺点,例如ViewHolder的复用。
②RecycleView的优点。每一块的功能RecycleView都能轻松实现,因为它都封装好专门的类去实现。想改变布局的显示方式(例如横向排列、九宫格排列等),就可以通过LayoutManager去指定,例如GridLayoutManager和LinearLayoutManager等。如果要改变子项之间的分割线样式则可以通过ItemDecoration去设置。想给子项添加动画则可以通过ItemAnimator去实现。而最基本的ViewHolder也是可以由开发者根据需求去自行创建与绑定,从而实现Adapter。最后,RecycleView的点击事件也是由开发者根据需求去自行注册,这样就不会出现像ListView那样只能识别子项,而子项中的控件不能实现点击事件的情况。
③RecycleView的使用。下面以实现一个普通的滚动列表为例子,先设计子项布局recycle_item.xml:
<?xml version="1.0"encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/item_text"
android:layout_width="match_parent"
android:layout_height="40dp"
android:gravity="center"
android:text="text"
android:background="#D81B60"/>
</LinearLayout>
然后Activity的布局文件引入RecyclerView:
<?xml version="1.0"encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".BActivity">
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/recycler_view"
android:divider="#008577"
android:dividerHeight-"10dp"/>
</LinearLayout>
接下来就是创建RecyclerView的适配器Adapter了:
public class MyRecyclerAdapter extends RecyclerView.Adapter<MyRecyclerAdapter.ViewHolder> {
//定义数据源
private List<String>mList;
public MyRecyclerAdapter(List<String>list) {
mList=list;
}
//返回item个数
@Overide
public int getItemCount() {
return mList.size();
}
//创建 ViewHolder
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent,int viewType) {
return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.recycle_item,parent,false));
}
//填充视图
@Override
public void onBindViewHolder(@NonNull final MyRecyclerAdapter.ViewHolder holder,final int position) {
holder.mView.setText(mList.get(position));
}
//定义ViewHolder
public class ViewHolder extends RecyclerView.ViewHolder {
public TextView mView;
public ViewHolder(View itemView) {
super(itemView);
mView=itemView.findViewById(R.id.item_text);
}
}
}
最后一步就是在Activity里使用RecyclerView了:
public class BActivity extends AppCompatActivity{
private static final String TAG="BActivity";
private RecyclerView mRecyclerView;
private MyRecyclerAdapter adapter;
private LinearLayoutManager mLayoutManager;
private List<String>list;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_b);
initData();
//创建RecyclerView,为其配置适配器Adapter
mRecyclerView=findViewById(R.id.recycler_view);
adapter=new MyRecyclerAdapter(list);
mRecyclerView.setAdapter(adapter);
}
/**
*模拟初始化数据
*/
public void initData() {
list=new ArrayList<>();
for(int i=0;i<=10;i++) {
list.add("子项"+i);
}
}
}
由此可见,使用RecyclerView的思路大致可按照以上这个步骤去使用。
[解析] 作为最经常使用的View之一,就是一道问RecycleView的综合题,把RecycleView所有的知识点给一起描述一遍即可。
9. 下面程序的输出结果是什么?
int i=1;
if(i)
System.out.println("true");
else
System.out.println("false");
编译错误。因为if条件只接受boolean类型的值(true或false),而i的类型为int,不能将其隐式地转换为boolean类型。
10. 使用动画时要注意什么?
①在使用帧动画的时候要注意避免OOM(内存溢出)问题,因为当图片数量较多或者占用资源大时会容易出现OOM问题;
②除了OOM问题还有内存泄漏问题也要注意,因为在使用属性动画的过程中会涉及循环,所以要及时释放资源;
③使用View动画就要注意使用View的clearAnimation()方法,因为View动画不是真正改变View的状态,所以有时会出现动画完成后View无法隐藏的问题;
④为了适配性,动画过程中最好使用dp;
⑤当需要交互性的时候,使用属性动画而不是View动画;
⑥在使用动画时,如果条件允许,最好开启硬件加速。
11. 分别描述一下广播的类型及其区别。
①普通广播:是一种完全异步的广播,发出之后,所有广播接收器都能在同一时刻接收到该广播;优点是传递效率高,缺点是安全性不能保证,因为接收器并不能处理拦截广播:
Intent intent=newIntent("action");
sendOrderedBroadcast(intent,null);
②有序广播:是一种同步执行的广播,所以该广播发出后,同一时刻只有一个广播接收器可接收到该广播,并且接收该广播的广播接收器能对该广播进行拦截,如果不拦截,则可继续传给下一个广播接收器:
<receiver
android:name=".MyBroadcastReceiver"
……>
<intent-filter android:priority="500">
……
</intent-filter>
</receiver>
然后发送广播:
Intent intent=new Intent("action");
sendOrderedBroadcast(intent,null);
如果要拦截广播则在广播接收器上调用:
//拦截广播
abortBroadcast();
③本地广播:有序广播与普通广播都是全局广播,它们发出的广播都是可以被其他应用程序所接收的,而自定义的广播器也能接收他们发过来的广播。而本地广播,则是在一个应用程序内进行传播,所以定义的广播接收器也只能接收来自该应用程序发来的广播。优点是安全性得到保证,传送的广播不会被其他应用程序接收到:
Intent intent=new Intent("action");
localBroadcastManager.sendBroadcast(intent);
[解析] 记住它们的工作流程图以及使用的代码。
12. 说一下在Android中的数据库数据迁移问题。
数据库数据迁移也就是升级数据库,数据库升级通常有3种情况:添加表、删除表和修改表。而修改表通常是添加表字段和删除表字段。
首先将要修改的表进行改名称从而变成一张临时表,然后再创建一张新表,把临时表内的数据迁移到新表内,最后删除临时表。
13. 你是怎么优化自定义View的?
①减少冗余的代码,尤其是onDraw()里的代码,尽量减少调用刷新方法的频率;
②避免在onDraw()里处理内存分配的逻辑;
③因为每次执行requestLayout()方法都是非常耗时,所以最好自定义ViewGroup来执行,因为ViewGroup有onLayout()方法直接可以对子控件进行布局;
④减少View的层级,避免冗余复杂。
[解析] 可以把一般的思路给说出来,然后再结合具体的例子去讲解会更好。
14. 下面的程序输出结果是什么?
String s="abc";
String s1="ab"+"c";
System.out.println(s==s1);
true。"ab"+"c"通过编译器就被转换为"abc",存储在常量区,因此输出结果为true。
15. 同样都是提交SharedPrefrence修改的数据,commit()和apply()有什么区别?
①commit()方法采用同步机制将数据提交到本地中,因此当并发提交的时候,要等正在commit()的数据保存成功后再进行操作;而apply()则是原子的提交,采用异步机制将数据提交。
②commit()具有返回值,返回值是boolean类型,表示提交是否成功;而apply()没有返回值,即使提交失败也不会报错,没有任何提示信息。
③如果对提交后返回的结果不感兴趣,一般情况下使用apply()。
16. 怎样使用SQLite去删除表中个别字段?
假如现在想删除表pingred的age字段,首先可以使用如下sql语句:
create table temp as select pingredId, name, sex from record where 1=1;
可以看到,复制了一个和pingred表一样表结构(唯独没有把age复制)的temp表出来,这样就可以直接删除旧表pingred,然后修改表temp表名为pingred即可:
drop table pingred;
alter table temp rename to pingred;
当然要注意的是,这种思路还是有风险的,毕竟要删除原先一整张pingred,所以要看清楚才去删。
除了以上这个方法,还可以直接使用如下sql语句:
alter table pingred
drop column age;
实现的效果同样是删除表pingred的age字段。
[解析] 其实这种题的答案有很多,只要说出几种即可。
17. 请你描述一下属性动画及其特性。
①通过改变任意对象的属性从而实现动画操作,它是通过反射机制来实现的;
②与View动画框架相比,属性动画具有交互性,真正改变了View本身,通过改变其属性不仅实现位置移动的动画效果,而且它的响应区域也跟随改变;
③虽然ViewPropertyAnimator只能使用它给定的属性,具有一定局限性,但使用ViewPropertyAnimator去实现属性动画会很简单,因为它提供了很多方法,开发者只要直接使用就能实现动画效果。
相比于ViewPropertyAnimator,ObjectAnimator可以自己定制属性,也就是想要操作View的属性,要在创建ObjectAnimator对象时确立下来。最后主动调用ObjectAnimator对象的开始方法让它播放动画效果。
18. Activity之间的通信方式有哪些?
●Intent,当使用Intent进行跳转启动活动时,可以先把要传递的数据放置在Intent中,然后当成功启动了另一个活动后,再从Intent里取出刚传递过来的数据。
假设ActivityA启动ActivityB,在ActivityA中:
//创建Intent对象
Intent intent=new Intent(ActivityA.this,ActivityB.class);
//把要传递的数据放进Intent中去
intent.putExtra("name","Pringred");
intent.putExtra("age",25);
startActivity(intent);
然后在ActivityB中获取数据:
//通过getIntent()获取intent
Intent intent=getIntent();
//调用intent.getXXXExtra("键名")来获取数据值
String name=intent.getStringExtra("name");
int age=intent.getIntExtra("age",18);
●Bundle,也可以通过创建Bundle的bundle.putXXX()把数据存进Bundle里去,然后再通过intent.putExtras(bundle)将bundle存到intent中去,这样等启动了ActivityB后就逐一往intent、bundle里取数据值。
●还可以使用Java中类的静态成员来进行交互,即“类名.属性名”。所以可以在ActivityA中定义一个静态成员,倘若ActivityB想访问该静态成员,直接在AcitvityB中通过“ActivityA.属性名”格式来获取就可以了:
public class ActivityB extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_b);
//获取AcvityA的name并更改赋值
ActivityA.name="Pingred";
Button button=(Button)findViewById(R.id.button);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
}
}
●通过文件存储、SharedPreference、数据库、内容提供者等外部工具来进行交互。
[解析] 把常用的方法(Intent、Bundle、工具类等)说清楚,结合代码来分析。
19. 为什么所有事件都要被同一个View消费?
如果在一次完整的事件中分别将不同的事件分配给了不同的View容易造成事件响应混乱。所以必须保证所有的事件都是被同一个View消费,对事件ACTION_DOWN进行判断,当View消费了ACTION_DOWN事件时,接收到后续的事件,并且将后续所有事件传递过来,不会再传递给其他View。
如果上层View拦截了当前正在处理的事件,会收到ACTION_CANCEL事件,表示当前事件已经结束,后续事件不会再传递。
20. 说一下ContentProvider、ContentResolver、ContentObserver之间的关系。
●ContentProvider:
①四大组件之一,给那些需要共享给其他应用的数据创建了外部访问接口,其他应用只需调用这些接口就能访问到要交互的数据了;
②为应用之间的数据共享提供了渠道,例如一些应用可以访问手机系统的通讯录,而且可以进行修改手机号码与姓名等操作,都是因为通讯录使用了ContentProvider;
③要让应用的某些数据能让别的应用访问或者修改,就使用ContentProvider,而如果想要访问或者修改的数据已经实现了ContentProvider,那么该应用就要使用ContentResolver。
●ContentResolver:
①内容解析,获取ContentProvider提供的数据;
②使用notifyChange (uri)来发出消息。
●ContentObserver:
监听因Uri引起的数据的变化,然后做出响应,有表监听器和行监听器,根据它的Uri的种类来判断,调用registerContentObserver()来监听。
[解析] 三者的关系其实就是通过ContentResolver来对ContentProvider提供的数据进行访问与修改等操作,并且注册ContentObserver来监听数据的变化情况。分别以是什么、有什么用、怎么用的思路去简单说一下这三者之间的关系即可。