MQW-面试

1.android面试题

https://www.jianshu.com/p/c70989bd5f29  大厂面试题

https://mp.weixin.qq.com/s/XRaxusIxwavy8spEdYZkpg  性能优化

https://cloud.tencent.com/developer/article/1334825  ktolin协程1

http://www.imooc.com/article/74963  ktolin协程2

https://www.jianshu.com/p/3ffc11978602 ktolin协程3

android面试

//////////////////////////////////////////////////// Part1知识点//////////////////////////////////////////////////////////////

1.Dalvik、Art虚拟机

dalvik采用JIT即时编译,art采用预编译AOT,在apk运行之前翻译成本地机器指令

2.Activity:生命周期、launchMode。

3.Fragment:生命周期、懒加载、两种Adapter之间的区别。

4.BroadcastReceiver:基本概念、广播分类、权限。

5.Service:生命周期、onStartCommand的返回值(int)、startService和bindService的区别、相同进程&不同进程的交互

6.IntentService实现原理:内部有一个继承自Handler的ServiceHandler,通过Looper创建的 handelr对象 ,在handelrMessage中回调onHnadlerIntent给子类

7.事件分发:事件分发的原理、处理过的滑动冲突问题:

8.绘制view流程

9.自定义View:Canvas & Path

10.属性动画

11.设计模式

12.怎么分析内存泄漏

13.性能优化(APK大小、启动速度、布局、内存、电量、列表滑动)、工具使用

14.Handler机制(很多细节需要关注:如线程如何建立和退出消息循环等等)

15.Android中的多线程

16.ANR原理ANR分析

17.apk中的结构

18.apk打包流程

19.Binder机制

20.Activity启动流程、androidmanifest什么时候被解析

21.AMS、WMS原理

22.RxJava:操作符的应用场景、重点操作符的内部实现、和RxJava的对比。

Glide:流程、缓存相关的实现。

Retrofit:流程、动态代理 & 注解解析。

OkHttp:流程、队列实现、缓存实现、拦截器。

OkHttp源码分析:带你一起探究OKhttp源码,面对Http源码分析不在畏惧

Retrofit源码分析:带你一起探究Retrofit 源码,让你不再畏惧Retrofit的面试提问

RxJava源码分析:带你一起探究Rxjava源码,学会Rxjava竟如此简单

Glide源码分析:Glide高级详解缓存与解码复用

      EventBus源码分析:https://www.jianshu.com/p/b3486441d7df 

23.插件化(思想、实现原理)

24.组件化(优势、ARouter)blog.csdn.net/feiyu1947/article/details/85488791  知乎例子

25.Gradle (常用配置、多渠道打包)

26.Android的权限管理机制

27.Android6.0/7.0/8.0/9.0特性  https://blog.csdn.net/u012758803/article/details/54844903 

28.Android进程保活

29.ConstranLayout

30.ListView和RecyclerView区别https://blog.csdn.net/shu_lance/article/details/79566189 

31.AsyncTask的内部实现原理?https://www.cnblogs.com/absfree/p/5357678.html 

AsyncTask内部维护了一个线程池,是串行还是并行,怎么维护的?

     串行https://www.cnblogs.com/absfree/p/5357678.html

32.RecyclerView的拖拽怎么实现的?https://blog.csdn.net/aiynmimi/article/details/77744610 

33.ThreadLocal原理

34.画出Android的大体架构图

35.描述清点击 Android Studio 的 build 按钮后发生了什么,一个应用程序安装到手机上时发生了什么;

36.图片加载库相关,bitmap如何处理大图,如一张30M的大图,如何预- - 防OOM

37.https相关,如何验证证书的合法性,https中哪里用了对称加密,哪里用了非对称加密

38.设计模式相关(例如Android中哪里使用了观察者模式,单例模式相关)

39.sqlite升级,增加字段的语句

40.屏幕适配 (参照今日头条适配方案)

41.RecyclerView:缓存原理、和ListView的对比

42.安全相关(数据加密、代码混淆、WebView/JS交互)

43.存储:数据库升级&优化、ContentProvider,SharePreference

44.重要的工具类&源码实现:AsyncTask、HandlerThread、Handler、IntentService、LruCache、LinkedHashMap、SparseArray

45.liveData(JetPack)、ViewModel

46.Kotlin基础特性

Kotlin协程

//////////////////////////////////////////////////// Part2相关问题及描述//////////////////////////////////////////////////////////////

Activity的启动过程(不要回答生命周期)

app启动的过程有两种情况,第一种是从桌面launcher上点击相应的应用图标,第二种是在activity中通过调用startActivity来启动一个新的activity

