Android面试题汇总2016

一.Activity和Service通信方式

   1.Intent:startService(intent),intent可携带参数,在onStartCommand方法里接收intent的时候获取这个参数(Intent要判空);

   2.interface:定义一个接口和一个获取参数的方法,在Service中需要传递数据的地方调用接口进行数据传递,在Acitivity里实现这个接口并覆写接口里的方法,从而获取数据;

   3.Binder:在Service里新建Binder类的时候,在Binder里定义传递数据的方法,在Activity的ServiceConnection里获取该Binder的实例后即可调用方法;

   4.BroadcastReceiver:在Service中发送一个广播,在Activity中注册相应的广播接收,即可获取Intent中传递过来的数据。

二.View的requestLayout、invalidate与postInvalidate有何异同

   1.requestLayout:当我们动态移动一个View的位置,或者View的大小、形状发生了变化的时候,我们可以在view中调用这个方法,即:view.requestLayout();子View调用requestLayout方法,会标记当前View及父容器,同时逐层向上提交,直到ViewRootImpl处理该事件,ViewRootImpl会调用三大流程,从measure开始,对于每一个含有标记位的view及其子View都会进行测量、布局、绘制。

   2.invalidate:该方法的调用会引起View树的重绘,常用于内部调用(比如 setVisiblity())或者需要刷新界面的时候,需要在主线程(即UI线程)中调用该方法;当子View调用了invalidate方法后,会为该View添加一个标记位,同时不断向父容器请求刷新,父容器通过计算得出自身需要重绘的区域,直到传递到ViewRootImpl中,最终触发performTraversals方法,进行开始View树重绘流程(只绘制需要重绘的视图);

   3.postInvalidate:这个方法与invalidate方法的作用是一样的,都是使View树重绘,但两者的使用条件不同,postInvalidate是在非UI线程中调用,invalidate则是在UI线程中调用;

一般来说,如果View确定自身不再适合当前区域,比如说它的LayoutParams发生了改变,需要父布局对其进行重新测量、布局、绘制这三个流程,往往使用requestLayout。而invalidate则是刷新当前View,使当前View进行重绘,不会进行测量、布局流程,因此如果View只需要重绘而不需要测量,布局的时候,使用invalidate方法往往比requestLayout方法更高效。

三、子线程与主线程(非UI线程与UI线程)交互方式

   1.Handler

   2.Activity.runOnUIThread(new Runnable(){... ...});其实也是基于Handler的实现,其内部代码如下:

@Override
public final void runOnUiThread(Runnable action) {
      if (Thread.currentThread() != mUiThread) { //如果当前线程不是UI线程
            mHandler.post(action); //则通过Handler.post
       } else { 
            action.run(); //如果是UI线程,则直接执行run方法
       }
}

   3.View.post(Runnable)或View.postDelay(Runnable,long) 后者多了个延迟时间

大致流程是:View获得当前线程(即UI线程,因为只有UI线程才能创建view)的Handler,然后将action对象post到Handler里。在Handler里,它将传递过来的action对象包装成一个Message(Message的callback为action),然后将其投入UI线程的消息循环中。在Handler再次处理该Message时,有一条分支就是为它所设,直接调用runnable的run方法。因此,我们可以在run方法中更新UI。从本质上说,它还是以Handler为基础的异步消息处理机制。相对于新建Handler进行处理更加便捷

   4.AsyncTask:最好去看一下源码,很多面试会问到AsyncTask的源码。

四、怎么在Http连接中设置超时和代理

 1、Http连接的超时一般可分为connect timeout和socket timeout;

   1.1、HttpClient方式设置超时

HttpClient httpClient = new DefaultHttpClient();
HttpParams params = httpClient.getParams();
//Socket等待建立连接的超时时间
HttpConnectionParams.setConnectionTimeout(params, HTTP_CONNECT_TIMEOUT);
//Socket输入流等待数据到达的超时时间
HttpConnectionParams.setSoTimeout(params, HTTP_SOCKET_TIMEOUT);

   1.2、HttpURLConnection方式设置超时

HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(HTTP_CONNECT_TIMEOUT);
conn.setReadTimeout(HTTP_SOCKET_TIMEOUT);

 2、Android应用程序访问互联网时如果处于WAP环境下,需要首先设置代理,之后才能访问互联网。

    2.1、HttpClient方式设置代理

HttpClient httpClient = new DefaultHttpClient();
String host = Proxy.getDefaultHost(); //默认代理服务器地址
int port = Proxy.getDefaultPort(); //默认代理服务器端口号
HttpHost httpHost = new HttpHost(host, port);
HttpParams params = httpClient.getParams();
params.setParameter(ConnRouteParams.DEFAULT_PROXY, httpHost); //设置默认代理

   2.2、HttpURLConnection方式设置代理

