这篇专门记录面试中问到的问题, 并对其中回答的不好的或者没回答上来的问题进行复盘
# 请描述一下支付宝支付完整的请求流程
功能流程
数据交互
我回去看了一下我们的代码, 首先我们的客户端会把价格和数量等传给我们的服务端, 然后服务端返回给我们的客户端一个订单信息, 返回的订单信息里有一个参数叫payInfo, 是按照支付宝文档要求拼接好的keyvalue形式的字符串并且已经签过名, 然后就这样
1 | Runnable payRunnable = new Runnable() { |
使用payInfo构造一个PayTask对象, 然后在子线程中异步调用, 这样就调起支付宝客户端了.
在handler收到消息之后客户端将结果数据传给我们的服务端, 进行验签
1 | private static Handler mHandler = new Handler() { |
然后我们的服务端会返回给客户端一个支付结果, 最终客户端根据这个支付结果来显示是支付成功还是支付失败.
这是支付宝官方对流程的描述
- 构造订单数据并签名
商户服务器端根据手机支付宝支付开发包的接口规则,通过程序生成得到签名结果及要传输给手机支付宝支付开发包的数据集合。签名相关的公私钥生成及配置规则,见PID和密钥管理。 - 发送请求数据
把构造完成的数据集合传递给手机支付宝支付开发包。 - 手机支付宝支付开发包对请求数据进行处理
手机支付宝支付开发包将请求数据根据业务规则包装后传递给手机支付宝支付服务器端,服务器端得到这些集合后,会先进行安全校验等验证,一系列验证通过后便会处理完成这次发送过来的数据请求。 - 返回处理的结果数据
对于处理完成的交易,支付宝会以两种方式把数据分别反馈给商户客户端和商户服务器端。- 在手机客户端上,手机支付宝支付开发包直接把处理的数据结果反馈给商户客户端;
- 在服务器端上,手机支付宝支付服务器端主动发起通知,调用商户在请求时设定好的页面路径(参数notify_url,如果商户没设定,则不会进行该操作)。
- 商户对获取的返回结果数据进行处理
商户在客户端同步通知接收模块或服务器端异步通知接收模块获取到支付宝返回的结果数据后,可以结合商户自身业务逻辑进行数据处理(如:订单更新、自动充值到会员账号中等)。同步通知结果仅用于结果展示,入库数据需以异步通知为准。
总结下来就是在我们客户端这边基本是什么都没做的, 签名和验签这些工作都是我们的服务端来做的, 这样更安全.
# 请说一下混淆的原理, 为什么四大组件不能被混淆, 为什么自定义View不能被混淆?
- android平台的混淆原理是用”不能直接猜出含义 的通用变量名和函数名a b c等”替换编译后程序包中”具有明显语义信息的变量名和函数名”. 这样,通过逆向工程得到的只是难以理解的代码.
如何开启混淆配置?
minifyEnabled true // true开启混淆配置, false关闭 - 因为四大组件都在AndroidMainfest中进行了注册, 混淆后xml文件中相关的类找不到它们了。类里面的一些方法,变量是可以混淆的。
- 同上, 在xml里面配置好的文件的类名不能被混淆
其他一些不能被混淆的:
- jni方法
- 反射用到的类
- gson, fastjson解析的对象类
- webview的js调用的接口方法
- Parcelable,Serializable
- R文件
- 泛型、枚举
参考资料:
android混淆出现的问题与思考?
Android混淆从入门到精通
Android 混淆代码的原理与实施
代码混淆的实现原理与方法
# 请说说组件化, 请说说组件化中遇到的坑
- 我回答了一个当主项目与 module 中有同样资源时,module 却会使用主项目的资源, 然后就被问这是为什么?然后我就回答不上来了
参考:
Android主项目和Module中R类的区别
R.java、R2.java是时候懂了
# 请说说你们的多渠道打包是怎么打的?有没有想办法用别的方式打包来解决打包效率低的问题?
参考:
有没有快速的安卓分渠道包的工具推荐一下?
android如何多渠道打包?
Android产品研发(五)–>多渠道打包
# TCP和UDP位于哪一层
传输层
# TCP和UDP的区别
区别:
连接性:
TCP协议面向有连接的协议,要先确保发送方和接收方之间先建立连接才能进行通信;
UDP协议是面向无连接的协议,即发送数据之前不需要建立连接可靠性:
TCP提供可靠的服务,也就是说,通过TCP连接传送的数据,可以保证无差错、不丢失、不重复、且按序到达;
UDP尽最大努力交付,即不保证可靠交付。传输内容:
TCP是面向字节流,TCP把数据看成一连串无结构的字节流;
UDP是面向报文的,UPD没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低。服务性质:
每一条TCP连接都是点到点的
UPD支持一对一、一对多、多对一和多对多的交互通讯开销
TCP首部开销20字节;
UDP首部开销小,只有8个字节。信道:
TCP的逻辑通信信道是全双工的可靠信道,
UDP是不可靠信道。
# 请说说retrofit请求的流程
其实retrofit的源码我是真的看过, 但是有的关键地方印象不深, 导致没有说出来, 现在来重新梳理一下.
1.创建retrofit对象, 这里其实没什么好说的, 利用builder模式设置了一堆自定义的配置, 包括baseUrl, okhttpClient, CallAdapterFactory, ConverterFactory等
1 | Retrofit.Builder builder = new Retrofit.Builder().baseUrl(baseUrl) |
2.调用retrofit.create()方法, 这里是重点
1 | public <T> T create(final Class<T> service) { |
创建一个serviceMethod, 创建serviceMethod的时候会解析这个方法的各种包括方法上的注解, 方法的参数类型, 方法参数的注解, 同时也会创建callAdapter和responseConverter等.接着传入serviceMethod创建一个okhttpCall. 传入这个okhttpCall作为一个委托类对象, 创建一个ExecutorCallbackCall对象并且最终返回.
这里使用了代理模式, 调用这个ExecutorCallbackCall对象的enqueue方法其实就是调用okhttpCall的enqueue方法.
# mvp模式有什么优缺点?
数据、View、Presenter,View将操作给Presenter,Presenter去获取数据,数据获取好了返回给Presenter,Presenter去刷新View.
与mvc模式最大的不同点就是model与view不再有直接交互.
优点:
- 分离了视图逻辑和业务逻辑,降低了耦合。
- Activity只处理生命周期的任务,代码变得更加简洁。
- 模块职责划分明显,层次清晰。
- Presenter可以复用,一个Presenter可以用于多个View,而不需要更改Presenter的逻辑。
- Presenter被抽象成接口,可以有多种具体的实现,所以方便进行单元测试。
缺点:
- Presenter中除了应用逻辑以外,还有大量的View->Model,Model->View的手动同步逻辑,造成Presenter比较笨重,维护起来会比较困难。
- 由于对视图的渲染放在了Presenter中,所以视图和Presenter的交互会过于频繁。
- 接口爆炸
参考:
Android大厂面试题锦集附答案(BAT TMD JD 小米)
Android MVP 详解(上)
从google todo-mvp示例再次学习MVP
# 请说说jsbridge的用法, jsbridge的原理有了解过吗?
- js调用java方法
js代码
1 |
|
java代码
1 | mBridgeWebView.loadUrl("file:///android_asset/web.html"); |
- java调用js方法
java代码
1 | mBridgeWebView.callHandler("functionInJs", "这是java发给js的数据", new CallBackFunction() { |
js代码
1 | connectWebViewJavascriptBridge(function(bridge) { |
# 请说说listview和recyclerview的区别
- 缓存机制对比
listview缓存层级是二级缓存, 而recyclerView是四级缓存;listview缓存的是view, recyclerview缓存的是viewholder, 两者获取缓存的流程也不同 - 局部刷新
recyclerview提供了局部刷新的方法,notifyItemChanged
, 避免调用许多无用的bindView
方法 - 动画效果
setItemAnimator( )
,ItemTouchHelper
用于实现 RecyclerView 条目的滑动、删除和拖拽效果 - 嵌套滚动机制
recyclerview支持嵌套滚动机制而listview不支持
recyclerView的优缺点:
优点:
RecyclerView本身它是不关心视图相关的问题的,由于ListView的紧耦合的问题,google的改进就是RecyclerView本身不参与任何视图相关的问题。它不关心如何将子View放在合适的位置,也不关心如何分割这些子View,更不关心每个子View各自的外观。更进一步来说就是RecyclerView它只负责回收和重用的工作,这也是它名字的由来。
所有关于布局、绘制和其他相关的问题,也就是跟数据展示相关的所有问题,都被委派给了一些”插件化”的类来处理。这使得RecyclerView的API变得非常灵活。你需要一个新的布局么?接入另一个LayoutManager就可以了!你想要不同的动画么?接入一个新的ItemAnimator就可以了,诸如此类等等。
缺点:
在RecyclerView中,没有一个onItemClickListener方法。所以目前在适配器中处理这样的事件比较好。
整体总结它的几点如下:
Adapter:包装数据集合并且为每个条目创建视图。
ViewHolder:保存用于显示每个数据条目的子View。
LayoutManager:将每个条目的视图放置于适当的位置。
ItemDecoration:在每个条目的视图的周围或上面绘制一些装饰视图。
ItemAnimator:在条目被添加、移除或者重排序时添加动画效果。
结论:
列表页展示界面,需要支持动画,或者频繁更新,局部刷新,建议使用RecyclerView,更加强大完善,易扩展;其它情况(如微信卡包列表页)两者都OK,但ListView在使用上会更加方便,快捷。
# 请先自我介绍一下
你好, 我叫周方圆, 2015年毕业, 工作的第三年了. 我现在所在的公司叫…, 做的app叫…, 是一款…类型的app. 我在其中负责的主要功能和模块是…
其实就是将简历上的东西背一遍.
# android view绘制机制和加载过程,请详细说下整个流程
- ViewRootImpl会调用performTraversals(),其内部会调用performMeasure()、performLayout、performDraw()。
- performMeasure()会调用最外层的ViewGroup的measure()→onMeasure(),ViewGroup的onMeasure()是抽象方法,但其提供了measureChildren(),这之中会遍历子View然后循环调用measureChild()这之中会用getChildMeasureSpec()+父View的MeasureSpec+子View的LayoutParam一起获取本View的MeasureSpec,然后调用子View的measure()到View的onMeasure()→setMeasureDimension(getDefaultSize(),getDefaultSize()),getDefaultSize()默认返回measureSpec的测量数值,所以继承View进行自定义的wrap_content需要重写。
- performLayout()会调用最外层的ViewGroup的layout(l,t,r,b),本View在其中使用setFrame()设置本View的四个顶点位置。在onLayout(抽象方法)中确定子View的位置,如LinearLayout会遍历子View,循环调用setChildFrame()→子View.layout()。
- performDraw()会调用最外层ViewGroup的draw():其中会先后调用background.draw()(绘制背景)、onDraw()(绘制自己)、dispatchDraw()(绘制子View)、onDrawScrollBars()(绘制装饰)。
- MeasureSpec由2位SpecMode(UNSPECIFIED、EXACTLY(对应精确值和match_parent)、AT_MOST(对应warp_content))和30位SpecSize组成一个int,DecorView的MeasureSpec由窗口大小和其LayoutParams决定,其他View由父View的MeasureSpec和本View的LayoutParams决定。ViewGroup中有getChildMeasureSpec()来获取子View的MeasureSpec。
- 三种方式获取measure()后的宽高:
- Activity#onWindowFocusChange()中调用获取
- view.post(Runnable)将获取的代码投递到消息队列的尾部。
- ViewTreeObservable.
# 请简述handler消息机制的原理
- 在主线程中系统默认创建了 Looper, 也就是在ActivityThread的main方法中调用了
Looper.prepareMainLooper();
在这个方法中, 给当前线程设置了一个new出来的Looper对象sThreadLocal.set(new Looper(quitAllowed));
- Looper在被new出来的时候会创建一个MessageQueue对象
- 接着调用
Looper.loop();
开启消息循环. 首先会取出在当前线程中存储的Looper对象, 从中拿出MessageQueue对象, 然后开启一个死循环, 在这个死循环中从MessageQueue中获取消息, 如果取到了消息就调用msg.target.dispatchMessage(msg);
方法, 如果没有就”阻塞”在Message msg = queue.next();
这句 - handler在构造的时候会取出储存在当前线程中的Looper对象, 如果没有取到就报错, 然后获取Looper对象中的MessageQueue对象
- handler.sendMessage最终会调用一个
handler.enqueueMessage
的方法, 在这个方法中会给message.target赋值当前handler, 然后往MessageQueue中插入消息 - 而Looper循环中如果取到了消息就会调用
msg.target.dispatchMessage(msg);
方法, 也就是用发送这个Message的handler去处理这个消息
# 6.0动态权限的申请流程
# 线程池的几个参数的含义, 四种线程池的区别?
1 | public ThreadPoolExecutor(int corePoolSize, |
- corePoolSize
核心线程数, 在创建完线程池之后,核心线程先不创建,在接到任务之后创建核心线程。并且会一直存在于线程池中(即使这个线程啥都不干),有任务要执行时,如果核心线程没有被占用,会优先用核心线程执行任务。数量一般情况下设置为CPU核数的二倍即可。 - maximumPoolSize
最大线程数, 最大线程数 = 核心线程数 + 非核心线程数
非核心线程:简单理解,即核心线程都被占用,但还有任务要做,就创建非核心线程 - keepAliveTime
非核心线程闲置超时时长, 这个参数可以理解为,任务少,但池中线程多,非核心线程不能白养着,超过这个时间不工作的就会被干掉,但是核心线程会保留。 - unit
keepAliveTime的单位 - workQueue
线程池中的任务队列, 默认情况下,任务进来之后先分配给核心线程执行,核心线程如果都被占用,并不会立刻开启非核心线程执行任务,而是将任务插入任务队列等待执行,核心线程会从任务队列取任务来执行,任务队列可以设置最大值,一旦插入的任务足够多,达到最大值,才会创建非核心线程执行任务。
常见的workQueue有四种:
1.SynchronousQueue:这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现<线程数达到了maximumPoolSize而不能新建线程>的错误,使用这个类型队列的时候,maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大
2.LinkedBlockingQueue:这个队列接收到任务的时候,如果当前已经创建的核心线程数小于线程池的核心线程数上限,则新建线程(核心线程)处理任务;如果当前已经创建的核心线程数等于核心线程数上限,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize
3.ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误,或是执行实现定义好的饱和策略
4.DelayQueue:队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务 - threadFactory
创建线程的工厂, 可以用线程工厂给每个创建出来的线程设置名字。一般情况下无须设置该参数。 - handler
饱和策略, 这是当任务队列和线程池都满了时所采取的应对策略,默认是AbordPolicy, 表示无法处理新任务,并抛出 RejectedExecutionException 异常。此外还有3种策略,它们分别如下。
(1)CallerRunsPolicy:用调用者所在的线程来处理任务。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
(2)DiscardPolicy:不能执行的任务,并将该任务删除。
(3)DiscardOldestPolicy:丢弃队列最近的任务,并执行当前的任务。
结合图来记忆一下原理
# Bitmap加载大图如何优化防止oom?
- 加载 Bitmap 的方式
- decodeFile( 文件系统 )
- decodeResourece( 资源 )
- decodeStream( 输入流 )
- decodeByteArray( 字节数 )
- 高效加载 Bitmap 的流程
- 将 BitmapFactory.Options 的 inJustDecodeBounds 参数设为 true, 然后加载图片就可以实现只解析图片的宽高信息,并不会真正的加载图片.
- 从 BitmapFactory.Options 中取出图片原始的宽高信息,对应于 outWidth 和 outHeight 参数.
- 根据采样率规则并结合目标 view 的大小计算出采样率 inSampleSize(一般取值为 2 的指数)
- 将 BitmapFactory.Options 的 inJustDecodeBounds 设置为 false 重新加载图片
# 请说说http和https的区别
https = https + SSL/TLS(Secure Sockets Layer/Transport Layer Security)
http默认使用80端口,https默认使用443端口
https的握手流程, 简单的来说:
- 客户端和服务端建立 SSL 握手,客户端通过 CA 证书来确认服务端的身份;
- 互相传递三个随机数,之后通过这随机数来生成一个密钥;(RSA非对称加密与解密)
- 互相确认密钥,然后握手结束;
- 数据通讯开始,都使用同一个对话密钥来加解密;(AES对称加密)
参考资料:
HTTPS加密原理
看完你就知道什么是 HTTPS 了
看完还不懂HTTPS我直播吃翔
HTTPS与HTTP区别 – TLS/SSL
详解https是如何确保安全的?
HTTP和HTTPS详解。
HTTPS原理及OKHTTP对HTTPS的支持
# 请说说UI卡顿和过度绘制是怎么检测和优化的
# 请说说java的垃圾回收机制(gc)
垃圾回收是一种自动的存储管理机制。当一些被占用的内存不再需要时,就应该予以释放,以让出空间,这种存储资源管理,称为垃圾回收(garbage collection)
整个Java堆可以切割成为三个部分:
- Young(年轻代):
- Eden(伊利园):存放新生对象。
- Survivor(幸存者):存放经过垃圾回收没有被清除的对象。
- Tenured(老年代):对象多次回收没有被清除,则移到该区块。
- Perm:存储类和方法对象。
Young Generation区域存放的是最近被创建对象,此区域最大的特点就是创建的快,被销毁的也很快。当对象在Young Generation区域停留的时间到达一定程度的时候,它就会被移动到Old Generation区域中,同理,最后他将会被移动到Permanent Generation区域中
Java 不同的世代使用不同的 GC 算法
Young Generation采用了Copying算法进行回收,Copying算法就是扫描出存活的对象,并复制到一块新的空间中
Old Generation与Young Generation不同,对象存活的时间比较长,比较稳固,因此采用标记(Mark)算法来进行回收
怎么判断对象是否需要回收(对象是否已死)?
一、引用计数法
主流的JVM里面没有选用引用计数算法来管理内存,其中最主要的原因是它很难解决对象间的互循环引用的问题
二、可达性分析算法
通过一些列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时(就是从GC Roots 到这个对象是不可达),则证明此对象是不可用的。
什么情况下GC会执行
因为它对系统影响很明显,所以它到底在什么时候执行呢?
总的来说,有两个条件会触发主GC:
- 当应用程序空闲时,即没有应用线程在运行时,GC会被调用。因为GC在优先级最低的线程中进行,所以当应用忙时,GC线程就不会被调用,但以下条件除外。
- Java堆内存不足时,GC会被调用。当应用线程在运行,并在运行过程中创建新对象,若这时内存空间不足,JVM就会强制地调用GC线程,以便回收内存用于新的分配。若GC一次之后仍不能满足内存分配的要求,JVM会再进行两次GC作进一步的尝试,若仍无法满足要求,则 JVM将报“out of memory”的错误,Java应用将停止。
由于是否进行主GC由JVM根据系统环境决定,而系统环境在不断的变化当中,所以主GC的运行具有不确定性,无法预计它何时必然出现,但可以确定的是对一个长期运行的应用来说,其主GC是反复进行的。
GC会造成什么影响
在开始学习GC之前你应该知道一个词:stop-the-world。不管选择哪种GC算法,stop-the-world都是不可避免的。
也就是说,当垃圾回收开始清理资源时,其余的所有线程都会被停止。所以,我们要做的就是尽可能的让它执行的时间变短。如果清理的时间过长,在我们的应用程序中就能感觉到明显的卡顿。
参考:
【Android 性能优化】—— 详解内存优化的来龙去脉
Java gc(垃圾回收机制)小结,以及Android优化建议
JVM 的 工作原理,层次结构 以及 GC工作原理
JVM GC垃圾回收机制
JVM怎么判断对象是否已死?
JAVA gc垃圾回收机制
Java垃圾回收(GC)机制详解
# runtimeException可以被捕获吗?
不可以, 比如
1 | try { |
其中sUser == null
, 还是会抛出空指针异常