我们创建一个新的项目,默认的根activity都是MainActivity,而所有的activity都是保存在堆栈中的,我们启动一个新的activity就会放在上一个activity上面,而我们从桌面点击应用图标的时候,由于launcher本身也是一个应用,当我们点击图标的时候,系统就会调用startActivitySately(),一般情况下,我们所启动的activity的相关信息都会保存在intent中,比如actioncategory等等。我们在安装这个应用的时候,系统也会启动一个PackaManagerService的管理服务,这个管理服务会对AndroidManifest.xml文件进行解析,从而得到应用程序中的相关信息,比如serviceactivityBroadcast等等,然后获得相关组件的信息。当我们点击应用图标的时候,就会调用startActivitySately()方法,而这个方法内部则是调用startActivty(),而startActivity()方法最终还是会调用startActivityForResult()这个方法。而在startActivityForResult()这个方法。因为startActivityForResult()方法是有返回结果的,所以系统就直接给一个-1,就表示不需要结果返回了。而startActivityForResult()这个方法实际是通过Instrumentation类中的execStartActivity()方法来启动activityInstrumentation这个类主要作用就是监控程序和系统之间的交互。而在这个execStartActivity()方法中会获取ActivityManagerService的代理对象,通过这个代理对象进行启动activity。启动会就会调用一个checkStartActivityResult()方法,如果说没有在配置清单中配置有这个组件,就会在这个方法中抛出异常了。当然最后是调用的是Application.scheduleLaunchActivity()进行启动activity,而这个方法中通过获取得到一个ActivityClientRecord对象,而这个ActivityClientRecord通过handler来进行消息的发送,系统内部会将每一个activity组件使用ActivityClientRecord对象来进行描述,而ActivityClientRecord对象中保存有一个LoaderApk对象,通过这个对象调用handleLaunchActivity来启动activity组件,而页面的生命周期方法也就是在这个方法中进行调用。

Android内存优化

Android的内存优化在我看来分为两点:避免内存泄漏、扩大内存,其实就是开源节流。

其实内存泄漏的本质就是较长生命周期的对象引用了较短生命周期的对象。

常见的内存泄漏:

单例模式导致的内存泄漏。最常见的例子就是创建这个单例对象需要传入一个Context,这时候传入了一个Activity类型的Context,由于单例对象的静态属性,导致它的生命周期是从单例类加载到应用程序结束为止,所以即使已经finish掉了传入的Activity,由于我们的单例对象依然持有Activity的引用,所以导致了内存泄漏。解决办法也很简单,不要使用Activity类型的Context,使用Application类型的Context可以避免内存泄漏。

静态变量导致的内存泄漏。静态变量是放在方法区中的,它的生命周期是从类加载到程序结束,可以看到静态变量生命周期是非常久的。最常见的因静态变量导致内存泄漏的例子是我们在Activity中创建了一个静态变量,而这个静态变量的创建需要传入Activity的引用this。在这种情况下即使Activity调用了finish也会导致内存泄漏。原因就是因为这个静态变量的生命周期几乎和整个应用程序的生命周期一致,它一直持有Activity的引用,从而导致了内存泄漏。

非静态内部类导致的内存泄漏。非静态内部类导致内存泄漏的原因是非静态内部类持有外部类的引用,最常见的例子就是在Activity中使用Handler和Thread了。使用非静态内部类创建的Handler和Thread在执行延时操作的时候会一直持有当前Activity的引用,如果在执行延时操作的时候就结束Activity,这样就会导致内存泄漏。解决办法有两种:第一种是使用静态内部类,在静态内部类中使用弱引用调用Activity。第二种方法是在Activity的onDestroy中调用handler.removeCallbacksAndMessages来取消延时事件。

使用资源未及时关闭导致的内存泄漏。常见的例子有:操作各种数据流未及时关闭,操作Bitmap未及时recycle等等。

使用第三方库未能及时解绑。有的三方库提供了注册和解绑的功能,最常见的就是EventBus了,我们都知道使用EventBus要在onCreate中注册,在onDestroy中解绑。如果没有解绑的话,EventBus其实是一个单例模式,他会一直持有Activity的引用,导致内存泄漏。同样常见的还有RxJava,在使用Timer操作符做了一些延时操作后也要注意在onDestroy方法中调用disposable.dispose()来取消操作。

属性动画导致的内存泄漏。常见的例子就是在属性动画执行的过程中退出了Activity,这时View对象依然持有Activity的引用从而导致了内存泄漏。解决办法就是在onDestroy中调用动画的cancel方法取消属性动画。

