Android面试题

Activity的启动流程

  1. 点击桌面App图标,Launcher进程采用Binder IPC向system_server进程发起startActivity请求;
  2. system_server进程接收到请求后,向zygote进程发送创建进程的请求;
  3. Zygote进程fork出新的子进程,即App进程;
  4. App进程,通过Binder IPC向sytem_server进程发起attachApplication请求;
  5. system_server进程在收到请求后,进行一系列准备工作后,再通过binder IPC向App进 程发送scheduleLaunchActivity请求;
  6. App进程的binder线程(ApplicationThread)在收到请求后,通过handler向主线程发送LAUNCH_ACTIVITY消息;
  7. 主线程在收到Message后,通过发射机制创建目标Activity,并回调Activity.onCreate()等方法。

Activity四种启动模式

  1. 标准模式(默认模式)android:launchMode="standard"
    每次启动一个Activity就会创建一个新的实例。
  2. 栈顶复用模式:android:launchMode="singleTop"
    如果新Activity已经位于任务栈的栈顶,就不会重新创建,并回调onNewIntent()方法。
  3. 栈内复用模式:android:launchMode="singleTask"
    只要该Activity在一个任务栈中存在,都不会重新创建,并回调onNewIntent()方法
  4. 单实例模式: android:launchMode="singleInstance"
    具有此模式的Activity只能单独位于一个任务栈中,且此任务栈中只有唯一一个实例。

Android中有哪几种类型的动画

  • View动画(View Animation)/补间动画(Tween animation):对View进行平移、缩放、旋转和透明度变化的动画,不能真正的改变view的位置。应用如布局动画、Activity切换动画
  • 逐帧动画(Drawable Animation):是View动画的一种,它会按照顺序播放一组预先定义好的图片
  • 属性动画(Property Animation):对该类对象进行动画操作,真正改变了对象的属性

谈谈消息机制Hander

  • 作用:跨线程通信。当子线程中进行耗时操作后需要更新UI时,通过Handler将有关UI的操作切换到主线程中执行。
  • Message(消息):需要被传递的消息,其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,最终由Handler处理。
  • MessageQueue(消息队列):用来存放Handler发送过来的消息,内部通过单链表的数据结构来维护消息列表,等待Looper的抽取。
  • Handler(处理者):负责Message的发送及处理。通过 Handler.sendMessage() 向消息池发送各种消息事件;通过 Handler.handleMessage() 处理相应的消息事件。
  • Looper(轮询器):通过Looper.loop()不断地从MessageQueue中抽取Message,按分发机制将消息分发给目标处理者。

Handler导致的内存泄漏

  • 泄漏原因:Message持有对Handler的引用,而非静态内部类的Handler又隐式持有对外部类Activity的引用,使得引用关系会保持至消息得到处理,从而阻止了Activity的回收。
  • 防止泄漏:
    1、当外部类结束生命周期时清空消息队列removeCallbacksAndMessages(null)
    2、使用静态内部类+WeakReference弱引用

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

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

谈谈几个常用的框架的实现原理

Glide框架
Glide.with(content).load(url).into(imageView) ,with绑定生命周期,load指定加载资源,into指明加载目标。

with:创建一个无UI的Fragment,通过添加的这个Fragment 感知 Activity 、Fragment 的生命周期,将RequestManager存入到之这个Fragment的LifeCycle,
通过Lifecycle在Fragment关键生命周期通知RequestManger进行相关的操作。

缓存机制:
1、活动缓存, 弱应用 HashMap,如果当前对应的图片资源正在使用,则这个图片会被Glide放入活动缓存。
Map activeEngineResources = new HashMap<>();
2、内存缓存, LruCache LinkedHasMap,如果图片最近被加载过,并且当前没有使用这个图片,则会被放入内存中。
Map cache = new LinkedHashMap<>(100, 0.75f, true);
3、磁盘缓存(DiskLruCache)缓存图片到本地文件夹。
Retrofit框架
Retrofit通过动态代理,用MethodHandler完成接口方法。
Retrofit的MethodHandler通过RequestFactoryParser.parse解析,获得接口方法的参数和注解的值,传入到OkHttpCall,OkHttpCall生成okhttp3.Call完成Http请求并使用Converter解析数据回调。
Retrofit通过工厂设置CallAdapter和Converter,CallAdapter包装转换Call,Converter转换(解析)服务器返回的数据、接口方法的注解参数。

