以下这些面试题都是笔者在(2017年1月-2017年3月)这段时间所面试Android工程师的总结而来,面试的公司包括巨头xx等,还有新贵公司如dd在线科技,gm金融,zk网,momo科技,zbj等,还有小型活力公司如软都科技,星云颜值,英克科技等,不足之处,还望各位不吝赐教。
1.谈一谈MVC,MVP,MVVM模式的理解。
第一次遇到这个问题,笔者只能悻悻的回答MVC模式,其他没法展开说,后来总结记下来,下次遇到so easy(面试遇到两次该问题)。
一、 MVC
MVC模式的意思是,软件可以分成三个部分。
各部分之间的通信方式如下。
1.View 传送指令到 Controller
2.Controller 完成业务逻辑后,要求 Model 改变状态
3.Model 将新的数据发送到 View,用户得到反馈
所有通信都是单向的。
二、MVP
MVP 模式将 Controller 改名为 Presenter,同时改变了通信方向。
1. 各部分之间的通信,都是双向的。
2. View 与 Model 不发生联系,都通过 Presenter 传递。
3. View 非常薄,不部署任何业务逻辑,称为"被动视图"(Passive View),即没有任何主动性,而 Presenter非常厚,所有逻辑都部署在那里。
三、MVVM
MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。
唯一的区别是,它采用双向绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然。
关于data-binding的用法,可以看看这篇文章 完全掌握Android Data Binding
2.自定义view需要重写哪些方法,如何设定自定义view固定宽高比,如何自定义属性标签?
自定义view需要重写onMeasure(),onLayout()(继承来自ViewGroup时需要),onDraw()。
在OnMeasure()方法中,如果是view的话,view的视图的最终大小确定通过调用setMeasuredDimension()来实现,在此处可以设置固定宽高比,如果是viewgroup的话,对其子视图进行遍历的measure()过程。
OnLayout()主要作用 :为将整个根据子视图的大小以及布局参数将View树放到合适的位置上。如果该View不是ViewGroup类型 ,调用setFrame()方法设置该控件的在父视图上的坐标轴,如果该View是ViewGroup类型,则对它的每个子View进行layout()过程。
OnDraw()方法中,首先绘制view背景,接着绘制视图本身,然后调用dispatchDraw ()方法绘制子视图,最后绘制滚动条。
自定义属性的使用:
一、在res/values文件下定义一个attrs.xml文件,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ToolBar">
<attr name="buttonNum" format="integer"/>
<attr name="itemBackground" format="reference|color"/>
</declare-styleable>
</resources>
二、在布局xml中如下使用该属性:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:toolbar="http://schemas.android.com/apk/res/cn.zzm.toolbar"
androidrientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<cn.zzm.toolbar.ToolBar android:id="@+id/gridview_toolbar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="@drawable/control_bar"
android:gravity="center"
toolbar:buttonNum="5"
toolbar:itemBackground="@drawable/control_bar_item_bg"/>
</RelativeLayout>
三、在自定义组件中,可以如下获得xml中定义的值:
TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.ToolBar);
buttonNum = a.getInt(R.styleable.ToolBar_buttonNum, 5);
itemBg = a.getResourceId(R.styleable.ToolBar_itemBackground, -1);
a.recycle();
就这么简单的三步,即可完成对自定义属性的使用。
3.如何使用gradle打jar包
在android studio中则有点困难,其主要的原因是需要掌握gradle命令形式。接下来就直接上干货。
首先,在你需要进行分装jar的模块进行build.gradle文件下进行以下添加:
然后在Terminal中执行gradle makeJar命令,如果提示:gradle命令不可用。那么你需要在你的系统环境变量中进行变量的添加:
找到你的gradle插件路径进行添加,然后再在path属性的末尾进行
“;%GRADLE_HOME%\bin”添加。
然后输入 "gradle -v"或者"gradle -version"都可以.
再次运行gradle makeJar。然后再一长串的编译后在末尾看到
表示创建jar包成功,在build/libs下可以看到。
4.APP进程被系统杀死后,还能不能接收到broadcast?
首先,答案肯定是不能的。自android 3.1开始,系统自动给所有intent添加了FLAG_EXCLUDE_STOPPED_PACKAGES,导致app处于停止状态就不能收到广播。要想处于停止状态的app收到广播,需要添加FLAG_INCLUDE_STOPPED_PACKAGES这个标记。这样的话,停止的app应该是能够收到系统广播了。
5.listview的实现原理及工作原理
这个问题笔者以前没有关注过,下来后仔细查看了一番,ListView这么厉害的原因,其中一部分就是因为RecycleBin缓存机制。RecycleBin缓存机制是写在AbsListView的一个内部类。所以ListView继承于AbsListView,也继承了这股力量。RecycleBin缓存机制的工作原理:
ListView每当一项子view滑出界面时,RecycleBin会调用addScrapView()方法将这个废弃的子view进行缓存。每当子view滑入界面时,RecycleBin会调用getScrapView()方法获取一个废弃已缓存的view。所以我们再看回Adapter的getView()方法:
@Override public View getView(int position, View convertView, ViewGroup parent) {
View view;
if (convertView == null) {
view = LayoutInflater.from(context).inflate(resourceId, null);
······
} else {
view = convertView;
}
······
return view;
} 这个convertView是什么?convertView就是RecycleBin缓存机制调用getScrapView()方法获取废弃已缓存的view。
getView() 方法中有个判断,
if (convertView == null) ,当convertView为空,也就是没有废弃已缓存的view时,将调用LayoutInflater的inflate()方法加载出来布局view,这个操作是比较耗时的;当convertView不为空时,我们就直接用convertView了,而不需要再次调用LayoutInflater的inflate()方法加载出来布局view。
ListView的工作原理:
View在显示到界面的过程中,会进行两次onMeasure()和onLayout()过程。(如果父视图的子视图的个数为0,就会执行一次。否则就会执行多次。因为开始时父试图中是没有子试图的。但是当你从xml文件中加载子试图或者在java代码中添加子试图时,父试图的状态会发生变化,这个变化会引起onlayout甚至是onmeasure。)
第一次onLayout() :
ListView在加载子项视图的时候,先判断是否有子元素、RecycleBin缓存机制中是否已经有缓存视图了。由于此时ListView是第一次加载,没有任何视图,RecycleBin中也没有任何的缓存记录,所以ListView就直接进行计算,绘制子view等等一系列操作。第二次onLayout() :
到了第二次onLayout()的时候,要注意,因为在有了第一次onLayout()的过程,ListView现在已经加载好了子项视图了。所以当ListView再次判断子元素是否为空时,现在子元素不再等于0了。所以这次会进行下面这些操作:
1. ListView首先调用RecycleBin缓存机制的fillActiveViews()方法,将第一次onLayout()已经加载好的视图全部缓存到mActiveViews中,然后再detach掉第一次所有加载好的视图。这样就解决了第二次onLayout()再次加载视图的时候,出现数据重复的问题。
2. 巧妙的是,在接下来加载子项视图的时候,也是先判断RecycleBin缓存机制中的mActiveViews是否为空,但是因为刚才ListView已经把第一次加载好的子视图全部缓存到了mActiveViews中了,所以此时mActiveViwe并不空,接下来就只要把mActiveViews里面的视图全部attach到ListView上,这样ListView中所有子视图又全部显示出来了。ListView的滑动部分工作原理:
滑动部分的机制是写在AbsListView当中的。那么监听触控事件是在onTouchEvent()方法当中进行的。onTouchEvent()方法内有个switch()条件判断。
1. 首先当判断到我们的动作是滑动时,就计算出我们触发event事件手指在Y方向上的位移距离。
2. 根据这个距离计算出view是否滑出了界面之外,如果滑出了界面之外,RecycleBin缓存机制就调用addScrapView()方法将这个View加入到废弃缓存当中,然后再将这个view进行detach掉。因为这个view已经移出界面了,所以没必要为它保存。
3. 接下来,所有子视图就根据这个距离进行相应的偏移。当发现ListView中最后一个View的底部已经移入了屏幕,或者ListView中第一个View的顶部移入了屏幕时,就会对ListView进行填充。 在填充的时候,ListView先调用RecycleBin缓存机制中的getScrapView()方法来尝试从废弃缓存中获取一个View,如果成功从废弃缓存中取得一个scrapView时,我们就将这个scrapView传入getView()方法中,否则就将null传入getView()方法中。6.Android中定时器任务的实现方式?
Android中定时执行任务的3种实现方法:
一、采用Handler与线程的sleep(long)方法(不建议使用,Java的实现方式)
二、采用Handler的postDelayed(Runnable, long)方法(最简单的android实现)
三、采用Handler与timer及TimerTask结合的方法(比较多的任务时建议使用)下面逐一介绍:
一、采用Handle与线程的sleep(long)方法
Handler主要用来处理接受到的消息。这只是最主要的方法,当然Handler里还有其他的方法供实现,有兴趣的可以去查API,这里不过多解释。
1. 定义一个Handler类,用于处理接受到的Message。
Handler handler = new Handler() { public void handleMessage(Message msg) {
// 要做的事情
super.handleMessage(msg);
}
};
2. 新建一个实现Runnable接口的线程类,如下:
public class MyThread implements Runnable { @Override
public void run() {
// TODO Auto-generated method stub
while (true) {
try {
Thread.sleep(10000);// 线程暂停10秒,单位毫秒
Message message = new Message();
message.what = 1;
handler.sendMessage(message);// 发送消息
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
3. 在需要启动线程的地方加入下面语句:
new Thread(new MyThread()).start();
二、采用Handler的postDelayed(Runnable, long)方法这个实现比较简单一些。
1. 定义一个Handler类
Handler handler=new Handler(); Runnable runnable=new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
//要做的事情
handler.postDelayed(this, 2000);
}
};
2. 启动计时器
handler.postDelayed(runnable, 2000);//每两秒执行一次runnable.
3. 停止计时器
handler.removeCallbacks(runnable);
三、采用Handler与timer及TimerTask结合的方法
1. 定义定时器、定时器任务及Handler句柄
private final Timer timer = new Timer(); private TimerTask task;
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
// 要做的事情
super.handleMessage(msg);
}
};
2. 初始化计时器任务
task = new TimerTask() { @Override
public void run() {
// TODO Auto-generated method stub
Message message = new Message();
message.what = 1;
handler.sendMessage(message);
}
};
3. 启动定时器
timer.schedule(task, 2000, 2000);
schedule(TimerTask task, long delay, long period):这个方法是说,delay/2000秒后执行task, 然后进过period/2000秒再次执行task,这个用于循环任务,执行无数次
4. 停止计时器
timer.cancel();