WebView导致的内存泄漏。WebView比较特殊,即使是调用了它的destroy方法,依然会导致内存泄漏。其实避免WebView导致内存泄漏的最好方法就是让WebView所在的Activity处于另一个进程中,当这个Activity结束时杀死当前WebView所处的进程即可,我记得阿里钉钉的WebView就是另外开启的一个进程,应该也是采用这种方法避免内存泄漏。

扩大内存,为什么要扩大我们的内存呢?有时候我们实际开发中不可避免的要使用很多第三方商业的SDK,这些SDK其实有好有坏,大厂的SDK可能内存泄漏会少一些,但一些小厂的SDK质量也就不太靠谱一些。那应对这种我们无法改变的情况,最好的办法就是扩大内存。

扩大内存通常有两种方法:一个是在清单文件中的Application下添加largeHeap=”true”这个属性,另一个就是同一个应用开启多个进程来扩大一个应用的总内存空间。第二种方法其实就很常见了,比方说我使用过个推的SDK,个推的Service其实就是处在另外一个单独的进程中。

Android中的内存优化总的来说就是开源和节流,开源就是扩大内存,节流就是避免内存泄漏。

Binder机制-1Android系统中,每一个应用程序都运行在独立的进程中,这也保证了当其中一个程序出现异常而不会影响另一个应用程序的正常运转。在许多情况下,我们activity都会与各种系统的service打交道,很显然,我们写的程序中activity与系统service肯定不是同一个进程,但是它们之间是怎样实现通信的呢?所以Binderandroid中一种实现进程间通信(IPC)的方式之一。

1

).首先,Binder分为ClientServer两个进程。注意,ClientServer是相对的。谁发消息,谁就是Client,谁接收消息,谁就是Server。举个例子,两个进程AB之间使用Binder通信,进程A发消息给进程B,那么这时候ABinder ClientBBinder Server;进程B发消息给进程A,那么这时候BBinder ClientABinder Server——其实这么说虽然简单了,但还是不太严谨,我们先这么理解着。

2

).其次,我们看下面这个图(摘自田维术的博客),基本说明白了Binder的组成解构:

图中的IPC就是进程间通信的意思。图中的ServiceManager,负责把Binder Server注册到一个容器中。有人把ServiceManager比喻成电话局,存储着每个住宅的座机电话,还是很恰当的。张三给李四打电话,拨打电话号码,会先转接到电话局,电话局的接线员查到这个电话号码的地址,因为李四的电话号码之前在电话局注册过,所以就能拨通;没注册,就会提示该号码不存在。对照着Android Binder机制,对着上面这图,张三就是Binder Client,李四就是Binder Server,电话局就是ServiceManager,电话局的接线员在这个过程中做了很多事情,对应着图中的Binder驱动.

3

).接下来我们看Binder通信的过程,还是摘自田维术博客的一张图:

注:图中的SM也就是ServiceManager。我们看到,Client想要直接调用Serveradd方法,是不可以的,因为它们在不同的进程中,这时候就需要Binder来帮忙了。首先是ServerSM这个容器中注册。其次,Client想要调用Serveradd方法,就需要先获取Server对象, 但是SM不会把真正的Server对象返回给Client,而是把Server的一个代理对象返回给Client,也就是Proxy。然后,Client调用Proxyadd方法,SM会帮他去调用Serveradd方法,并把结果返回给Client。以上这3步,Binder驱动出了很多力,但我们不需要知道Binder驱动的底层实现,涉及到C++的代码了——把有限的时间去做更有意义的事情。(ps:以上节选自包建强老师的文章点我点我 ).

为什么android选用Binder来实现进程间通信?

1

).可靠性。在移动设备上,通常采用基于Client-Server的通信方式来实现互联网与设备间的内部通信。目前linux支持IPC包括传统的管道,System V IPC,即消息队列/共享内存/信号量,以及socket中只有socket支持Client-Server的通信方式。Android系统为开发者提供了丰富进程间通信的功能接口,媒体播放,传感器,无线传输。这些功能都由不同的server来管理。开发都只关心将自己应用程序的clientserver的通信建立起来便可以使用这个服务。毫无疑问,如若在底层架设一套协议来实现Client-Server通信,增加了系统的复杂性。在资源有限的手机 上来实现这种复杂的环境,可靠性难以保证。

2

).传输性能。socket主要用于跨网络的进程间通信和本机上进程间的通信,但传输效率低,开销大。消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的一块缓存区中,然后从内核缓存区拷贝到接收方缓存区,其过程至少有两次拷贝。虽然共享内存无需拷贝,但控制复杂。比较各种IPC方式的数据拷贝次数。共享内存:0次。Binder1次。Socket/管道/消息队列:2次。

