面试宝典褐言的Android探索之旅面试

较新的Android面试题

2017-04-21  本文已影响1628人  CokaZhang

最近从成都来深圳了。目前在深圳这边找工作。作为一个Android开发者,来深圳这边找工作给我的感觉就是好难。可能是市场的原因,随着Android技术的成熟,各大培训机构的加入,给市场带来了不小的冲击。面试官所问的问题也越来越有难度。
在这边也面试了几家公司了,发现面试的问题很多都跟实际开发是两码事儿。是得准备一下才行,不然在这边都找不到工作了。 这两天搜罗了一些面试题,做了一下整理。这些东西可以给准备做Android的萌新一些参考。各位大牛就看个乐呵,其中有不对的地方忘指出。

正文:

最新整理面试题

1.简述synchronized,object,Monitor机制.

synchronized主要是用来进行同步操作,能帮助我们设计更安全的多线程程序。

1)当两个并发线程访问同一个对象object中的synchronized同步代码块时,一个时间内只能有一个线程得到执行。另一个线程只能等到当前线程执行完这个代码块才能执行该代码块。
2)当一个线程访问object的一个synchronized同步代码块时,其他线程对object中所有其它synchronized同步代码块的访问将被阻塞。
3)一个线程访问object中的synchronized代码块时,另外一个线程仍然可以访问object中的其它代码。

monitor是一个同步工具,相当于操作系统中的互斥量。它内置于每一个object对象中,相当于一个许可证。拿到许可证即可以进行操作,没有拿到需要阻塞等待。
使用synchronized时,其实是通过锁对象的monitor的取用与释放来实现的。

2.简述happen-before规则。

happen-before其实就是一个保证而已,它保证一套语句对内存的写操作对于另一条语句是可见的。

规则

1)程序顺序规则:一个线程中的每个操作,happen-befor于该线程中的任意后续操作。
2)监视锁规则:对一个监视器锁的解锁,happen-before于随后对这个监视器锁的加锁。
3)volatileb变量规则:对于一个volatile域的写,happen-before于任意后续对这个volatile域的读。
4)传递性:如果A happen-before B ,且B happen-before C,那么A happen-before C。

3.简述AQS。

AQS全称为(Abstract Queued Synchronizer),这个类是在java.util.concurrent.locks下面。
AQS的核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个就是AQS机制。

4.jvm运行时数据区域有哪几部分组成,各自作用。

jvm运行时数据区通常包括:程序计数器(Program Counter Register),Java栈(VM Stack),本地方法栈(Native Method Stack),方法区(Method Area),堆(Heap)。
作用:

5.gc算法有哪些,gc收集器有哪些

gc是java的垃圾回收机制

gc收集器:

6.简述class加载各阶段,Class Loader有哪些模型。

在java应用程序开发中,只有被jvm装载的class类型才能在程序中使用。只要生成的字节码符合jvm指令集和文件格式,就可以在jvm上运行,这为java的跨平台性提供条件。
字节码的装载过程分为3个阶段:加载,连接,初始化。其中连接包括3个步骤(验证,准备,解析)。
1)加载:处于class装载的第一个阶段,这时jvm必须完成通过类的全面获取类的二进制数据流,解析类的二进制数据流为方法区内的数据结构,创建java.lang.Class的实例。

2)连接:

3)初始化:如果前面的步骤都没有出现问题,那么表示类可以顺利的装载到系统中。这个时候才会执行java字节码。初始化阶段的主要工作是执行类的初始化方法。

Class Loader是一个对象,主要是对类的请求提供服务,当jvm需要某个类是它根据名称向ClassLoader请求这个类,然后ClassLoader返回这个类的class对象。
1)引导类加载器(Bootstrap Class Loader):它用来加载java的核心库,使用源生代码来实现的。
2)扩展类加载器(Extensions Class Loader):用来加载java的扩展库。jvm的实现会提供一个扩展库目录,该类加载器在目录中寻找并加载java类。
3)系统类加载器(System Class Loader):一般来讲,java应用的类都是由它加载完成的,是根据java的应用类路径来加载java类。

