Android ClassAndroidAndroid面试

Android面试及其汇总操作一

2017-09-18  本文已影响926人  黑狗狗哥

Android面试及其汇总操作一

对IPC的看法

IPC进程通信的方式有哪些?
  • Linux中有socket,named pipe,message,queque,signal,share memory,Binder.
  • Java中有socket,named pipe等
为什么google采用Binder通信?
  • 因为Binder是相对高效率的(为什么说是相对呢,由于在进行通信的时候,Share memory是不用复制变量的,Binder需要复制一次,而其他的基本都需要复制两次,因此说Binder是相对高效的,它相对与Share memory是低效的,为什么Android不采用Share memory的方式呢?,因为它控制复杂,且难以使用)。
Binder通信简介:
  • Binder的用户空间为每个进程维护着一个可用的线程池,线程池用于处理到来的IPC以及进程本地消息,它是采用C/S模式设计的,所有IBinder通信线程都必须实现IBinder接口。通过IPC,两个进程间的通信看上去就好像是一个进程进入了另外一个进程执行代码然后带执行结果返回。
IBinder接口的简介:
  • IBinder接口是跨进程对象的抽象。普通对象在当前进程可以访问,但如果希望对象能被其他进程访问,那必须实现IBinder接口。调用者不必关心IBinder指向的是本地对象还是远程对象,因为它看起来就和调用本地对象一样。
实现IBinder的接口的原则:
  • 1、 抛出的异常不要返回给调用者,因为跨进程抛异常处理是不可取的.
    2、 IPC调用是同步的,因此要是知道使用IPC服务比较耗时的话,应避免在UI线程中调用。
    3、 IPC调用会挂起应用程序,这时应考虑开启一个线程来处理,能在AIDL接口中声明静态属性。
(AIDL)IPC调用步骤:
  • 1、 声明一个类型的变量,该接口类型在.aidl(注意不能定义这个文件中的接口的范围和方法范围,即不能添加public等的修饰范围修饰符)文件中定义。
    2、 实现ServiceConnection.
    3、 通过BindService的方式绑定服务并且在ServiceConnection的onServiceConnected()方法中通过调用YourInterfaceName.Stub.asInterface(IBinder)方法获得远程的AIDL对应的接口实体的引用。
    4、 通过第3部获得的引用来处理业务。
    5、 通过unbindSerice()的方法断开连接

Handler机制

Handler机制的几个成员介绍:
  • 1:Message,消息载体,负责承载Handler发送的信息内容
    2:MessageQueue,消息队列,负责传递Message
    3:Looper,消息循环,负责在在MessageQueue循环的取出消息
Handler的处理流程:
  • 1、 线程通过Looper.prepare()来创建Looper(但是注意的是在UI线程中Looper在创建的线程的时候已经创建好的,因此不用调用Looper.prepare(),并且注意的是这个方法只能调用一次,如果多次调用了会RuntimeException的错),然后通过Looper.loop()方法来开启Loop循坏来监听MessageQueue。
    2、 非UI线程创建Message对象(这里注意的是最好使用Message.obtain()方法来获得Message对象,因为系统会为Message创建一个消息池,通过这个方法可以获取消息池中的对象。尽量不要使用new方式来创建Message对象),并且设置Message属性的值,最后通过Handler对象的sendMessage(Message)方法传递Message对象
    3、 Message会被存入MessageQueue中,这个时候Looper会从MessageQueue中一个一个的把Message取出,并且传递给Message对应的Handler中去,这个Handler其实就是Message的target属性,既对应的Handler = Message.target
    4、 Handler处理传递过来的Message(在Handler的handlerMessage(Message)方法中处理)。
Handler.post(Runnable)与Handler.sendMessage(Message)的区别:
  • 1、 都是把消息放到消息队列中等待执行,前者放的是一个Runnable对象,后者是一个Message对象
    2、 前者最终还是会转换成sendMessage,只是最终的处理方式不一样,前者会执行runnable的run方法;后者可以被安排到线程中执行
HandlerThread类:
  • HandlerThread继承Thread,是一个包含有Looper的线程类。正常情况下,除了UI线程,工作线程是没有looper的,但是为了像UI线程一样使用Handler,Android叶自定义了一个包含looper的工作线程—HandlerThread类。

Android内存管理

简介Android内存:
  • 这里先说下linux系统的,不同于windows系统,linux系统在看上去是没有多少剩余内存的,但是这个确实linux比较优秀的一个地方,正是因为这样子,linux系统能充分利用内存来供给用户很好的体验,而在打开新的应用程序的时候,linux系统则通过回收资源来提供足够的内存给新的应用程序。
    平时我们在使用手机的时候基本不用在于剩余内存,这是因为Android应用程序都泡在java虚拟机之上。而Android系统会为我们自动管理这些内存,Android也使用类似与java的GC垃圾回收机制,通过这个机制回收优先级低的内存,以供优先级高的应用程序使用,平时为什么我们打开大应用为卡呢,因为这个时候系统剩余的内存不足以支撑新的应用程序,因此系统会回收优先级低的内存供给这个大应用使用,所以看上去就会卡顿。要是了解内存的回收机制,我们应该先了解Android的进程优先级;