3

).安全性。Android是一个开放式的平台,所以确保应用程序安全是很重要的。Android对每一个安装应用都分配了UID/PID,其中进程的UID是可用来鉴别进程身份。传统的只能由用户在数据包里填写UID/PID,这样不可靠,容易被恶意程序利用。而我们要求由内核来添加可靠的UID。所以,出于可靠性、传输性、安全性。android建立了一套新的进程间通信方式。

Binder机制-2

在Linux中,为了避免一个进程对其他进程的干扰,进程之间是相互独立的。在一个进程中其实还分为用户空间和内核空间。这里的隔离分为两个部分,进程间的隔离和进程内的隔离。

既然进程间存在隔离,那其实也是存在着交互。进程间通信就是IPC,用户空间和内核空间的通信就是系统调用。

Linux为了保证独立性和安全性,进程之间不能直接相互访问,Android是基于Linux的,所以也是需要解决进程间通信的问题。

其实Linux进程间通信有很多方式,比如管道、socket等等。为什么Android进程间通信采用了Binder而不是Linux已有的方式,主要是有这么两点考虑:性能和安全

性能。在移动设备上对性能要求是比较严苛的。Linux传统的进程间通信比如管道、socket等等进程间通信是需要复制两次数据,而Binder则只需要一次。所以Binder在性能上是优于传统进程通信的。

安全。传统的Linux进程通信是不包含通信双方的身份验证的,这样会导致一些安全性问题。而Binder机制自带身份验证,从而有效的提高了安全性。

Binder是基于CS架构的,有四个主要组成部分。

Client。客户端进程。

Server。服务端进程。

ServiceManager。提供注册、查询和返回代理服务对象的功能。

Binder驱动。主要负责建立进程间的Binder连接,进程间的数据交互等等底层操作。

Binder机制主要的流程是这样的:

服务端通过Binder驱动在ServiceManager中注册我们的服务。

客户端通过Binder驱动查询在ServiceManager中注册的服务。

ServiceManager通过Binder驱动返回服务端的代理对象。

客户端拿到服务端的代理对象后即可进行进程间通信。

LruCache的原理

LruCache的核心原理就是对LinkedHashMap的有效利用,它的内部存在一个LinkedHashMap成员变量。值得我们关注的有四个方法:构造方法、get、put、trimToSize。

构造方法:在LruCache的构造方法中做了两件事,设置了maxSize、创建了一个LinkedHashMap。这里值得注意的是LruCache将LinkedHashMap的accessOrder设置为了true,accessOrder就是遍历这个LinkedHashMap的输出顺序。true代表按照访问顺序输出,false代表按添加顺序输出,因为通常都是按照添加顺序输出,所以accessOrder这个属性默认是false,但我们的LruCache需要按访问顺序输出,所以显式的将accessOrder设置为true。

get方法:本质上是调用LinkedHashMap的get方法,由于我们将accessOrder设置为了true,所以每调用一次get方法,就会将我们访问的当前元素放置到这个LinkedHashMap的尾部。

put方法:本质上也是调用了LinkedHashMap的put方法,由于LinkedHashMap的特性,每调用一次put方法,也会将新加入的元素放置到LinkedHashMap的尾部。添加之后会调用trimToSize方法来保证添加后的内存不超过maxSize。

trimToSize方法:trimToSize方法的内部其实是开启了一个while(true)的死循环,不断的从LinkedHashMap的首部删除元素,直到删除之后的内存小于maxSize之后使用break跳出循环。

其实到这里我们可以总结一下,为什么这个算法叫 最近最少使用 算法呢?原理很简单,我们的每次put或者get都可以看做一次访问,由于LinkedHashMap的特性,会将每次访问到的元素放置到尾部。当我们的内存达到阈值后,会触发trimToSize方法来删除LinkedHashMap首部的元素,直到当前内存小于maxSize。为什么删除首部的元素,原因很明显:我们最近经常访问的元素都会放置到尾部,那首部的元素肯定就是 最近最少使用 的元素了,因此当内存不足时应当优先删除这些元素。

Android中的事件分发机制

在我们的手指触摸到屏幕的时候,事件其实是通过 Activity -> ViewGroup -> View 这样的流程到达最后响应我们触摸事件的View。

说到事件分发,必不可少的是这几个方法:dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent。接下来就按照Activity -> ViewGroup -> View的流程来大致说一下事件分发机制。

我们的手指触摸到屏幕的时候,会触发一个Action_Down类型的事件,当前页面的Activity会首先做出响应,也就是说会走到Activity的dispatchTouchEvent()方法内。在这个方法内部简单来说是这么一个逻辑:

调用getWindow.superDispatchTouchEvent()。

如果上一步返回true,直接返回true;否则就return自己的onTouchEvent()。

这个逻辑很好理解,getWindow().superDispatchTouchEvent()如果返回true代表当前事件已经被处理,无需调用自己的onTouchEvent;否则代表事件并没有被处理,需要Activity自己处理,也就是调用自己的onTouchEvent。

getWindow()方法返回了一个Window类型的对象,这个我们都知道,在Android中,PhoneWindow是Window的唯一实现类。所以这句本质上是调用了PhoneWindow中的superDispatchTouchEvent()。

而在PhoneWindow的这个方法中实际调用了mDecor.superDispatchTouchEvent(event)。这个mDecor就是DecorView,它是FrameLayout的一个子类,在DecorView中的superDispatchTouchEvent()中调用的是super.dispatchTouchEvent()。到这里就很明显了,DecorView是一个FrameLayout的子类,FrameLayout是一个ViewGroup的子类,本质上调用的还是ViewGroup的dispatchTouchEvent()。

分析到这里,我们的事件已经从Activity传递到了ViewGroup,接下来我们来分析下ViewGroup中的这几个事件处理方法。

在ViewGroup中的dispatchTouchEvent()中的逻辑大致如下:

通过onInterceptTouchEvent()判断当前ViewGroup是否拦截事件,默认的ViewGroup都是不拦截的;

如果拦截,则return自己的onTouchEvent();

如果不拦截,则根据child.dispatchTouchEvent()的返回值判断。如果返回true,则return true;否则return自己的onTouchEvent(),在这里实现了未处理事件的向上传递。

通常情况下ViewGroup的onInterceptTouchEvent()都返回false,也就是不拦截。这里需要注意的是事件序列,比如Down事件、Move事件……Up事件,从Down到Up是一个完整的事件序列,对应着手指从按下到抬起这一系列的事件,如果ViewGroup拦截了Down事件,那么后续事件都会交给这个ViewGroup的onTouchEvent。如果ViewGroup拦截的不是Down事件,那么会给之前处理这个Down事件的View发送一个Action_Cancel类型的事件,通知子View这个后续的事件序列已经被ViewGroup接管了,子View恢复之前的状态即可。

这里举一个常见的例子:在一个Recyclerview钟有很多的Button,我们首先按下了一个button,然后滑动一段距离再松开,这时候Recyclerview会跟着滑动,并不会触发这个button的点击事件。这个例子中,当我们按下button时,这个button接收到了Action_Down事件,正常情况下后续的事件序列应该由这个button处理。但我们滑动了一段距离,这时Recyclerview察觉到这是一个滑动操作,拦截了这个事件序列,走了自身的onTouchEvent()方法,反映在屏幕上就是列表的滑动。而这时button仍然处于按下的状态,所以在拦截的时候需要发送一个Action_Cancel来通知button恢复之前状态。

事件分发最终会走到View的dispatchTouchEvent()中。在View的dispatchTouchEvent()中没有onInterceptTouchEvent(),这也很容易理解,View不是ViewGroup,不会包含其他子View,所以也不存在拦截不拦截这一说。忽略一些细节,View的dispatchTouchEvent()中直接return了自己的onTouchEvent()。如果onTouchEvent()返回true代表事件被处理,否则未处理的事件会向上传递,直到有View处理了事件或者一直没有处理,最终到达了Activity的onTouchEvent()终止。

这里经常有人问onTouch和onTouchEvent的区别。首先,这两个方法都在View的dispatchTouchEvent()中,是这么一个逻辑:

如果touchListener不为null,并且这个View是enable的,而且onTouch返回的是true,满足这三个条件时会直接return true,不会走onTouchEvent()方法。

上面只要有一个条件不满足,就会走到onTouchEvent()方法中。所以onTouch的顺序是在onTouchEvent之前的。

View的绘制流程

视图绘制的起点在ViewRootImpl类的performTraversals()方法,在这个方法内其实是按照顺序依次调用了mView.measure()、mView.layout()、mView.draw()

View的绘制流程分为3步:测量、布局、绘制,分别对应3个方法measure、layout、draw。

测量阶段。measure方法会被父View调用,在measure方法中做一些优化和准备工作后会调用onMeasure方法进行实际的自我测量。onMeasure方法在View和ViewGroup做的事情是不一样的:

View。View中的onMeasure方法会计算自己的尺寸并通过setMeasureDimension保存。

ViewGroup。ViewGroup中的onMeasure方法会调用所有子View的measure方法进行自我测量并保存。然后通过子View的尺寸和位置计算出自己的尺寸并保存。