7.简述常用的JDK命令行以及工具。

jak常用工具在安装目录的bin目录下。
工具

--编译工具:javac
--运行工具:java
--压缩工具:jar
--文档生成工具:javadoc
--字符编码转换工具:native2ascii

命令
1,编译命令
编译一个文件:在目录下面输入javac a.java(java文件名),编译多个文件中间用空格隔开;javac -d cls a.java是讲编译的文件放到同目录的cls文件夹中。
2,运行命令
java my.hello运行my包下面的hello编译文件。
java -calsspath cl my.java
3,压缩命令
jar -cvf:压缩 //jar -cvf hello.jar a.class b.class c.class
jar -xvf:解压 //jar -xvf cl\hello.jar 解压cl目录下的hello.jar
4,文档生成工具 (将源文件中的doc帮助文件生成HTML文档)
javadoc hello.java
javadoc -d java\doc -version -author lcz\src\lcz.java
5.字符编码转换工具
native2ascii -encoding 编码 源文件名字 改变后的名字

8.简述字节码文件的组成

1,Class字节码中有两种数据类型:
字节数据直接量:这是基本的数据类型。共细分为u1、u2、u4、u8四种,分别代表连续的1个字节、2个字节、4个字节、8个字节组成的整体数据
表:是有多个基本数据或者其它表,按照规定顺序组成的大的数据集合。
2, 具体详解请参考http://www.blogjava.net/DLevin/archive/2011/09/05/358033.html (我也不懂 ,自己去看大神写的)。

9,如何实现一个定时调度和循环调度。

1,使用Timer: Timer myTimer = new Timer(); myTimer.schedule(new Worker(),1000);//1秒后执行。
myTimer.scheduleAtFixedRate(new Worker(),5000,1000);//5秒后执行第一次,之后每隔1秒执行一次。

10,多线程,如何实现一个ThreadLoacl。

ThreadLoacl,线程安全类。关于线程安全除了synchronized关键字,还有另一种方法就是ThreadLoacl,它通过为每个线程提供一个独立的变量副本,解决了变量并发访问的冲突问题。ThreadLocal比直接使用synchronized更方便,简单并且结果程序拥有更高的并发性。
java代码

1.private static final ThreadLocal threadSession = new ThreadLocal();    
2.    
3.public static Session getSession() throws InfrastructureException {    
4.    Session s = (Session) threadSession.get();    
5.    try {    
6.        if (s == null) {    
7.            s = getSessionFactory().openSession();    
8.            threadSession.set(s);    
9.        }    
10.    } catch (HibernateException ex) {    
11.        throw new InfrastructureException(ex);    
12.    }    
13.    return s;    
14.}   

可以看到在getSession()方法中,首先判断当前线程中有没有放进去session,如果还没有,那么通过sessionFactory().openSession()来创建一个session,再将session set到线程中,实际是放到当前线程的ThreadLocalMap这个map中,这时,对于这个session的唯一引用就是当前线程中的那个ThreadLocalMap,而threadSession作为这个值的key,要取得这个session可以通过threadSession.get()来得到,里面执行的操作实际是先取得当前线程中的ThreadLocalMap,然后将threadSession作为key将对应的值取出。

11,说说你了解的一个线程安全队列。

分为BlockingQueQue(阻塞队列)和 ConcurrentLinkedQueQue(非阻塞队列)。
以BlockingQueQue为例,BlockingQueQue作为线程容器,可以为线程同步提供有力的保障。
BlockingQueQue有两种实现:
1)基于数组的阻塞队列实现ArrayBlockingQueQue,内部维护了一个定长数组,以便缓存队列中的数据对象。
2)基于链表的阻塞队列LinkedBlockingAQueQue,它内部维护了一个链表结构的数据缓冲队列。
BlockingQueQue按照FIFO(先进先出)排列元素。
注意:1,必须要使用take()方法在获取时达成阻塞结果。
2,使用poll()方法将产生非阻塞效果。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;