Android内存优先级:
  • 1、 前台进程:
    a、 正在与用户交互中的Activity
    b、 调用了startForeground()方法的service
    c、 正在执行生命周期方法的service
    d、 正在执行onReceiver()方法的BroadcastReceiver
    e、 与前台activity绑定的service
  • 2、 可见进程:
    a、 部分界面被挡住的activity,既activity执行了onPause()但是没有执行onStop()方法,典型的情况就是对话框的弹出
    b、 与可见Activity绑定的service
  • 3、 服务进程:
    a、已启动的service
  • 4、 后台进程:
    a、不可见的activity,Android系统保留这个进程的原因是因为为了提升用户体验,会保留这些activity直到系统发生了内存回收,在下次启动这个进程对应的应用的时候可以更快速
  • 5、 空进程:
    a、 不包含任何活动状态的进程,空进程存在的唯一理由是为了缓存一些数据,以便下次更快的启动

BroadcastReceiver生命周期

当BroadcastReceiver执行onReceiver()方法的时候他是活跃的,但是执行完onReceiver()后他却是不活跃的。一个活跃的BroadcastReceiver是不会被系统杀死的,不活跃的在系统内存回收的时候,可能会被杀死。BroadcastReceiver在执行onReceiver()时候生命周期开始,执行完后就结束了。

java垃圾回收机制

如何判断某个对象是否为“垃圾”?

两种判断对象是否为垃圾的方法:
(java并没有采用这种方法,python采用了)采用引用计数来判断,这个种方法只要引用计数不为0,则说明对象不为“垃圾“,但是这种方法有个缺点就是,无法解决循环引用的问题,循环引用的意思是,a对象包含b,b对象包含a,这个时候把a,b都置为null,但是此时a和b的引用计数缺不为0,因此他们两个不为被判断成“垃圾”。
(java采用这种方法)采用可达性分析(好像是图论的知识),通过一系列的“GC Roots”对象作为起点搜索,如果“GC Roots”和一个对象之间没有可达路径,则称该对象不可达,然后标记这个对象,但是不可达对象不一定会被回收,被判定成不可达对象至少要经过两次标记过程才有可能被回收.

GC Root对象有:

1、 虚拟机栈中引用的对象(本地变量表)
2、 方法区中静态属性引用的对象
3、 方法区中常量引用的对象
4、 本地方法栈中引用的对象

垃圾回收算法:

由于java虚拟机规范没有对垃圾回收算法做出明确的规定,因此各大厂商可以采用不同的方式来实现垃圾回收收集器。

1、 Mark-Sweep(标记-清除)算法

最简单最基础的垃圾回收算法,它的实现比较简单。回收通过两个阶段:一是标记阶段,标记出所有需要被回收的对象。二是回收阶段,回收被标记的对象所占用的空间。但是这种算法有个较严重的缺点就是容易产生内存碎片。

2、 Cpoying(复制)算法

为了解决Mark-Sweep算法的产生碎片的缺陷,Copying算法把内存划分成大小相等的两块,每次使用其中的一块。当这一块内存使用完后,就把还存活着的对象复制到另外一块上去,然后把这块内存清空。这种算法虽然解决了碎片问题,但是也带来了不好的问题,那就是硬把内存缩减到成了原来的一半,而且当存活的对象很多的时候,这个算法的效率就会大打折扣了。

3、 Mark-Compact(标记-整理)算法

为了解决Copy算法的缺陷,充分利用内存空间。Mark-Compact算法就被提出了。这种算法分为两个阶段:一是标记阶段,这个阶段和Mark-Sweep的标记阶段一样,标记需要被回收的对象。二是回收阶段,不同于Mark-Sweep的是,把存活的对象移到一端,然后清理端边界以外的内存。

4、 Generational Collection(分代收集)算法

这种算法是目前大部分JVM采用的垃圾回收算法。它的核心思想是根据对象的存活的生命周期将内存划分成若干个不同的区域。一般情况下将堆区划分成老年代和新生代,老年代的特别是每次垃圾回收都有少量的对象被回收,而新生代特点是每次垃圾回收时都有大量的对象需要回收,因此就更具不同代的特别采用最合适的收集算法。
目前大部分垃圾收集器对于新生代都采用Copying算法,因为新生代中每次垃圾回收都要回收大部分的对象,也就是需要复制的操作次数较少,但是内存并不是按照1:1来划分的,二是把新生代划分成一块较大的Eden区和两块较小的Survivor区(一般是按8:1:1分配),每次使用Eden空间和其中一块Survior空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另外一块Survivor区区,然后清除Eden和刚才使用过的Survivor区。
新生代进入老年代:新生代中如果存在一个非常大的对象,则这个对象会直接进入老年代。新生代中对象经过了15次垃圾回收后还存在的则也会进入老年代
而老年代的特别是每次回收都只回收少量对象,一般使用的时候Mark-Compact算法。
注意的是:在堆区之外还有一个代就是永久代(Permanet Generation),它用来存储Class类,常量,方法描述等。对永久代的回收主要回收两部分内容:废弃常量和无用的类。

