Android面试专题我爱编程

准备Android面试题目

2018-05-24  本文已影响33人  stone305585

面试完了,等offer。

推荐几个需要特别熟悉源码原理的框架,因为二面一般会问:Volley(比较老了)、Okhttp、Retrofit、DiskLruCache、Glide、热修复的框架、路由框架比如ARouter。这些一定要有两个特别特别熟悉。


分割线


持续更新答案,上一篇的36问等过两天工作定下来再写,下面的题目大家有兴趣去研究一下

先推荐一本android书,看完了Android基础技术面试都没问题
《Android开发艺术探索》任玉刚 著

1:简历上的每个点都仔细看一下实现原理和数据结构,为什么要用它实现,具体实现了什么,可以用别的吗?

Android里面的SparseArray,两个数组存放key和object

点击launcher上的app之后activity启动流程

activity启动流程图.jpg activity启动流程2.jpg activity启动流程3.jpg

3:Android的插件化、热修补、Android的ClassLoader的相关原理,反射的原理,动态代理的两种方式是底层库的区别,都是运行时操作字节码。

建议重新看一下View中的measure layout invalidate draw源码,各个标志为,setFrame是连接layout和draw的关键点,也是判断是否change的关键,如果change则measure layout draw都会执行,否则只是force_layout的话只会measure和layout

这一篇讲的这个方法的衔接讲的不错

invalidate方法,当子View调用了invalidate方法后,会为该View添加一个标记位,这个标记位是只是draw的标记位,因此measure、layout流程不会执行。同时不断向父容器请求刷新,父容器通过计算得出自身需要重绘的区域,直到传递到ViewRootImpl中,最终触发performTraversals方法,进行开始View树重绘流程(只绘制需要重绘的视图)。
requestLayout方法,会标记当前View及父容器,同时逐层向上提交,直到ViewRootImpl处理该事件,从measure开始,对于每一个含有标记位的view及其子View都会进行测量、布局,会不会执行draw方法,要看重绘过程中具体的判断。

一个执行draw,一个只执行measure和layout,draw不确定

view的方法:

public void requestLayout() {
        if (mMeasureCache != null) mMeasureCache.clear();

        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
            // Only trigger request-during-layout logic if this is the view requesting it,
            // not the views in its parent hierarchy
            ViewRootImpl viewRoot = getViewRootImpl();
            if (viewRoot != null && viewRoot.isInLayout()) {
                if (!viewRoot.requestLayoutDuringLayout(this)) {
                    return;
                }
            }
            mAttachInfo.mViewRequestingLayout = this;
        }

        mPrivateFlags |= PFLAG_FORCE_LAYOUT;//强制layout的方法,设置父view的重新layout的标记。
        mPrivateFlags |= PFLAG_INVALIDATED; //强制重绘的方法

        if (mParent != null && !mParent.isLayoutRequested()) {
            mParent.requestLayout();
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }
    }

5:进程如何守护:广播,双进程,全家桶,如何跨进程通信,Binder跨进程的通信原理

6:AIDL的实现原理,创建方式,文件结构

7:动态代理

OKHTTP连接池!!

SPDY相关笔记:SPDY还是在应用层的优化
1:直到2008年,大部分浏览器才最终从每个域名2个连接发展到每个域名6个连接。
2:额外的初始客户请求;在HTTP协议中,只有客户端能初始化一个请求。尽管服务器知道客户端需要资源,但是它没有机制来通知客户端,而且必须等待客户端发来资源请求。(类似后面http2的请求html还给了js和css的例子),支持两种服务器推送和服务器暗示。
3:冗余和未压缩的头部。有一些头部在同一个信道的请求中多次发送,并且比较大未压缩
4:为了考虑未来的网络安全性,底层采用SSL作为传输层协议
5:为了克服网络带宽造成阻塞,客户端可以设置请求的优先级,在TCP的并行请求中,优先级高的先执行。

volley也支持Https,需要自己构造SSLSocketFactory;

Http各个版本:

二进制分帧:在传输成和应用层之间加了帧层,把http的header和body都封装成frame流,用于双向数据流通信,过去的http主要优化TCP的慢启动特性,降低延迟,而不是提高带宽,所以复用连接的这种方式,正好利用了tcp启动中间阶段的较快窗口期。同时慢启动时间的减少,使拥塞和丢包恢复速度更快

头部压缩:HTTP/1.1并不支持 HTTP 首部压缩,为此 SPDY 和 HTTP/2 应运而生, SPDY 使用的是通用的DEFLATE(这个自己查查吧)算法,HTTP2使用了专门为压缩头部设计的HPACK算法;(额,为了看下DEFLATE算法,我复习了半小时的哈夫曼编码,为了面试,面试举例基本套路:网络请求->okhttp3->http2,spdy->区别->压缩算法->分别什么算法->哈夫曼演变而来,哈夫曼编码了解吗->哈夫曼树构造->前缀编码。。。。)

服务器端Push:客户端请求一次,服务器可以返回多个相应,比如请求html,服务器可以把相关的js和css都返回,并且这些可以缓存并共用。

为了解决http的频繁建立连接,无法服务器端push而生。
http1.1中的keep-alive是指多个http连接中可以实现多个request请求,但是一个request对应一个response
websocket也是基于http的,使用http协议来完成部分握手,握手请求头包含:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com

服务器返回:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat

javax里可以实现websocket

@ServerEndpoint("/websocket") //这个注解可以标志为websocket服务
public class WebSocketTest 
//再使用注解实现一系列方法  onOpen   onClose  onMessage等
@OnOpen
 public void onOpen(Session session){
    this.session = session;
    webSocketSet.add(this);     //加入set中
    addOnlineCount();           //在线数加1
    System.out.println("有新连接加入!当前在线人数为" + getOnlineCount());
}

顺便提一下volatile,volatile只保证可见性,禁止并发重排序,1000个线程执行volatile类型的i++,结果不一定是1000.
synchronize主要保持两种特性,原子性(atomicity)和 可见性(visibility)。
1:synchronize代码块 使用的java代码在使用javap进行反编译class文件后能看出来


image.png

monitor是对于每个对象来说,每个对象都有一个自己的监视器锁,默认monitor计数为0,当执行monitorenter后,某个线程获取锁,对象的monitor加1,当该线程再次进入已获取锁的对象代码块,则再次加1,其他线程想使用该对象,只能等对象的monitor为0才能使用并再加1.只有获取monitor的线程才能操作monitor的相关方法,比如休眠,唤醒等等。
2:synchronize修饰方法,可以看到增加了ACC_SYNCHRONIZED标识符,JVM检测到该标识符,执行线程会获取monitor,本质是一样的,只是外表不一样。(reentrantLock底层也是操作对象的monitor,在lock1.lock后的线程A操作lock2的condition2.signal方法就会抛出ava.lang.IllegalMonitorStateException

image.png

ReentrantLock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。RLock可以设置公平非公平。对象的监视器锁也是非公平锁。

lockInterruptibly可以实现可中断锁,lock.lockInterruptibly 而 lock.lock不可被中断。

Reentrant底层的AQS实现有一个state代表锁获取计数器,如果拥有锁的某个线程再次得到锁(是可重入的概念),那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放,这里仿照了Synchronize的做法。lock必须在finally中释放,不然不会被释放。可实现 时间锁等候、 可中断锁等候、 无块结构锁、 多个条件变量 锁投票

condition可以用来通知具体的哪个线程开始执行,而synchronize中使用对象的notify方法只能告诉大家,这个对象可以被大家使用了,具体谁用这个决定不了,得看线程调度。condition则区分消费者的condition还是生产者的condition,分别调度。

View的measure方法,优化了只有当forceLayout和needsLayout时才会走onMeasure,具体计算看源码,执行onMeasure一定会执行onLayout

view 的measure的方法

···
···
···
if (forceLayout || needsLayout) {
            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // 执行onMeasure方法。
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }

            // flag not set, setMeasuredDimension() was not invoked, we raise
            // an exception to warn the developer
            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("View with id " + getId() + ": "
                        + getClass().getName() + "#onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }

            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;//如果需要force layout就会设置 layout的标记位
        }

LinearLayout的测量:
(1)如果我们在LinearLayout中不使用weight属性,将只进行一次measure的过程。

(2)如果使用了weight属性,LinearLayout在第一次测量时避开设置过weight属性的子View,之后再对它们做第二次measure。由此可见,weight属性对性能是有影响的。

RelativeLayout由于子view可以横向依赖也可以纵向依赖,所以会排序后绘制两次:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mDirtyHierarchy) {
            mDirtyHierarchy = false;
            sortChildren();
        }

       