public class BlockingDeque {
//阻塞队列,FIFO
private static LinkedBlockingQueue<Integer> concurrentLinkedQueue = new LinkedBlockingQueue<Integer>(); 

      
public static void main(String[] args) {  
 ExecutorService executorService = Executors.newFixedThreadPool(2);  

 executorService.submit(new Producer("producer1"));  
 executorService.submit(new Producer("producer2"));  
 executorService.submit(new Producer("producer3"));  
 executorService.submit(new Consumer("consumer1"));  
 executorService.submit(new Consumer("consumer2"));  
 executorService.submit(new Consumer("consumer3"));  

}  

static class Producer implements Runnable {  
 private String name;  

 public Producer(String name) {  
     this.name = name;  
 }  

 public void run() {  
     for (int i = 1; i < 10; ++i) {  
         System.out.println(name+ "  生产: " + i);  
         //concurrentLinkedQueue.add(i);  
         try {
            concurrentLinkedQueue.put(i);
            Thread.sleep(200); //模拟慢速的生产,产生阻塞的效果
         } catch (InterruptedException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
         }
         
        }  
     }  
 }  

static class Consumer implements Runnable {  
 private String name;  

 public Consumer(String name) {  
     this.name = name;  
 }  
 public void run() {  
     for (int i = 1; i < 10; ++i) {  
         try {          
                //必须要使用take()方法在获取的时候阻塞
                  System.out.println(name+"消费: " +  concurrentLinkedQueue.take());  
                  //使用poll()方法 将产生非阻塞效果
                  //System.out.println(name+"消费: " +  concurrentLinkedQueue.poll());  
                 
                 //还有一个超时的用法,队列空时,指定阻塞时间后返回,不会一直阻塞
                 //但有一个疑问,既然可以不阻塞,为啥还叫阻塞队列?
                //System.out.println(name+" Consumer " +  concurrentLinkedQueue.poll(300, TimeUnit.MILLISECONDS));                    
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }  

     }  
    }  
 }  
}

Android基础面试题

1,什么是ANR,如何避免它。

在Android设备上,如果你的应用程序有一段时间响应不够灵敏,当响应停顿的事件达到一定的值,系统会向用户显示一个对话框,这个对话框叫做应用程序无响应(Application Not Responding)对话框,用户可以选择让程序停止或者继续运行。不同的组件发生ANR的时间不一样,主线程是5s(Activity,Service),BroadCastReceiver是10秒。
解决方法:
将所有的耗时操作,比如访问网络,Socket通信,查询,复杂逻辑运算等都放到子线程中去执行,让后通过handler.sendMessage,runonUIThread,AsyncTask等方式更新UI,确保用户操作界面的流畅度。如果耗时操作需要用户等待,可以在界面上显示进度条。

2,View的绘制流程。

View的绘制是从ViewRoot的performTraversals()方法开始的,经过measure,layout,draw这个3大步骤。

measure的过程:

measure过程是对整个view树的所有控件计算宽高
measure是冲ViewRoot类中的host.measure开始的,内部调用的是View的measure(int widthMeasureSpec,int heightMeasureSpec)方法,measure方法里面调用了onMeasure(int widthMeasureSpec,int heightMeasureSpec)方法,方法中的两个参数都是是MeasureSpec类型(指父控件对子控件宽高的期望值,它是一个32位的int类型数,前两位表示测量模式,后30位表示测量大小)
测量模式一共有3种:
1)EXACTLY 精确测量模式,xml文件中写200dp,march_parent等代表使用该模式,
2)AT_MOST 最大模式,xml文件中写wrap_content表示使用该模式。
3)UNSPECIFIED 无限大测量模式,只有在绘制特定自定义View时才用的到这个模式。
真正代表测量结束的方法是setMeasuredDimension方法,该方法传入的两个参数是宽高的SpecSize。测量结束后我们可以通过getMeasureHeight和getMeasureWidth来获取测量宽高。
自定义ViewGroup一定要重写onMeasure方法,用于测量子View的宽高,不重写的话子View没有宽高。
自定义View如果在xml中使用了wrap_content属性,就需要重写onMeasure方法来设置wrap_content的默认大小,不然会显示出match_parent的效果。

