网络篇:
1
.TCP的三次握手和四次挥手:
答:
三次握手
:为了知道双方已经准备好发送数据了,二次握手会造成延迟到达的请求到达服务端使服务端进入等待状态,造成浪费,而三次握手可以识别是首次握手和第三次握手(第一次握手发送seq=x;第二次握手ack=x+1,seq=y,第三次握手发送seq=x+1,ack=y+1,这次握手其实可以携带数据)。
四次挥手
:为了知道双方已经结束发送数据,客户端通知服务端自己已经不能发送数据,需要知道服务端是否收到,这个需要两次握手,反之服务端也需要通过两次握手才能知道(为什么不能第二次的时候直接发送关闭请求呢,这样不是三次就够了吗? 因为这个时候不确定是否不能确定服务端不能发送信息,所以只是一个应答的过程),客户端等待2个传递时间的原因:客户端接收到服务端的传送结束响应,这个时候可能有前一个数据还没到客户端。
第一次握手
第二次握手
第三次握手
https解决的问题:正确获得服务端的共钥,采用证书机制,另外为了优化解密速度,采取对称加密
2
.TCP流量控制:
答:
解决方案:滑动窗口,滑动窗口的原理:
接收窗口动态改变
3
.TCP拥塞控制:
答:
指数增长阶段称之为慢启动
。
线性增长阶段称之为拥塞避免
。
快重传算法
首先要求接收方每收到一个失序的报文段后就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方)而不要等到自己发送数据时才进行捎带确认。
4
.Https:
答:添加证书机制,目的获取正确的共钥对一个随机数加密传给服务端,之后用这个随机数加密通信过程(加快解密速度)。
第三方库:
1
.EventBus:
用于线程间通信,订阅模式(观察者模式)的经典应用。
原理速讲:
//在activity里注册,这个就相当于把发布者(EventBus.getDefault())和 订阅者(activity)联系起来
EventBus.getDefault().register(this);
看看register怎么做的(版本不同 大体思路差不多 根据V2版本)
public void register(Object subscriber) {
// 暂时不考虑粘后两个参数性事件和优先级
this.register(subscriber, false, 0);
}
主体部分,代码都很简洁,往下看
private synchronized void register(Object subscriber, boolean sticky, int priority) {
// 获取这个Activity对应的SubscriberMethod的列表 ,SubscriberMethod是一个描述当前Activity对应的订阅方法的类,有成员变量method,methodString,eventType,threadMode,这几个变量就可以确认具体是哪个订阅方法
List<SubscriberMethod> subscriberMethods = this.subscriberMethodFinder.findSubscriberMethods(subscriber.getClass());
Iterator i$ = subscriberMethods.iterator();
while(i$.hasNext()) {
SubscriberMethod subscriberMethod = (SubscriberMethod)i$.next();
// subscribe 订阅方法是核心 单独拿出来分析一下也很简洁
this.subscribe(subscriber, subscriberMethod, sticky, priority);
}
}
接着看下subscribe
方法,去掉了粘性相关代码
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority) {
// 获取方法的eventType
Class<?> eventType = subscriberMethod.eventType;
// Subscription是一个描述activity和对应订阅方法的类 ,这里获取所有订阅了这个方法的Subscription对象
CopyOnWriteArrayList<Subscription> subscriptions = (CopyOnWriteArrayList)this.subscriptionsByEventType.get(eventType);
// 创建了一个本次Activity对应的Subscription对象
Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority);
// 检测是否已经有不同的的activity订阅过这方法,没有就新建,相同activity订阅就抛异常。
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList();
this.subscriptionsByEventType.put(eventType, subscriptions);
} else if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event " + eventType);
}
int size = subscriptions.size();
// 按照优先级插入对应的位置(包含该订阅方法的集合)
for(int i = 0; i <= size; ++i) {
if (i == size || newSubscription.priority > ((Subscription)subscriptions.get(i)).priority) {
subscriptions.add(i, newSubscription);
break;
}
}
// typesBySubscriber是一个activity包含所有订阅方法的集合
List<Class<?>> subscribedEvents = (List)this.typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList();
this.typesBySubscriber.put(subscriber, subscribedEvents);
}
((List)subscribedEvents).add(eventType);
}
这里可以看到有两个集合 一个是包含相同事件的对象集合 一个是某个activity包含所有订阅方法的集合。说到底, 注册过程就是往集合里添加数据的一个过程
,可以猜想post发送事件,是一个从集合里获取对象执行的一个过程
,取消注册,就是一个从集合remove事件的过程
。
EventBus可以从下面几点开讲:
1.观察者模式
2.线程切换
3.事件优先级
4.APT技术(之前的版本用的是反射获取信息)
2. Glide图片缓存
从下面几点分散开讲:
1.图片缓存 - 内存缓存,硬盘缓存
2.线程切换 - 请求图片的时候放在子线程,设置Imageview的时候转换到主线程
3.OOM问题 - 弱引用,LRuCache
4.内存泄漏
3.OkHttp
里面几个重要的组成部分:
1.RealCall:
2.Dispatcher:
3.OkHttpClient:
4.Interceptor:
拦截器设计,是OkHttp最重要的设计
Android高频问题
1
.消息机制
通常消息机制的几个大佬:Handler
,Looper
,MessageQueue
,Message
分析代码看的不是很清楚,借用下大佬画的图还有我自己的图
这边一般会展开讲为什么Loop()无限循环不会造成卡顿,首先一个点app不退出的条件就是依赖于这个Loop循环,退出循环意味着app就停止工作了,那为什么不会造成卡顿呢?线程在没有事件的时候是会进入阻塞状态。
2
.相关优化
内存优化:内存泄漏,大图优化
UI优化:过渡渲染,View Inspecter查看层级
网络优化: 减少请求次数,图片按需请求,数据缓存,弱网情况下不加载图片等。
ANR:耗时方法分析Systrace
3
.自定义View
对于自定义View需要知道的基础大概有这么几点:
1.了解一个View的MeasureSpec怎么来的,通常是这么一张图
2.了解measure layout draw三个过程的作用
measure
过程主要是测量控件的大小,单个View或者ViewGroup都需要自己去实现onMeasure方法实现自己的大小。
layout
过程,主要是用于ViewGroup布局子View,单个View不需要重写。
draw
过程:主要是用于单个View绘制内容。
4
.React Native的原理(Android角度)
从两个角度分析:UI绘制
UI绘制:
Android的页面就是一个Activity与主体视图c
protected void loadApp(String appKey) {
if (mReactRootView != null) {
throw new IllegalStateException("Cannot loadApp while app is already running.");
}
mReactRootView = createRootView();
mReactRootView.startReactApplication(
getReactNativeHost().getReactInstanceManager(),
appKey,
getLaunchOptions());
getPlainActivity().setContentView(mReactRootView);
}
看下mReactRootView是怎么实现的?
mReactRootView整个看下来就是一个继承SizeMonitoringFrameLayout的普通View,布局子View交给了 UIManagerModule
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// No-op since UIManagerModule handles actually laying out children.
}
UIManagerModule里面很多注册的交互事件比如更新View
@ReactMethod
public void updateView(int tag, String className, ReadableMap props) {
if (DEBUG) {
String message =
"(UIManager.updateView) tag: " + tag + ", class: " + className + ", props: " + props;
FLog.d(ReactConstants.TAG, message);
PrinterHolder.getPrinter().logMessage(ReactDebugOverlayTags.UI_MANAGER, message);
}
mUIImplementation.updateView(tag, className, props);
}
Js会把一些操作通过JsBridge
(C++实现)传递给UIManagerModule进行操作。
1.js调用java代码是从NativeModules.xxxModule.xxxMethod()
2.JSCExecutor::getNativeModule>()获取js所要调用的java层的方法的配置表
3.NativeModules.js根据得到的配置表调用BatchedBridge.enqueueNativeCall(),将module:moduleID, methodID, params等参数传递到c层
4.c层遍历获取对应moduleId的javamoudlewrapper,通过反射调用ReactContextBaseJavaModule中的代码
参考:https://blog.csdn.net/kakaxiqianxin/article/details/80666443
5
.触摸事件分发
主要角色:ViewGroup`` View
他们分别都有dispatchTouchEvent事件的实现,
ViewGroup
主要做了几件事:
1.判断是否拦截
2.没有拦截则在子View中查看响应子View
3.有的话执行子View的dispatchTouchEvent,mFirstTarget
不为空,
View
主要做的是响应事件的事:
OnTouch先于onTouchEvent执行,onTouchEvent中执行onClick方法
子View可点击,必会返回true。
面试考察点:不同的触摸情况,对应什么样的事件。eg:子View响应 DOWN
事件,但是父View拦截了MOVE
事件,这个时候,还是会有事件传递到子View,但是是CANCEL
事件。
6.UI卡顿相关:
卡顿原因
:
1.应用会在要绘制的时候会请求vsync信号,收到的时候会往消息队列插入一条消息,此时主线程前一条消息要是耗时久的话会造成doframe无法在16ms内被执行。
doframe 绘制超复杂UI,消耗超过16ms。
7. 启动加速:
1.可以影响的有Application的onCreate方法,一般会把不重要的操作放到子线程中初始化。
2.还有一个障眼法,就是给启动页面换一个背景,造成瞬间响应点击图标启动的操作
8.Activity和Window,DecorView的关系
想要记住很容易,可以从Activity的setContentView入手
public void setContentView(@LayoutRes int layoutResID) {
// 这个是PhoneWindow
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
PhoneWindow----getWindow().setContentView(layoutResID);
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
// 首次肯定先执行这 看下DecorView
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
installDecor就是创建DecorView的代码,前面可以看出来Activity包含PhoneWindow
if (mDecor == null) {
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
总结:
Activity包含了一个PhoneWindow,PhoneWindow就是继承于Window
Activity通过setContentView将View设置到了DecorView上
PhoneWindow里面包含了DecorView,最终布局被添加到Decorview上.
9. ViewRootImpl,WindowManager,WindowManagerService(WMS),SurfaceFlinger之间的关系
想要说明这三个的关系需要先明白这三个分别有什么作用:
ViewRootImpl:里面有两个个重要的类
1.W:负责和WindowManagerService交互,接受触摸事件(这里服务端通过Socket传递给客户端)等。
2.ViewRootHandler:接受W的异步消息。
除此之外也负责和服务端交互,负责更新View
WindowManager:
addView等API与服务端交流,感觉是个为了解耦而出现的类,具体的逻辑都是通过其他类完成与Service的交互。
WindowManagerService:
服务端负责Window的管理,不负责View绘制
他们之间的关系可以从一下这段代码开始分析
SurfaceFlinger:
服务端负责图层的管理
他们之间的关系可以从一下这段代码开始分析
(WindowManager)wm.addView(mDecor, getWindow().getAttributes());
看下addView
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
try {
root.setView(view, wparams, panelParentView);
}
public ViewRootImpl(Context context, Display display) {
mContext = context;
// 首先通过getWindowManagerService 获取WMS的代理,之后通过WMS的代理在服务端open一个Session,并在APP端获取该Session的代理
mWindowSession = WindowManagerGlobal.getWindowSession();
mWindow = new W(this);
}
很明显在addView的时候创建了ViewRootImpl,看下ViewRootImpl如何通过setView将视图添加到WMS的:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
// requestLayout是在addWindow之后执行的
requestLayout();
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
mInputChannel = new InputChannel();
}
try {
// IPC操作 通过Session add一个View
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
} catch (RemoteException e) {
}
}
看下如何addView的
代码在以下路径 代码很长精简一些 /frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
....
// win是WIndowState 这一步会向SuraceFlinger申请Suface
win.attach();
....
主要是对窗口分组,判断是否添加过窗口等操作。
之后WMS会向SurfaceFlinger申请Suface,这一步也是一个IPC的过程
总的流程:
首先APP端新建一个Surface图层的容器壳子,
APP通过Binder通信将这个Surface的壳子传递给WMS,
WMS为了填充Surface去向SurfaceFlinger申请真正的图层,
SurfaceFlinger收到WMS请求为APP端的Surface分配真正图层
将图层相关的关键信息Handle及Producer传递给WMS
之后更新UI就可以通过代理和SurfaceFlinger进行通信了
参考:https://www.jianshu.com/p/40776c123adb
10.一个App的启动流程:
从点击App的图标开始
1.点击App图标 Launcher进程发送startActivity IPC请求到AMS
2.AMS startProcessLocked
会去请求Zygote进程(socket方式)去创建进程
3.Zygote进程fork一个新的进程
4.被创建的app会执行ActivityThread的main方法
public static void main(String[] args) {
// 展示主体代码
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
Looper.loop();
}
Looper这个看着是不是很熟悉,这个就是UI线程Looper,所以在UI线程不需要Looper.loop()
thread.attach这个方法进行了IPC
RuntimeInit.setApplicationObject(mAppThread.asBinder());
final IActivityManager mgr = ActivityManager.getService();
try {
// 这里把ApplicationThread这个Binder传递给远程服务,这个时候相当于本身app变成远程服务端,Service变成发起者。常规Binder需要向ServiceManager获取远程Service的代理。
mgr.attachApplication(mAppThread, startSeq);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
5.Service会往App发送BIND_APPLICATION,创建Application
6.接着会往App发送LAUNCH_ACTIVITY,反射创建Activity
Java基础篇:
1
.线程篇:
线程并发基础知识
:
三个特性:
原子性
:由于操作不是一步完成:打个比方加法的CPU指令,现在寄存器中计算结果,这个可以被中断,原子性意味着加法操作是整个完成或者就没执行。
有序性
:代码的顺序和虚拟机执行顺序不一定一样
可见性
:Java的内存模型决定,变量都存储在主内存,线程中存储的是一份copy。
如何保证这三个特性?:
volatile
可以保证可见性(lock指令 cpu执行到时马上刷新数据到主内存,并马上通知其他线程数据地址失效),也可以防止指令重排序,不能保证原子性,通过内存屏障
防止指令排序。
Synchronized
:悲观锁,可以保证同一时间只有一个线程可以访问代码段,实现原理: monitorenter 和 monitorexit
执行同步代码块,首先会执行monitorenter指令,然后执行同步代码块中的代码,退出同步代码块的时候会执行monitorexit指令 。
CAS:乐观锁,compare and set,变量volatile保证可见性,unsafe操作保证原子性。