OkHttp 是一个高效的 HTTP 客户端,具有非常多的优势:
1、能够高效的执行 http,数据加载速度更快,更省流量
2、支持 GZIP 压缩,提升速度,节省流量
3、缓存响应数据,避免了重复的网络请求
4、使用简单,支持同步阻塞调用和带回调的异步调用
OkHttp流程:
1、采用责任链方式的拦截器,实现分成处理网络请求,可更好的扩展自定义拦截器(采用GZIP压缩,支持http缓存)
2、采用线程池(thread pool)和连接池(Socket pool)解决多并发问题,同时连接池支持多路复用(http2才支持,可以让一个Socket同时发送多个网络请求,内部自动维持顺序.相比http只能一个一个发送,更能减少创建开销))
3、底层采用socket和服务器进行连接.采用okio实现高效的io流读写

分化器 dispatcher:内部维护队列与线程池,完成请求调配。
// 双端队列,支持首尾两端 双向开口可进可出 
 // 准备运行的异步队列
 private final Deque readyAsyncCalls = new ArrayDeque<>();
 // 正在运行的异步
 private final Deque runningAsyncCalls = new ArrayDeque<>();
// 正在执行的同步队列
 private final Deque runningSyncCalls = new ArrayDeque<>();

拦截器:5个拦截器,完成整个请求过程
1、BridgeInterceptor(桥接拦截器):请求时,对必要的Header进行一些添加,接收响应时,移除必要的Header
2、RetryAndFollowUpInterceptor(重试与重定向拦截器):负责失败重试以及重定向
3、CacheInterceptor(缓存拦截器):负责读取缓存直接返回(根据请求的信息和缓存的响应的信息来判断是否存在缓存可用)、更新缓存
4、ConnectInterceptor(连接拦截器):负责和服务器建立连接
5、CallServerInterceptor(请求服务器拦截器):完成HTTP协议报文的封装和解析。
LeakCanary 原理
1、通过 registerActivityLifecycleCallbacks 监听Activity或者Fragment 销毁时候的生命周期。
在Application.ActivityLifecycleCallbacks 的 onActivityDestroyed方法调用RefWatcher.watch()实现。
2、通过弱引用和引用队列监控对象是否被回收(弱引用和引用队列ReferenceQueue联合使用时,
如果弱引用持有的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
即 KeyedWeakReference持有的Activity对象如果被垃圾回收,该对象就会加入到引用队列queue)。

Android 创建多个进程,Android中的进程通信

通过在配置清单中给四大组件设置android:process属性值,这样我们就可以轻易开启多进程模式。
Android中支持的多进程通信方式主要有以下几种,它们之间各有优缺点,可根据使用场景选择选择:
1、AIDL:功能强大,支持进程间一对多的实时并发通信,并可实现 RPC (远程过程调用)。
2、Messenger:支持一对多的串行实时通信, AIDL 的简化版本。
3、Bundle:四大组件的进程通信方式,只能传输 Bundle 支持的数据类型。
4、ContentProvider:强大的数据源访问支持,主要支持 CRUD 操作,一对多的进程间数据共享,例如我们的应用访问系统的通讯录数据。
5、BroadcastReceiver:即广播,但只能单向通信,接收者只能被动的接收消息。
6、文件共享:在非高并发情况下共享简单的数据。
7、Socket:通过网络传输数据。

谈谈 MVC、MVP、MVVM

  • MVC:
    模型层(Model) :针对业务模型,建立数据结构和相关的类,它主要负责网络请求,数据库处理,I/O的操作。
    视图层(View) :对应于xml布局文件和java代码动态view部分。
    控制层(Controller) :控制层是由Activity/Fragment来承担的,但是因为XML视图功能太弱,所以Activity/Fragment既要负责视图的显示又要加入控制逻辑,承担的功能过多,大项目就会遇到耦合过重,Activity/Fragment类过大等问题。
  • MVP:为了解决MVC耦合过重的问题,MVP的核心思想就是提供一个Presenter将视图逻辑和业务逻辑相分离,达到解耦的目的。
    但是随着业务逻辑的增加,一个页面可能会非常复杂,UI的改变是非常多,会有非常多的case,这样就会造成View的接口会很庞大。
  • MVVM:使用ViewModel代替Presenter,实现数据与View的双向绑定,这样就省去了很多在View层中写很多case的情况,只需要改变数据就行。