layout的过程:

ViewGroup用来将子View放在合适的位置上。
layout是从ViewRoot类中的host.layout开始的,内部调用的是ViewGroup的layout方法。在ViewGroup的layout方法中,先调用setFrame来确定自己的左上右下的位置,再调用onLayout来确定子View的位置。
自定义ViewGroup一定要重写layout方法来确定子View的位置,自定义View一般不需要重写该方法,它的位置是右父控件确定的。

draw过程:

此过程是真正将内容展示在屏幕上让我们能够看到的过程。
draw是从ViewRoot类中的host.draw开始的,内部调用的是View的draw方法。
draw的步骤:
1)绘制背景。
2)绘制内容,也就是调用onDraw方法。
3)绘制子View,调用的是dispatchDraw方法。
4)绘制装饰,如listview的滚动条等。

对于View的绘制过程,既可以说是简单的,也可以说是复杂的,简单的在于Google已经帮我们将draw框架写好了,我们在自定义ViewGroup时不用管draw过程,只需要实现measure和layout过程。复杂在于,我们写继承View的自定义控件的时候需要重写onDraw方法,这样才能绘制出你自定义的View的内容,onDraw(Canvas canvas)方法中最重要的两个东西是Paint和Canvas,这个使用起来算是比较复杂的。

3,自定义View如何考虑机型适配

这里要考虑的是屏幕的问题:

4,Touch事件分发机制

一个完整的touch事件,由一个down事件,多个move事件,一个up事件组成。
Touch事件的一般传递流程Activity-->window(唯一实现类PhoneWindow)-->顶级View(DecorView)-->ViewGroup-->View
监听Touch事件有两种方式,setOnTouchListener和直接重写三个方法:dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent。
使用setOnTouchListenre方式设置监听事件,该方式的优先级较高,如果在onTouchListener的onTouch方法中return true的话,那么onTouchEvent方法是接收不到该Touch事件的。而且因为onClickListener中的onClick方法实际上是在onTouchEvent中被调用的,所以Touch事件走不到onTouchEvent,Touch事件就不会生效。

直接重写三个方法:
dispatchTouchEvent:该方法是对点击事件的分发,在这个方法中我们一般return super.dispatchTouchEvent,讲该事件分发下去。
onInterceptTouchEvent:该方法表示对Touch事件进行拦截,这是ViewGroup特有的方法,View没有。在ViewGroup中如果onInterceptTouchEvent返回true,表示将该事件拦截,那么事件将传递给ViewGroup的onTouchEvent方法处理。如果onInterceptTouchEvent方法返回true,事件将传递个子View的dispatchTouchEvent进行分发。
onTouchEvent:该方法表示对Touch事件进行消费,返回true表现消费,返回false表示不消费,那么该事件该事件将传递给父控件的onTouchEvent处理。

5,Art和Dalvik的区别

在程序运行过程中Dalvik虚拟机不断的进行将字节码转换为机器码的工作。
而Art引入了AOT这种预编译技术,在应用程序的安装过程中已经将所有的字节码编译为了机器码,在运行的时候直接调用。Art极大的提高了程序的运行效率,同时减少了手机的耗电量,在垃圾回收机制上也有很大的优化,但是Art模式下应用程序的安装需要消耗更多的时间,同时也需要跟多的安装空间。
Dalvik 是Android4.4及以下平台的虚拟机。
Art 是在Android4.4以上平台使用的虚拟机。

6,ddms和traceView的区别

1, ddms:是android开发环境中的dalvik虚拟机调试监控服务;
ddms能够提供,测试设备截屏,针对特定的进程查看正在运行的线程以及堆信息,Logcat,广播状态信息,模拟电话呼叫,接收sms,虚拟地理坐标等。
2,traceView是android平台配备的性能分析的工具;它可以通过图形化让我们了解要跟踪的程序的性能,并且能具体到方法。
区别:ddms是一个程序执行查看器,在里面可以看见线程和堆栈等信息,traceView是程序性能分析器。