如何防止service被kill掉

1、 Service的onStartCommand()方法返回START_STICKY,这个值代表在系统杀死Service后,如果系统资源足够的情况下会重启service.
复习下onStartCommand()的返回值:
START_STICKY:如果service被进程kill掉,保留service的状态为开始状态,但不保留递送的intent对象。随后系统会尝试重新创建service,由于service状态是开始状态,因此,创建服务后一定会调用onStartCommand(Intent,int,int)方法,如果在此期间没有任何启动命令传递给service,那么参数Intent为null.
START_NOT_STIKY:“非粘性”。使用这个返回值时,如果执行完onStartCommand后,服务被异常kill掉,系统不会自动重启这个服务。
START_REDELIVER_INTENT:重传Intent,如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启这个服务,并将Intent的值传入。
START_STICKY_COMPATIBILITY:START_STICKY的兼容版,但不保证服务被kill后一定能重启
2、 通过startForeground将进程设置为前台进程,做前台服务,优先级较高,除非在系统内存非常缺,否者进程不会被kill
3、 双进程,让两个进程相互保护,其中一个被kill后,另外没被清理的进程可以立即重启进程。
4、 QQ黑科技:在应用程序退到后台,另外一个只有一个像素的页面停留在桌面,让自己保持前台状态,保护自己不被清理工具kill掉
5、 在已经root的设备下,修改相应的权限文件,将App伪装成系统级的应用(Android4.0系列的一个漏洞,已经确认可行)
6、 通过BroadcastReceiver + service,在service的onDestory()中发送广播,在BraodcastReceiver中启动service

AIDL的原理

1、 首先创建AIDL服务端,然后暴露这个服务端的接口给客户端。
在服务端,创建.AIDL文件存根(既Stub)对象,然后在onBind()方法中返回这个存根对象。
2、 客户端通过通过bind方式绑定这个服务端,然后在获取服务端在客户端的代理对象,然后就可以使用这个代理对象来和服务端通信了。
3、 他们之间的通信其实是通过底层的Binder来进行通信的,个人总结如下:
A、 首先,Service创建完成后向ServiceManager注册自己的Binder实体以及名字。
B、 Client通过名字向ServiceManager请求获取Service代理对象(既Service对应的Binder引用),如果Service已经在ServiceManager中注册后则返回Service代理对给Client。
C、 Client通过Service代理对象发起Service存在的服务的请求,请求经过Binder驱动(感觉这个Binder像路由器),然后请求到达相应的Service去。
D、 Service处理完请求后把请求的结果发送给Binder驱动,然后由Binder驱动发送回给相应的Client。

OOM发生与避免

1、 App加载大量的Bitmap对象,在使用完Bitmap后忘记调用Bitmap的recycler()方法。
解决方案:
检测Bitmap使用后是否存在recycler操作,在使用大量的Bitmap对象的时通过BitmapFactory.Options的inJustDecodeBounds=true的方式先加载Bitmap对象宽度和高度,然后再把Bitmap压缩成最小可接受的范围。在写缓存框架的时候,应当把使用软引用的方式来保存Bitmap
2、 使用Cursor忘记close
3、 Context泄露,比如生命周期较长的对象持有Activity引用导致Context泄露
解决方案:
考虑使用Application的Context代替Activity的Context
4、 静态变量使用完后没有置空操作
解决方案:
静态变量使用完后记得设置静态变量=null
5、 非静态类持有外部类的引用,但内部类生命周期超过外部类的生命周期
解决方案:
如果确定了内部类的生命周期超过外部类,则使用静态内部类

ANR

什么是ANR?

Android No Resoponse,Android应用程序没有响应,通常有两种情况会发生ANR:
1、秒内没有响应输入的事件(例如,按键按下,屏幕触屏)。
2、BroadcastReceiver在10秒内没有完成操作。
3、Service在20秒没有处理完成

如何避免ANR?

1、 在UI线程应尽可能少的去做事情。特别是,Activity应该在它的一些关键生命周期中(onCreate,onResume)不要进行耗时操作(如果存在耗时操作,可以考虑以下方法去解决:a、使用Handler +子线程的方式。b、使用Thread+runOnUiThread方法实现。c、使用AnsyTask方法去实现。d、使用RXJAVA来进行线程调度)
2、 避免在BroadcastReceiver中做耗时操作或计算,但是不要在子线程中做这些任务,因为BroadcastReceiver的生命周期短,替代的是,如果响应Intent广播需要执行一个耗时动作的话,应用程序应该启动一个Service(注意的是,可以在BroadcastReveiver中启动service,但service中启动BroadcastReceiver却是不可以的)
3、 避免在Broadcast中启动一个Activity,因为他会创建一个新的Activity,并且会在当前应用程序中抢夺焦点。取而代之的是使用NotificationManager来实现
4、 数据库操作,网络操作,IO操作,计算操作应当使用子线程来进行

上一篇下一篇

猜你喜欢

热点阅读