RecycleView 四级缓存

  1. 一级缓存:两个 ArrayList,一个存储当前还在屏幕中的 ViewHolder,另一个存储数据被更新的 ViewHolder
    final ArrayList mAttachedScrap = new ArrayList<>();
    ArrayList mChangedScrap = null;
  2. 二级缓存:ArrayList,默认大小为2,通常用来存储预取的 ViewHolder,同时在回收 ViewHolder时,也可能会存储一部分的 ViewHolder
    final ArrayList mCachedViews = new ArrayList();
  3. 三级缓存:ViewCacheExtension,自定义缓存【是空实现,一般都不会去实现它】
  4. 四级缓存:RecycledViewPool,缓存从二级缓存中移除的 ViewHolder。采用 SparseArray 保存其内部静态类 ScrapData,ScrapData 采用 ArrayList 存储 ViewHolder,ArrayList 大小默认为5,可动态改变。ViewHolder 从二级缓存移除,加入四级缓存前,会将数据全部清除,根据 ViewType 存储到 SparseArray 中。ViewHolder 服用时会重写 onBindViewHolder 方法填充数据。

AIDL简单讲解一下。底层实现的是什么机制?

AIDL是进程间通信的一种方式,底层实现是通过Binder机制来实现的。Binder机制底层又是通过mmap内存映射原理来实现的,内存分为用户空间和内核空间,Binder机制通过一次一次的数据拷贝来传递数据。

Binder机制了解

每个进程的内存空间都分为用户空间和内核空间,用户空间和内核空间之间的通信需要native层的方法来实现,方法叫copy_form_user()。进程A的内核空间和进程B的用户空间通过Linux的一个mmap方法来创建出一个共享的物理内存地址,然后进程A的用户空间和进程B的用户空间进行通信的话,就只需要进程A的用户空间往进程A的内核空间进行一次数据拷贝(copy_from_user),进程B的用户空间与进程A的内核空间通过mmap内存映射的方式,就能在共享的物理内存中获取到进程A拷贝的数据,就实现了通信。

AsyncTask原理

1 、AsyncTask中有两个线程池(SerialExecutor和THREAD_POOL_EXECUTOR)和一个Handler(InternalHandler),其中线程池SerialExecutor用于任务的排队,而线程池THREAD_POOL_EXECUTOR用于真正地执行任务,InternalHandler用于将执行环境从线程池切换到主线程。
2、InternalHandler是一个静态的Handler对象,为了能够将执行环境切换到主线程,这就要求sHandler这个对象必须在主线程创建。由于静态成员会在加载类的时候进行初始化,因此这就变相要求AsyncTask的类必须在主线程中加载,否则同一个进程中的AsyncTask都将无法正常工作。

谈谈对RxJava的理解

RxJava是基于响应式编程,基于事件流、实现异步操(类似于Android中的AsyncTask、Handler作用)作的库,基于事件流的链式调用,使得RxJava逻辑简洁、使用简单。RxJava原理是基于一种扩展的观察者模式,有四种角色:被观察者Observable 观察者Observer 订阅subscribe 事件Event。RxJava原理可总结为:被观察者Observable通过订阅(subscribe)按顺序发送事件(Emitter)给观察者(Observer), 观察者按顺序接收事件、作出相应的响应动作。

Android各版本新特性

1、Android5.0新特性:MaterialDesign设计风格、支持64位ART虚拟机、通知详情可以用户自己设计。
2、Android6.0新特性:动态权限管理、支持快速充电的切换、支持文件夹拖拽应用、相机新增专业模式。
3、Android7.0新特性:多窗口支持、V2签名、增强的Java8语言模式、夜间模式。
4、Android8.0(O)新特性:优化通知、画中画模式、自动填充框架、系统优化。
5、Android9.0(P)新特性:室内WIFI定位、“刘海”屏幕支持、安全增强。
6、Android10.0(Q)新特性:用户存储权限的变更、用户的定位权限的变更。

谈一下一次完整的http请求