布局阶段。layout方法会被父View调用,layout方法会保存父View传进来的尺寸和位置,并调用onLayout进行实际的内部布局。onLayout在View和ViewGroup中做的事情也是不一样的:

View。因为View是没有子View的,所以View的onLayout里面什么都不做。

ViewGroup。ViewGroup中的onLayout方法会调用所有子View的layout方法,把尺寸和位置传给他们,让他们完成自我的内部布局。

绘制阶段。draw方法会做一些调度工作,然后会调用onDraw方法进行View的自我绘制。draw方法的调度流程大致是这样的:

绘制背景。对应drawBackground(Canvas)方法。

绘制主体。对应onDraw(Canvas)方法。

绘制子View。对应dispatchDraw(Canvas)方法。

绘制滑动相关和前景。对应onDrawForeground(Canvas)。

Android源码中常见的设计模式以及自己在开发中常用的设计模式

Androidjs是如何交互的

在Android中,Android与js的交互分为两个方面:Android调用js里的方法、js调用Android中的方法。

Android调js。Android调js有两种方法:

WebView.loadUrl(“javascript:js中的方法名”)。这种方法的优点是很简洁,缺点是没有返回值,如果需要拿到js方法的返回值则需要js调用Android中的方法来拿到这个返回值。

WebView.evaluateJavaScript(“javascript:js中的方法名”,ValueCallback)。这种方法比loadUrl好的是可以通过ValueCallback这个回调拿到js方法的返回值。缺点是这个方法Android4.4才有,兼容性较差。不过放在2018年来说,市面上绝大多数App都要求最低版本是4.4了,所以我认为这个兼容性问题不大。

js调Android。js调Android有三种方法:

WebView.addJavascriptInterface()。这是官方解决js调用Android方法的方案,需要注意的是要在供js调用的Android方法上加上 @JavascriptInterface 注解,以避免安全漏洞。这种方案的缺点是Android4.2以前会有安全漏洞,不过在4.2以后已经修复了。同样,在2018年来说,兼容性问题不大。

重写WebViewClient的shouldOverrideUrlLoading()方法来拦截url,拿到url后进行解析,如果符合双方的规定,即可调用Android方法。优点是避免了Android4.2以前的安全漏洞,缺点也很明显,无法直接拿到调用Android方法的返回值,只能通过Android调用js方法来获取返回值。

重写WebChromClient的onJsPrompt()方法,同前一个方式一样,拿到url之后先进行解析,如果符合双方规定,即可调用Android方法。最后如果需要返回值,通过result.confirm(“Android方法返回值”)即可将Android的返回值返回给js。方法的优点是没有漏洞,也没有兼容性限制,同时还可以方便的获取Android方法的返回值。其实这里需要注意的是在WebChromeClient中除了onJsPrompt之外还有onJsAlert和onJsConfirm方法。那么为什么不选择另两个方法呢?原因在于onJsAlert是没有返回值的,而onJsConfirm只有true和false两个返回值,同时在前端开发中prompt方法基本不会被调用,所以才会采用onJsPrompt。

SparseArray原理

SparseArray,通常来讲是Android中用来替代HashMap的一个数据结构。

准确来讲,是用来替换key为Integer类型,value为Object类型的HashMap。需要注意的是SparseArray仅仅实现了Cloneable接口,所以不能用Map来声明。

从内部结构来讲,SparseArray内部由两个数组组成,一个是int[]类型的mKeys,用来存放所有的键;另一个是Object[]类型的mValues,用来存放所有的值。

最常见的是拿SparseArray跟HashMap来做对比,由于SparseArray内部组成是两个数组,所以占用内存比HashMap要小。我们都知道,增删改查等操作都首先需要找到相应的键值对,而SparseArray内部是通过二分查找来寻址的,效率很明显要低于HashMap的常数级别的时间复杂度。提到二分查找,这里还需要提一下的是二分查找的前提是数组已经是排好序的,没错,SparseArray中就是按照key进行升序排列的。

综合起来来说,SparseArray所占空间优于HashMap,而效率低于HashMap,是典型的时间换空间,适合较小容量的存储。

从源码角度来说,我认为需要注意的是SparseArray的remove()、put()和gc()方法。

remove()。SparseArray的remove()方法并不是直接删除之后再压缩数组,而是将要删除的value设置为DELETE这个SparseArray的静态属性,这个DELETE其实就是一个Object对象,同时会将SparseArray中的mGarbage这个属性设置为true,这个属性是便于在合适的时候调用自身的gc()方法压缩数组来避免浪费空间。这样可以提高效率,如果将来要添加的key等于删除的key,那么会将要添加的value覆盖DELETE。

