Android面试知识点总结(九)

作者: 奔跑吧李博 | 来源:发表于2023-01-04 21:28 被阅读0次
    • invalidate()、requestLayout() 区别
    结论:

    requestLayout方法会导致View的onMeasure、onLayout、onDraw方法被调用;invalidate方法则只会导致View的onDraw方法被调用。

    在View的requestLayout方法中,首先会设置View的标记位,PFLAG_FORCE_LAYOUT表示当前View要进行重新布局,PFLAG_INVALIDATED表示要进行重新绘制。invalidate方法没有标记PFLAG_FORCE_LAYOUT,所以不会执行测量和布局流程,而只是对需要重绘的View进行重绘,也就是只会调用onDraw方法。

    requestLayout方法中会一层层向上调用父布局的requestLayout方法,最终调用的是ViewRootImpl中的requestLayout方法。

    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
    
        void scheduleTraversals() {
            if (!mTraversalScheduled) {
                mTraversalScheduled = true;
                mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
                mChoreographer.postCallback(
                        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
                notifyRendererOfFramePending();
                pokeDrawLockIfNeeded();
            }
        }
    

    scheduleTraversals刷新view,会向messageQueue中发出同步屏障,优先去执行view刷新。scheduleTraversals方法最后会调用performTraversals方法开始执行View的三大流程,会分别调用View的measure、layout、draw方法。

    参考:
    https://www.cnblogs.com/normalandy/p/12408665.html

    • Bitmap 在内存中有多大?

    1.占用内存
    2.图片大小
    占用内存表示图片被加载进来以后占用的内存空间大小,图片大小则是图片在磁盘存储时占用的大小。

    获取一个biemap占用多大内存空间的方法如下:

    int sizeOf = bitmap.getRowBytes() * bitmap.getHeight();
           //getRowBytes 表示一行的字节数,getHeight可以认为总共有多少行。
    

    不同Android版本时的Bitmap占用内存模型:
    我们知道Android系统中,一个进程的内存可以简单分为Java内存和native内存两部分,而Bitmap对象占用的内存,有Bitmap对象内存和像素数据内存两部分,在不同的Android系统版本中,其所存放的位置也有变化。
    可以看到,最新的Android O之后,谷歌又把像素存放的位置,从java 堆改回到了 native堆。API 11的那次改动,是源于native的内存释放不及时,会导致OOM,因此才将像素数据保存到Java堆,从而保证Bitmap对象释放时,能够同时把像素数据内存也释放掉。

    参考:
    https://blog.csdn.net/u011494285/article/details/80523775
    https://www.jianshu.com/p/3f6f6e4f1c88

    • dp和px换算关系是怎样的
    /*
        根据手机的分辨率从 dp 单位转为 px(像素)
        */
        public static int dip2px(Context context,float dpValue){
            final float scale = context.getResource().getDisplayMetrics().density;
            return (int)(dpValue * scale + 0.5f);
        }
    
    • protobuffer是什么

    protocol buffers是一个灵活的、高效的、自动化的用于对结构化数据进行序列化的协议,与json、xml相比,protocol buffers序列化后的码流更小、速度更快、操作更简单。
    JSON因为有一定的格式,并且是以字符存在的,在数据量上还有可以压缩的空间。protobuf是二进制的、结构化的,所以比json的数据量更小,也更对象化。
    对于HTTP协议的交互,用的比较多的是json,而 tcp协议,用的比较多的是protobuffer。

    • 为什么view.post()能保证获取到view的宽高?

    通过View.post()添加的任务是在View绘制任务里 - 开始绘制阶段时添加到消息队列的尾部的。即View.post() 添加的任务能够保证在所有 View绘制流程结束之后才被执行,所以 执行View.post() 添加的任务时可以正确获取到 View 的宽高。

    • 若只是在代码中创建一个 View并调用它的post(),那么post的任务会不会被执行?

    不会。主要原因是:每个View中post() 需执行的任务,必须得添加到窗口视图-执行绘制流程,任务才会被post到消息队列里去等待执行,即依赖于dispatchAttachedToWindow ()。
    参考:https://www.jianshu.com/p/e2a8cd384eda

    • Kotlin中函数的可见性修饰符是怎样的
    • 谈谈你对 Activity.runOnUiThread 的理解?

    runOnUiThread过程源码

    // Activity.java
    public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }
    
    // Handler.java
    public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }
    

    首先判断当前是不是 UI线程,如果是就直接执行传入的Runnable接口的run方法。
    否则就通过Activity类中的Handler实例的post(Runnable)方法来发送消息,最终调用handler的enqueueMessage方法将任务加入到消息队列中,因为在Activity中的Handler对象运行在主线程(主线程中的Handler对象是在ActivityThread类中的main方法中创建的),故而这里就切换到了主线程中。

    Intent传输数据的大小有限制吗?如何解决?

    Intent传输数据的大小受Binder的限制,上限是1M。不过这个1M并不是安全的上限,Binder可能在处理别的工作,安全上限是多少这个在不同的机型上也不一样。

    传 512K 以下的数据的数据可以正常传递。
    传 512K~1024K 的数据有可能会出错,闪退。
    传 1M以上的数据会报错:TransactionTooLargeException
    考虑到 Intent 还包括要启动的 Activity 等信息,实际可以传的数据略小于 512K

    解决办法
    减少传输数据量
    Intent通过绑定一个Bundle来传输,这个可以超过1M,不过也不能过大
    通过内存共享,使用静态变量或者使用EventBus等类似的通信工具
    通过文件共享

    ACTION_CANCEL到底何时触发?滑出子View范围还能触发onClick事件吗?

    1.有四种情况会触发ACTION_CANCEL:

    在子View处理事件的过程中,父View对事件拦截
    ACTION_DOWN初始化操作(系统可能会由于App切换、ANR等原因丢失了up,cancel事件)
    在子View处理事件的过程中被从父View中移除时
    子View被设置了PFLAG_CANCEL_NEXT_UP_EVENT标记时(该view暂时detached)

    2.在view的onTouchEvent()中:

    case MotionEvent.ACTION_MOVE:
        // Be lenient about moving outside of buttons
        // 判断是否超出view的边界
        if (!pointInView(x, y, mTouchSlop)) {
            // Outside button
            if ((mPrivateFlags & PRESSED) != 0) {
                // 这里改变状态为 not PRESSED
                // Need to switch from pressed to not pressed
                mPrivateFlags &= ~PRESSED;
            }
        }
        break;
    
    case MotionEvent.ACTION_UP:
        boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
        // 可以看到当move出view范围后,这里走不进去了
        if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
            ...
            performClick();
            ...
        }
        mIgnoreNextUpEvent = false;
        break;
    

    结论:滑出view范围后,如果父view没有拦截事件,则会继续受到ACTION_MOVE和ACTION_UP等事件。
    一旦滑出view范围,view会被移除PRESSED标记,这个是不可逆的,然后在ACTION_UP中不会执行performClick()等逻辑,即不会触发onClick事件。

    参考:https://blog.51cto.com/u_15375308/4996238

    Android中的bitmap存在哪里

    在Android 3.0(API level 11) ~ Android 7.1(API level 25)中无论是Bitmap对象还是像素点数据(Pixel Data),都统一存储在Dalvik Heap。
    然而从Android 8.0(API level 26) 开始,截至到2018年3月的版本,素点数据(Pixel Data)被存储到Native Heap。
    native进程,是由c/c++分配的,/system/bin下面的所有程序运行在native进程中。
    dalvik进程,是由java程序的dalvik虚拟机分配的。

    - BIO、NIO分别是什么

    阻塞(Block) / 非租塞(NonBlock)
    阻塞:往往需要等待缓冲区中的数据准备好过后才处理其他的事情,否则一直等待在那里。
    非阻塞:当我们的进程访问我们的数据缓冲区的时候,如果数据没有准备好则直接返回,不会等待。如果数据已经准备好,也直接返回。
    阻塞和非阻塞关注的是程序在等待结果(消息,返回值)时的状态。

    同步(Synchronization) / 异步(Asynchronization)
    同步:是应用程序要直接参与IO读写的操作。
    异步:所有的IO读写交给操作系统去处理,应用程序只需要等待通知。

    同步阻塞I/O(BIO)

    同步阻塞I/O,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制来改善。

    同步非阻塞I/O(NIO)

    同步非阻塞I/O,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有IO请求时才启动一个线程进行处理。

    NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器。
    NIO的3个核心概念:

    • 缓冲区Buffer
      在NIO中,所有的数据都是用缓冲区处理。IO是面向流的,NIO是面向缓冲区的。
    • 通道Channel
      Channel是一个通道,可以通过它读取和写入数据,他就像自来水管一样,网络数据通过Channel读取和写入。
      通道和流不同之处在于通道是双向的,流只是在一个方向移动,而且通道可以用于读,写或者同时用于读写。
    • 多路复用器Selector
      Selector选择器可以监听多个Channel通道感兴趣的事情(read、write、accept(服务端接收)、connect,实现一个线程管理多个Channel,节省线程切换上下文的资源消耗。

    参考:https://www.jianshu.com/p/91fe446aeb86

    StackOverflowError出现的原因及解决

    一般出现这个问题是因为程序里有死循环或递归调用所产生的。

    相关文章

      网友评论

        本文标题:Android面试知识点总结(九)

        本文链接:https://www.haomeiwen.com/subject/bztevltx.html