7,四大组件及生命周期。

1)Activity:常用的生命周期方法有 ;
onCreat->onStart->onResume->onPuase->onStop->onDestroy
当已经存在的Activity由不可见到可见会走onRestart方法。
意外销毁时会走onSaveInstanceState方法结束,由意外销毁恢复是会走onRestoreInstanceState方法。
2)BroadcastReceive:在接收到广播是执行onReceive方法在该方法返回结果后结束,如果在onReceive中做了超过10s的事情就会报ANR。
3)Service:

8,对AIDL的理解

AIDL:android interface definition language的缩写。
AIDL是用来实现进程间通信的,可以帮我们实现发布以及调用远程服务。
使用:
1)服务端:创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将服务端暴露给客户端的接口在这个文件中声明,最后在Service中实现这个AIDL接口。
2)客户端:首先绑定服务端的Service,绑定成功后将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可以调用AIDL中的方法。

9,简单叙述okhttp的实现原理

Android中网络请求框架比较多,常用的有HttpURLConnention,Volley,okHttp,Retrofit,RxJava等,从Android4.4开始HttpURLConnection底层实现采用的是okhttp。
进行通信的原理主要是通过dispatcher不断从requestQueue中取出请求call,根据是否已经缓存调用Cache或NetWork这两类数据获取接口之一,从内存缓存或者服务器中获取请求的数据。分为同步和异步请求,同步请求通过call.execute()直接返回当前的response,而异步请求会将当前的call.enqueue添加到请求队列中,通过回调的方式来获取最后的结果。

10,如何保证一个后台服务不被杀死,比较省电的方式是什么。

Android中通过Service实现后台任务。
方法一:

通过将Service绑定到Notification,成为一个前提服务,可以提高存活率在Service中创建一个Notification,再调用Service.startForeground(int id,Notification notification)方法运行在前台即可。这个方式使用360等如阿健管家可以杀死。

方法二:
通过定时警报来不断启动Service,这样就算Service被杀死,也能再启动。同时也可以监听网络切换,开锁屏等广播启动Service。
参考:
Intent intent = new Intent(mContext,MyService.class);
PendingIntent sender = PendingIntent.getService(mContext,0,intent,0);
AlarmManager alarm = (AlarmManager)getSystemService(ALARM_SERVICE);
alarm.setRepeating(AlarmManager.RTC_WAKEUP,System.currentTimeMillis(),5*10000,sender);
这种方式不断启动的逻辑处理起来很麻烦。

方法三:
通过jni调用c,在c语音中启动一个进程fork()。 可以保证360等手机管家不会清理。但是带来了jni交互,稍微有点麻烦。

保证一个后台不被杀死的方法还要很多,对于那种方法比较省电我就不知道了。还需要继续学习。

11,请介绍一下ContentProvider是如何实现数据共享的。

Android提供了ContentProvider,一个程序可以通过实现一个ContentProvider的抽象接口将自己的数据完全暴露出去,而且ContentProvicer是以类似数据库中表的方式将数据暴露。也就是说ContentProvider就像一个“数据库”。那么外界获取其提供的数据,也就应该与从数据库中获取数据的操作基本一样,只不过是采用URI来表示外界需要访问的“数据库”。外部访问通过ContentResolver去访问并操作这些被暴露的数据。

12,Handler机制。

Handler包括四个角色:

在主线程创建Handler,在需要发送消息的地方创建一个Message,通过handler发送。这个消息回到MessageQueQue中,然后Looper会将这个消息取出交给handler处理。

Handler可以有多个,但是在同一线程中Looper和MessageQueQue只能有一个。

13,Binder机制

Binder包含四个角色:

Binder使用Client-Server通信方式,安全性好,简单高效。再加上其面向对象的设计思想,独特的接收缓存管理和线程池管理方式,成为Android进程间通信的中流砥柱。