String host = android.net.Proxy.getDefaultHost(); // 默认代理服务器地址
int port = android.net.Proxy.getDefaultPort(); // 默认代理服务器端口号
SocketAddress socketAddr = new InetSocketAddress(host, port);
// 构造代理对象
java.net.Proxy proxy = new java.net.Proxy(java.net.Proxy.Type.HTTP, socketAddr);
try {
    URL url = new URL(“www.baidu.com”);
    // 设置代理
    HttpURLConnection conn = (HttpURLConnection) url.openConnection(proxy);
} catch (MalformedURLException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

五、HTTP和HTTPS的区别

1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。

2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。

3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。

4、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

六、Get请求和Post请求的区别 

1、使用Get请求时,参数在URL中显示,而使用Post请求,则不会显示出来; 
2、Post传输的数据量大,可以达到2M,而Get方法由于受到URL长度的限制,只能传递大约1024字节. 
3、Get请求请求需注意缓存问题,Post请求不需担心这个问题; 
4、Post请求必须设置Content-Type值为application/x-form-www-urlencoded; 
5、发送请求时,因为Get请求的参数都在url里,所以send函数发送的参数为null,而Post请求在使用send方法时,却需赋予其参数; 
6、GET方式请求的数据会被浏览器缓存起来,因此其他人就可以从浏览器的历史记录中读取到这些数据,例如账号和密码等。在某种情况下,GET方式会带来严重的安全问题。而POST方式相对来说就可以避免这些问题。

七、TCP的三次握手和四次挥手

TCP的三次握手:

  1. 建立连接时,客户端发送syn包(syn=1)到服务器,同时随机生成初始序列号 seq=x,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。

  2. 服务器收到请求报文后,如果同意连接,则发出确认报文。确认报文中应该 ACK=1,SYN=1,确认号是ack=x+1,同时也要为自己随机初始化一个序列号 seq=y,此时,TCP服务器进程进入了SYN-RCVD(同步收到)状态。

  3. 客户进程收到确认后,还要向服务器给出确认。确认报文的ACK=1,ack=y+1,此时,TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态。

TCP的四次挥手:

  1. TCP发送一个FIN(finish结束),用来关闭客户到服务端的连接。客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。

  2. 服务端收到这个FIN,他发回一个ACK(确认)。服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。

  3. 服务端发送一个FIN(结束)到客户端,服务端关闭客户端的连接。服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。

  4. 客户端发送ACK(确认)报文确认,并将确认的序号+1,这样关闭完成。客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。

Q1、为什么连接的时候是三次握手,关闭的时候却是四次握手?

答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。

Q2、为什么不能用两次握手进行连接?

答:3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。

       现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发 送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。

Q3、如果已经建立了连接,但是客户端突然出现故障了怎么办?

TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

八、Handler原理

  • 作用:跨线程通信。
  • 使用场景:当子线程中进行耗时操作后需要更新UI时,通过Handler将有关UI的操作切换到主线程中执行。
  • Message:需要被传递的消息,其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,最终由Handler处理。
  • MessageQueue:用来存放Handler发送过来的消息,内部通过单链表的数据结构来维护消息列表,等待Looper的抽取。
  • Handler:负责发送及处理Message消息。
  • Looper:通过Looper.loop()不断地从MessageQueue中抽取Message,按分发机制将消息分发给目标处理者。
  • Handler.sendMessage()发送消息时,会通过MessageQueue.enqueueMessage()向MessageQueue中添加一条消息;通过Looper.loop()开启循环后,不断轮询调用MessageQueue.next();调用目标Handler.dispatchMessage()去传递消息,目标Handler收到消息后调用Handler.handlerMessage()处理消息。

   Q1、为什么系统不建议在子线程访问UI?

  • UI控件非线程安全,在多线程中并发访问可能会导致UI控件处于不可预期的状态。而不对UI控件的访问加上锁机制的原因有:上锁会让UI控件变得复杂和低效;上锁后会阻塞某些进程的执行

   Q2、一个Thread可以有几个Looper?几个Handler?

  • 一个Thread只能有一个Looper,可以有多个Handler;
  • Looper有一个MessageQueue,可以处理来自多个Handler的Message;
  • MessageQueue有一组待处理的Message,这些Message可来自不同的Handler;
  • Message中记录了负责发送和处理消息的Handler;
  • Handler中有Looper和MessageQueue;

   Q3、如何将一个Thread线程变成Looper线程?Looper线程有哪些特点?

  • 通过Looper.prepare()可将一个Thread线程转换成Looper线程。Looper线程和普通Thread不同,它通过MessageQueue来存放消息和事件、Looper.loop()进行消息轮询。

   Q4、可以在子线程直接new一个Handler吗?那该怎么做?

  • 不同于主线程直接new一个Handler,由于子线程的Looper需要手动去创建,在创建Handler时需要多一些方法: Looper.prepare();Looper.loop();

   Q5、Message可以如何创建?哪种效果更好,为什么?

  • Message msg = new Message();
  • Message msg = Message.obtain();
  • Message msg = handler.obtainMessage();
  • 后两种方法都是从整个Messge池中返回一个新的Message实例,能有效避免重复Message创建对象,因此更鼓励这种方式创建Message

   Q6、ThreadLocal有什么作用?

        ThreadLocal类可实现线程本地存储的功能,把共享数据的可见范围限制在同一个线程之内,无须同步就能保证线程之间不出现数据争用的问题,这里可理解为ThreadLocal帮助Handler找到本线程的Looper。

   Q7、主线程中Looper的轮询死循环为何没有阻塞主线程?

  • Android是依靠事件驱动的,通过Loop.loop()不断进行消息循环,可以说Activity的生命周期都是运行在 Looper.loop()的控制之下,一旦退出消息循环,应用也就退出了。而所谓的导致ANR多是因为某个事件在主线程中处理时间太耗时,因此只能说是对某个消息的处理阻塞了Looper.loop()。

   Q8、使用Hanlder的postDealy()后消息队列会发生什么变化?

       post delay的Message并不是先等待一定时间再放入到MessageQueue中,而是直接进入并阻塞当前线程,然后将其delay的时间和队头的进行比较,按照触发时间进行排序,如果触发时间更近则放入队头,保证队头的时间最小、队尾的时间最大。此时,如果队头的Message正是被delay的,则将当前线程堵塞一段时间,直到等待足够时间再唤醒执行该Message,否则唤醒后直接执行。

九、Android工程文件下res文件夹与assets文件夹的区别

  • assets通常用来存放游戏文件、脚本、字体文件等,且这些资源打包的时候不会被系统进行二进制编译,会原封不动打包进APK;res文件下存放图片资源drawable、动画资源anim、布局资源xml以及其他value等,且没有被用到的资源在打包的时候就不会把它们打包进APK(res/raw文件夹除外);
  • assets用AssetsManager访问,不会在R里生成索引ID;res用getResource()访问,在R里生成索引ID;
  • res/raw与assets里的文件在打包的时候都不会被系统二进制编译,都被原封不动打包进APK,通常用来存放游戏资源、脚本、字体文件等。但res/raw不可以创建子文件夹,而assets可以。 
  • res/xml会被编译成二进制文件。res/anim存放动画资源。

十、MVP模式Persenter造成内存泄漏如何优化

在Activity中的销毁方法中,调用presenter层的销毁方法。

十一、内部类是如何持有外部类的引用的

十二、View的绘制流程

  1. 绘制View的背景:drawBackground(canvas);
  2. 如有需要,则保存canvas的图层,为fading做准备;
  3. 绘制View:onDraw(canvas);
  4. 对应第二步,绘制View的fading边缘并恢复图层;
  5. 绘制View的装饰:例如滚动条onDrawScrollBars(canvas);

十三、Java常见运行时异常

  • java.lang.NullPointerException:空指针
  • java.lang.ClassNotFoundException:类不存在
  • java.lang.ArrayIndexOutOfBoundsException:数组下标越界
  • java.lang.NumberFormatException:数字格式化
  • java.lang.IllegalArgumentException:非法参数声明
  • java.lang.IllegalAccessException:无访问权限
  • java.sql.SQLException:Sql语句执行
  • java.util.ConcurrentModificationExecption:并发修改

十四、Synchronized和Lock的区别?Synchronized什么情况下是对象锁,什么情况下是全局锁?

区别:

  • 原始构成:synchronized是关键字,属于JVM层面,底层通过monitor对象来完成,wait/notify等方法也依赖于monitor,只有在同步块或方法中才能调用wait/notify等方法;Lock是个类,是api层面的锁。
  • 使用方法:synchronized 不需要手动释放锁,当synchronized 代码执行完后系统会自动让线程释放对锁的占用;Lock需要手动开锁和释放锁,不释放则会造成死锁。通过lock.lock()和lock.unlock()结合try...catch...finally语句块来完成。
  • 等待是否可中断:synchronized 不可中断,除非运行完毕或异常中断;Lock可中断,设置超时方法tryLock(long timeout,TimeUnit unit),调用 interrupt() 方法中断
  • 加锁是否公平:synchronized 是非公平锁;ReentrantLock 既可以是公平锁,也可以是非公平锁,根据参数设置
  • 锁绑定多个条件Condition:synchronized 没有,ReentrantLock 用来实现分组唤醒需要唤醒的线程们,可以精确唤醒,而不是像 synchronized 似的随即唤醒一个或者全部唤醒
  • 验证上一条:ReentrantLock如何精确唤醒线程

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

猜你喜欢

转载自blog.csdn.net/S_Alics/article/details/99433282