gc()。SparseArray中的gc()方法跟JVM的GC其实完全没有任何关系。gc()方法的内部实际上就是一个for循环,将value不为DELETE的键值对往前移动覆盖value为DELETE的键值对来实现数组的压缩,同时将mGarbage置为false,避免内存的浪费。

put()。put方法是这么一个逻辑,如果通过二分查找在mKeys数组中找到了key,那么直接覆盖value即可。如果没有找到,会拿到与数组中与要添加的key最接近的key索引,如果这个索引对应的value为DELETE,则直接把新的value覆盖DELETE即可,在这里可以避免数组元素的移动,从而提高了效率。如果value不为DELETE,会判断mGarbage,如果为true,则会调用gc()方法压缩数组,之后会找到合适的索引,将索引之后的键值对后移,插入新的键值对,这个过程中可能会触发数组的扩容。

图片加载如何避免OOM

我们知道内存中的Bitmap大小的计算公式是:长所占像素 宽所占像素 每个像素所占内存。想避免OOM有两种方法:等比例缩小长宽、减少每个像素所占的内存。

等比缩小长宽。我们知道Bitmap的创建是通过BitmapFactory的工厂方法,decodeFile()、decodeStream()、decodeByteArray()、decodeResource()。这些方法中都有一个Options类型的参数,这个Options是BitmapFactory的内部类,存储着BItmap的一些信息。Options中有一个属性:inSampleSize。我们通过修改inSampleSize可以缩小图片的长宽,从而减少BItmap所占内存。需要注意的是这个inSampleSize大小需要是2的幂次方,如果小于1,代码会强制让inSampleSize为1。

减少像素所占内存。Options中有一个属性inPreferredConfig,默认是ARGB_8888,代表每个像素所占尺寸。我们可以通过将之修改为RGB_565或者ARGB_4444来减少一半内存。

EventBus

EventBus源码分析

大致是这么一个流程:

register:

获取订阅者的Class对象

使用反射查找订阅者中的事件处理方法集合

遍历事件处理方法集合,调用subscribe(subscriber,subscriberMethod)方法,在subscribe方法内:

如果事件继承性为true,遍历这个Map类型的stickEvents,通过isAssignableFrom方法判断当前事件是否是遍历事件的父类,如果是则发送事件

如果事件继承性为false,通过stickyEvents.get(eventType)获取事件并发送

如果事件类型集合为空则创建一个新的集合,这一步目的是延迟集合的初始化

拿到事件类型集合后将新的事件类型加入到集合中

如果Subscription集合为空则创建一个新的集合,这一步目的是延迟集合的初始化

拿到Subscription集合后遍历这个集合,通过比较事件处理的优先级,将新的Subscription对象加入合适的位置

通过subscriberMethod获取处理的事件类型eventType

将订阅者subscriber和方法subscriberMethod绑在一起形成一个Subscription对象

通过subscriptionsByEventType.get(eventType)获取Subscription集合

通过typesBySubscriber.get(subscriber)获取事件类型集合

判断当前事件类型是否是sticky

如果当前事件类型不是sticky(粘性事件),subscribe(subscriber,subscriberMethod)到此终结

如果是sticky,判断EventBus中的一个事件继承性的属性,默认是true

post:

postSticky

将事件加入到stickyEvents这个Map类型的集合中

调用post方法

post

事件继承性为true,找到当前事件所有的父类型并调用postSingleEventForEventType方法发送事件

事件继承性为false,只发送当前事件类型的事件

在postToSubscription中分为四种情况

POSTING,调用invokeSubscriber(subscription,event)处理事件,本质是method.invoke()反射

MAIN,如果在主线程直接invokeSubscriber处理;反之通过handler切换到主线程调用invokeSubscriber处理事件

BACKGROUND,如果不在主线程直接invokeSubscriber处理事件;反之开启一条线程,在线程中调用invokeSubscriber处理事件

ASYNC,开启一条线程,在线程中调用invokeSubscriber处理事件

在postSingleEventForEventType中,通过subscriptionsByEventType.get(eventClass)获取Subscription类型集合

遍历这个集合,调用postToSubscription发送事件

将事件加入当前线程的事件队列中

通过while循环不断从事件队列中取出事件并调用postSingleEvent方法发送事件

在postSingleEvent中,判断事件继承性,默认为true

unregister:

删除subscriptionsByEventType中与订阅者相关的所有subscription

删除typesBySubscriber中与订阅者相关的所有类型

插件化相关技术,热修补技术是怎样实现的,和插件化有什么区别