14,ListView中图片错位的原理,和解决方法。

错位原理: 如果我们只是简单的显示数据,没有convertView的复用和异步操作,就不会产生图片错位。重用convertView但没有异步操作也不会有错位现象。例如我们的listView中刚好显示7个item,当向下滑动时,显示出item8,而item8是重用的item1,如果此时异步网络请求item8的图片,比item1的图片慢,那么item8就会显示item1的图片。当item8下载完成,此时用户向上滑显示item1时,又复用的item8的image。这样就导致的图片错位。
解决方法: 对imageview设置tag,并预设一张图片。向下滑动后,item8显示,item1隐藏。但由于item1是第一次进来就显示所以一般情况下,item1都会比item8先下载完,此时可见的item8的tag和隐藏了的item1的url不匹配,所以就算item1的图片下载完也不会显示到item8中,因为tag标识的永远是可见图片中的url
holder.img.setTag(imgUrl);
holder.img.setImageResource(R.drawable.ic_launcher);
if(imageView.getTag() != null && imageView.getTag().equals(imageUrl)){ imageView.setImageBitmap(result);}

15,在manifest 和代码中如何注册和使用BroadcastReceiver;

<receiver android:name="包名.自己扩展的广播接收者名">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED"/>
</intent-filter>
</receiver>

16,ApplicationContext和ActivityContext的区别;

这是两种不同的context,也是最常见的两种.第一种中context的生命周期与Application的生命周期相关的,context随着Application的销毁而销毁,伴随application的一生,与activity的生命周期无关.第二种中的context跟Activity的生命周期是相关的,但是对一个Application来说,Activity可以销毁几次,那么属于Activity的context就会销毁多次.至于用哪种context,得看应用场景,个人感觉用Activity的context好一点,不过也有的时候必须使用Application的context.application context

17,Serializable 和Parcelable 的区别;

在Android上应该尽量采用Parcelable,它效率更高。
Parcelabe代码比Serializable多一些。
Parcelabe比Serializable速度高十倍以上。
Serializable只需要对某个类以及它的属性实现Serializable接口即可,无需实现方法。缺点是使用的反射,序列化的过程较慢,这种机制会在序列化的时候创建许多的临时对象。容易触发GC。
Parcable方法实现的原理是将一根完整的对象进行分解,而分解后的每一部分都是Intent所支持的数据类型,这样也就实现传递对象的功能。

18,介绍一下NDK;

一 : NDK是一系列工具的集合
NDK提供了一系列的工具,帮助开发者快速开发C或C++的动态库,并能自动将so和java应用一起打包成apk。这些工具对开发者的帮助是巨大的。
NDK集成了交叉编译器,并提供了相应的mk文件隔离CPU,平台,ABI等差异。开发人员只需要简单修改mk文件(指出哪些文件需要编译,编译特性要求等)就可以创建出so。
NDK可以自动将so和java应用一起打包,极大的减轻了开发人员的打包工作。
二 : NDK提供了一份稳定,功能有限的API头文件声明。
Google明确声明该API是稳定的,在后续所有版本中都稳定支持当前发布的API。从该版本的NDK中看出,这些API支持的功能非常有限,包含:c标准库,标准数学库,压缩库,Log库。

19,混合开发;

混合开发就是在一个App中内嵌一个轻量级的浏览器,一部分源生的功能改为Html5来开发,这部分功能不仅能够在不升级App的情况下动态更新,也可以在Android或者iOS的App上同时运行,让用户的体验更好又可以节省开发的资源。
混合开发最主要的是Html5和Native的交互
在Android中WebView本来就支持js和java相互调用,只需要开启WebView的脚本执行,然后通过mWebView.addJavascriptInterface(new JsBridge(),"bxbxbai");向Html5页面注入一股Java对象,然后就可以在Html5页面中调用Native的功能了。
Android4.2以后的系统规定允许被js调用的Java方法必须以@JavascriptInterface进行注解。

上一篇下一篇

猜你喜欢

热点阅读