···
···
···

        //横向排序的子view

        View[] views = mSortedHorizontalChildren;
        int count = views.length;

        for (int i = 0; i < count; i++) {
            View child = views[i];
            if (child.getVisibility() != GONE) {
                LayoutParams params = (LayoutParams) child.getLayoutParams();
                int[] rules = params.getRules(layoutDirection);

                applyHorizontalSizeRules(params, myWidth, rules);
                measureChildHorizontal(child, params, myWidth, myHeight);

                if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
                    offsetHorizontalAxis = true;
                }
            }
        }

        //纵向排序的子view
        views = mSortedVerticalChildren;
        count = views.length;
        final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;

        for (int i = 0; i < count; i++) {
            final View child = views[i];
            if (child.getVisibility() != GONE) {
                final LayoutParams params = (LayoutParams) child.getLayoutParams();

                applyVerticalSizeRules(params, myHeight, child.getBaseline());
                measureChild(child, params, myWidth, myHeight);
                if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {
                    offsetVerticalAxis = true;
                }

                if (isWrapContentWidth) {
                    if (isLayoutRtl()) {
                        if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
                            width = Math.max(width, myWidth - params.mLeft);
                        } else {
                            width = Math.max(width, myWidth - params.mLeft - params.leftMargin);
                        }
                    } else {
                        if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
                            width = Math.max(width, params.mRight);
                        } else {
                            width = Math.max(width, params.mRight + params.rightMargin);
                        }
                    }
                }

                if (isWrapContentHeight) {
                    if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
                        height = Math.max(height, params.mBottom);
                    } else {
                        height = Math.max(height, params.mBottom + params.bottomMargin);
                    }
                }

                if (child != ignore || verticalGravity) {
                    left = Math.min(left, params.mLeft - params.leftMargin);
                    top = Math.min(top, params.mTop - params.topMargin);
                }

                if (child != ignore || horizontalGravity) {
                    right = Math.max(right, params.mRight + params.rightMargin);
                    bottom = Math.max(bottom, params.mBottom + params.bottomMargin);
                }
            }
        }

···
···
···       

Jvm新建对象时的内存分配:new的时候,首先检查常量池中有类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析、初始化。然后在堆上分配内存:

分配内存时还需考虑多线程问题,采用两种方案:(1)采用CAS分配内存 (2)使用线程中的本地线程分配缓冲(TLAB),先分配到TLAB上,当需要新的TLAB时才会加锁。

可作为GC root的点:

13:动态规划基本问题。

回收机制差别
区别:

ListView和RecyclerView最大的区别在于数据源改变时的缓存的处理逻辑,ListView是"一锅端",将所有的mActiveViews都移入了二级缓存mScrapViews,而RecyclerView则是更加灵活地对每个View修改标志位,区分是否重新bindView。

15:JNI中的ENV指针是线程安全还是进程安全

推荐不错的讲解文章

Glide支持GIF,Picasso不支持
Glide会为同一图片缓存多种imageView尺寸,Picasso只会缓存一种原图
Glide ARGB565 Picasso ARGB8888

ScrollView测量不一样的地方,这就是为什么嵌套ListView或者RecyclerView会出问题,绘制不全

@Override
    protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int usedTotal = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin +
                heightUsed;
        final int childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
                Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - usedTotal),
                MeasureSpec.UNSPECIFIED);//这里出现了不常见的MeasureSpec.UNSPECIFIED,也是重写了ViewGroup里的MeasureChildWithMargins

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
上一篇下一篇

猜你喜欢

热点阅读