快速了解Android回答

网络篇:

1.TCP的三次握手和四次挥手:

答:
三次握手:为了知道双方已经准备好发送数据了,二次握手会造成延迟到达的请求到达服务端使服务端进入等待状态,造成浪费,而三次握手可以识别是首次握手和第三次握手(第一次握手发送seq=x;第二次握手ack=x+1,seq=y,第三次握手发送seq=x+1,ack=y+1,这次握手其实可以携带数据)。
四次挥手:为了知道双方已经结束发送数据,客户端通知服务端自己已经不能发送数据,需要知道服务端是否收到,这个需要两次握手,反之服务端也需要通过两次握手才能知道(为什么不能第二次的时候直接发送关闭请求呢,这样不是三次就够了吗? 因为这个时候不确定是否不能确定服务端不能发送信息,所以只是一个应答的过程),客户端等待2个传递时间的原因:客户端接收到服务端的传送结束响应,这个时候可能有前一个数据还没到客户端。

第一次握手


image.png

第二次握手


image.png

第三次握手
image.png

https解决的问题:正确获得服务端的共钥,采用证书机制,另外为了优化解密速度,采取对称加密

2.TCP流量控制:

答:
解决方案:滑动窗口,滑动窗口的原理:
接收窗口动态改变


image.png

3.TCP拥塞控制:

答:
指数增长阶段称之为慢启动
线性增长阶段称之为拥塞避免
快重传算法首先要求接收方每收到一个失序的报文段后就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方)而不要等到自己发送数据时才进行捎带确认。

4.Https:

答:添加证书机制,目的获取正确的共钥对一个随机数加密传给服务端,之后用这个随机数加密通信过程(加快解密速度)。

第三方库:

1.EventBus:

用于线程间通信,订阅模式(观察者模式)的经典应用。
原理速讲:

//在activity里注册,这个就相当于把发布者(EventBus.getDefault())和 订阅者(activity)联系起来
EventBus.getDefault().register(this);

看看register怎么做的(版本不同 大体思路差不多 根据V2版本)


EventBus.png
 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.消息机制

通常消息机制的几个大佬:HandlerLooper,MessageQueueMessage
分析代码看的不是很清楚,借用下大佬画的图还有我自己的图

消息机制

消息机制

这边一般会展开讲为什么Loop()无限循环不会造成卡顿,首先一个点app不退出的条件就是依赖于这个Loop循环,退出循环意味着app就停止工作了,那为什么不会造成卡顿呢?线程在没有事件的时候是会进入阻塞状态。

2.相关优化

内存优化:内存泄漏,大图优化
UI优化:过渡渲染,View Inspecter查看层级
网络优化: 减少请求次数,图片按需请求,数据缓存,弱网情况下不加载图片等。
ANR:耗时方法分析Systrace

3.自定义View

对于自定义View需要知道的基础大概有这么几点:
1.了解一个View的MeasureSpec怎么来的,通常是这么一张图


父View对子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进行通信了


image.png

参考: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操作保证原子性。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342