相同点:都使用ClassLoader来实现的加载的新的功能类,都可以使用PathClassLoaderDexClassLoader

不同点:热修复因为是为了修复Bug的,所以要将新的同名类替代同名的Bug类,要抢先加载新的类而不是Bug类,所以多做两件事:在原先的app打包的时候,阻止相关类去打上CLASS_ISPREVERIFIED标志,还有在热修复时动态改变BaseDexClassLoader对象间接引用的dexElements,这样才能抢先代替Bug类,完成系统不加载旧的Bug.

而插件化只是增肌新的功能类或者是资源文件,所以不涉及抢先加载旧的类这样的使命,就避过了阻止相关类去打上CLASS_ISPREVERIFIED标志和还有在热修复时动态改变BaseDexClassLoader对象间接引用的dexElements.

所以插件化比热修复简单,热修复是在插件化的基础上在进行替旧的Bug

//////////////////////////////////////////////////// Part3问题点博客详情//////////////////////////////////////////////////////////////

1.Activity的启动过程(不要回答生命周期)

http://blog.csdn.net/luoshengyang/article/details/6689748

2.Activity的启动模式以及使用场景

(1)manifest设置,(2)startActivity flag

http://blog.csdn.net/CodeEmperor/article/details/50481726

此处延伸:栈(First In Last Out)与队列(First In First Out)的区别

3.Service的两种启动方式

(1)startService(),(2)bindService()

http://www.jianshu.com/p/2fb6eb14fdec

4.Broadcast注册方式与区别

(1)静态注册(minifest),(2)动态注册

http://www.jianshu.com/p/ea5e233d9f43

此处延伸:什么情况下用动态注册

5.HttpClientHttpUrlConnection的区别

http://blog.csdn.net/guolin_blog/article/details/12452307

此处延伸:Volley里用的哪种请求方式(2.3前HttpClient,2.3后HttpUrlConnection)

6.httphttps的区别

http://blog.csdn.net/whatday/article/details/38147103

此处延伸:https的实现原理

7.手写算法(选择冒泡必须要会)

http://www.jianshu.com/p/ae97c3ceea8d

8.进程保活(不死进程)

http://www.jianshu.com/p/63aafe3c12af

此处延伸:进程的优先级是什么(下面这篇文章,都有说)

https://segmentfault.com/a/1190000006251859

9.进程间通信的方式

(1)AIDL,(2)广播,(3)Messenger

AIDL :https://www.jianshu.com/p/a8e43ad5d7d2

https://www.jianshu.com/p/0cca211df63c

Messenger :

http://blog.csdn.net/lmj623565791/article/details/47017485

此处延伸:简述Binder ,

http://blog.csdn.net/luoshengyang/article/details/6618363/

10.加载大图

http://blog.cdn.net/lmj623565791/article/details/49300989

11.三级缓存(各大图片框架都可以扯到这上面来)

(1)内存缓存,(2)本地缓存,(3)网络

内存:http://blog.csdn.net/guolin_blog/article/details/9526203

本地:http://blog.csdn.net/guolin_blog/article/details/28863651

12.MVP框架(必问)

http://blog.csdn.net/lmj623565791/article/details/46596109

此处延伸:手写mvp例子,与mvc之间的区别,mvp的优势

13.讲解一下Context

http://blog.csdn.net/lmj623565791/article/details/40481055

14.JNI

http://www.jianshu.com/p/aba734d5b5cd

此处延伸:项目中使用JNI的地方,如:核心逻辑,密钥,加密逻辑

15.java虚拟机和Dalvik虚拟机的区别

http://www.jianshu.com/p/923aebd31b65

16.线程sleepwait有什么区别

http://blog.csdn.net/liuzhenwen/article/details/4202967

17.ViewViewGroup事件分发

http://blog.csdn.net/guolin_blog/article/details/9097463

http://blog.csdn.net/guolin_blog/article/details/9153747

18.保存Activity状态

onSaveInstanceState()

http://blog.csdn.net/yuzhiboyi/article/details/7677026

19.WebViewjs交互(调用哪些API

http://blog.csdn.net/cappuccinolau/article/details/8262821/

20.内存泄露检测,内存性能优化

http://blog.csdn.net/guolin_blog/article/details/42238627

21.布局优化

http://blog.csdn.net/guolin_blog/article/details/43376527

22.自定义view和动画

以下两个讲解都讲得很透彻,这部分面试官多数不会问很深,要么就给你一个效果让你讲原理。

(1)http://www.gcssloop.com/customview/CustomViewIndex

(2)http://blog.csdn.net/yanbober/article/details/50577855

你可能感兴趣的:(MQW-面试)