首先进行DNS域名解析

  1. 三次握手建立TCP连接
  2. 客户端向服务器发送请求命令
  3. 客户端发送请求头信息
  4. 服务器应答 Http/1.1 200 OK
  5. 服务器返回相应头信息
  6. 服务器向客户端发送数据
  7. 服务器关闭TCP连接

谈一下一次完整的https请求

  1. 客户端发起https请求
  2. 服务器必须要有一套数字证书,可以自己制作,也可以向权威机构申请。这套证书其实就是一对公私钥。
  3. 服务器将自己的数字证书(含有公钥、证书的颁发机构等)发送给客户端。
  4. 客户端收到服务器端的数字证书之后,会对其进行验证,主要验证公钥是否有效,比如颁发机构,过期时间等等。如果不通过,则弹出警告框。如果证书没问题,则生成一个密钥(对称加密算法的密钥,其实是一个随机值),并且用证书的公钥对这个随机值加密。
  5. 客户端会发起https中的第二个请求,将加密之后的客户端密钥(随机值)发送给服务器。
  6. 服务器接收到客户端发来的密钥之后,会用自己的私钥对其进行非对称解密,解密之后得到客户端密钥,然后用客户端密钥对返回数据进行对称加密,这样数据就变成了密文。
  7. 服务器将加密后的密文返回给客户端。
  8. 客户端收到服务器发返回的密文,用自己的密钥(客户端密钥)对其进行对称解密,得到服务器返回的数据。

布局性能优化之Include、ViewStub、Merge

  • include标签常用于将布局中的公共部分提取出来供其他layout共用,以实现布局模块化,也是平常我们设计布局时用的最多的。
  • ViewStub就是一个宽高都为0的一个View,它默认是不可见的,只有通过调用setVisibility函数或者Inflate函数才会将其要装载的目标布局给加载出来,从而达到延迟加载的效果,这个要被加载的布局通过android:layout属性来设置。
  • 在一个布局中包含一个布局时,merge标签有助于消除视图层次结构中的冗余视图组。如果是merge标签,那么直接将其中的子元素添加到merge标签parent中,这样就保证了不会引入额外的层级。

非UI线程是否可以刷新UI

非UI线程是可以刷新UI的,前提是它要拥有自己的ViewRoot。如果想直接创建ViewRoot实例,你会发现找不到这个类。但是可以通过WindowManager。

因为通常的子线程更新UI的报错是ViewRootImpl类的checkThread函数抛出的,而ViewRootImpl在onResume中才会创建,所以在这之前子线程更新UI是不会报错的

class NonUiThread extends Thread{
      @Override
      public void run() {
         Looper.prepare();
         TextView tx = new TextView(MainActivity.this);
         tx.setText("non-UiThread update textview");
         WindowManager windowManager = MainActivity.this.getWindowManager();
         WindowManager.LayoutParams params = new WindowManager.LayoutParams(
             200, 200, 200, 200, WindowManager.LayoutParams.FIRST_SUB_WINDOW,
                 WindowManager.LayoutParams.TYPE_TOAST,PixelFormat.OPAQUE);
         windowManager.addView(tx, params); 
         Looper.loop();
     }
 }

Android ANR

Application Not Responding,应用无响应。

发生场景:

  • Service Timeout:比如前台服务在20s内未执行完成。
  • BroadcastQueue Timeout:比如前台广播在10s内未执行完成。
  • ContentProvider Timeout:内容提供者,在publish过超时10s。
  • InputDispatching Timeout: 输入事件分发超时5s,包括按键和触摸事件。

导致ANR无响应的常见原因 :

  • 主线程阻塞 (避免死锁的出现,使用子线程来处理耗时操作或阻塞任务)
  • IO阻塞 (文件读写或数据库操作放在子线程异步操作)
  • CPU大量计算,内存不足

定位分析:

  • ActivityManager( 996): ANR in ... ANR发生在哪个应用
  • PID:8625: 产生ANR应用的线程号
  • ActivityManager( 996): Reason: Input dispatching timed out ANR产生的原因
  • Load: 3.46 / 2.71 / 1.24 CPU前1分钟、5分钟、15分钟的CPU平均负载
  • ActivityManager( 996): CPU usage from 94ms to 643ms later

你可能感兴趣的:(Android面试题)