Android开发Android开发Android开发经验谈

Android面试知识点总结(九)

2023-01-04  本文已影响0人  奔跑吧李博
结论:

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

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(像素)
    */
    public static int dip2px(Context context,float dpValue){
        final float scale = context.getResource().getDisplayMetrics().density;
        return (int)(dpValue * scale + 0.5f);
    }

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

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

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

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个核心概念:

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

StackOverflowError出现的原因及解决

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

上一篇下一篇

猜你喜欢

热点阅读