以下这些面试题都是笔者在(2017年1月-2017年3月)这段时间所面试Android工程师的总结而来,面试的公司包括巨头xx等,还有新贵公司如dd在线科技,gm金融,zk网,momo科技,zbj等,还有小型活力公司如软都科技,星云颜值,英克科技等,不足之处,还望各位不吝赐教。

1.谈一谈MVC,MVP,MVVM模式的理解。

第一次遇到这个问题,笔者只能悻悻的回答MVC模式,其他没法展开说,后来总结记下来,下次遇到so easy(面试遇到两次该问题)。

一、 MVC

MVC模式的意思是,软件可以分成三个部分。

androidmvp和mvc面试题 android mvvm面试题_androidmvp和mvc面试题

各部分之间的通信方式如下。

androidmvp和mvc面试题 android mvvm面试题_androidmvp和mvc面试题_02

1.View 传送指令到 Controller

2.Controller 完成业务逻辑后,要求 Model 改变状态

3.Model 将新的数据发送到 View,用户得到反馈

所有通信都是单向的。

二、MVP

MVP 模式将 Controller 改名为 Presenter,同时改变了通信方向。

androidmvp和mvc面试题 android mvvm面试题_缓存_03

1. 各部分之间的通信,都是双向的。

2. View 与 Model 不发生联系,都通过 Presenter 传递。

3. View 非常薄,不部署任何业务逻辑,称为"被动视图"(Passive View),即没有任何主动性,而 Presenter非常厚,所有逻辑都部署在那里。

三、MVVM

MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。

androidmvp和mvc面试题 android mvvm面试题_加载_04

唯一的区别是,它采用双向绑定(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文件下进行以下添加:

androidmvp和mvc面试题 android mvvm面试题_缓存_05

然后在Terminal中执行gradle makeJar命令,如果提示:gradle命令不可用。那么你需要在你的系统环境变量中进行变量的添加:

androidmvp和mvc面试题 android mvvm面试题_加载_06

找到你的gradle插件路径进行添加,然后再在path属性的末尾进行

“;%GRADLE_HOME%\bin”添加。

androidmvp和mvc面试题 android mvvm面试题_android_07

然后输入 "gradle -v"或者"gradle -version"都可以.

再次运行gradle makeJar。然后再一长串的编译后在末尾看到

androidmvp和mvc面试题 android mvvm面试题_缓存_08

表示创建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();