Android面试八股汇总(1)

八股卷起来

Posted by River on March 7, 2022

前言

不知何时起,大约是21年初的时候首次听说客三消这个词;意思是客户端将在三年内消失; 移动互联网发展了十年,客户端技术几乎非常成熟;流量往头部的app汇集的趋势不可逆, 市面上招聘客户端最多的应该是字节了,客户端求职也激烈了起来;各种八股轮番轰炸, 本着好记性不如烂笔头的原则,记录android面试常见的八股知识点;所谓八股就是在 实际开发中用的极少,但是还是需要知道起技术原理以提升自己面试时的气度。

1、骨灰级的Handler

记得13年的时候,能够聊一些Handler机制;就算是厉害的开发了,如今再问Handler ,心底一万头草泥马奔跑,真的太卷啦。
无论如何,Handler是UiFrameWork的核心机制消息的核心实现;掌握其运行原理还是很有必要滴。

1.1、 核心关系,构造和准备工作

Handler: 消息处理和接受者,Looper: 消息调度者,MessageQueue: 消息存储的队列

Handler的构造函数

Handler的6个构造函数,核心构造函数就下面这个:

     public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

干了两件事:
1、Handler是匿名内部类,非静态内部类,方法中定义的局部类都可能导致内存泄露,打印一点日志。
2、对核心字段mLooper,mQueue赋值;

经验: 从mLooper = Looper.myLooper()可以推测从子线程空参构造创建Hanndler会抛出异常;建议创建Handler的时候显式的指定Looper。

Looper的构造函数

Looper的沟通函数是private修饰的,实际在prepare方法中调用。

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
}

public static void prepare() {
        prepare(true);
}

private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
}


MessageQueue的构造函数


MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
}

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    if (!nativeMessageQueue) {
        jniThrowRuntimeException(env, "Unable to allocate native queue");
        return 0;
    }

    nativeMessageQueue->incStrong(env);
    return reinterpret_cast<jlong>(nativeMessageQueue);
}

构造方法中干了两件事
1、quitAllowed设置了是否允许Message退出。

一般在自定义子线程开启Looper设置true;
比如Looper.prepare()方法默认传参数quitAllowed传的是true;
在Ui线程中的消息是不可退出的,ActivityThread的main方法中会调用;
Looper.prepareMainLooper,quitAllowed传的就是false,表示不可退出。

2、调用nativeInit方法初始化native层的nativeMessageQueue,之后再将生成的nativeMessageQueue,
通过reinterpret_cast方法将指针转化为long值返回给Framework层保存。

1.2、 消息入队

日常使用的new Handler().post()还是new Handler().postDelayed(), 都会调用到sendMessageDelayed(Message msg, long delayMillis),Runnable赋值给Message的callback字段;
继而调用enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis),核心实现都在enqueueMessage。

// Handler.sendMessageDelayed
 public final boolean sendMessageDelayed(Message msg, long delayMillis){
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

// Handler.sendMessageAtTime
public boolean sendMessageAtTime(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);
}

// Handler.enqueueMessage
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
}

// MessageQueue.enqueueMessage
 boolean enqueueMessage(Message msg, long when) {
        // 一些异常校验的逻辑
        。。。

        synchronized (this) {
            //健壮性代码,Message正在退出
            if (mQuitting) {
            
                。。。
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            
            if (p == null || when == 0 || when < p.when) {
                // 当前消息队列空闲的
                。。。
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

入队的核心实现干了两件事:(可以看出MessageQueue并不是一个严格意义上的队列结构;而是封装了消息入队存储相关逻辑的而已)

1、使用Message.next保存下一个Message,从而按照时间将所有的Message排序,从而达到入队的效果。

2、如果需要唤醒,则调用nativeWake方法来唤醒之前等待的线程。

1.3、消息分发

Looper.loop;

    
    public static void loop() {
        //...省略一波代码
        final MessageQueue queue = me.mQueue;

        //...省略一波代码
        for (;;) {
            Message msg = queue.next(); //取出Message
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            //...省略一波代码

            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            //省略一波代码target

            msg.recycleUnchecked();
        }
    }

整个方法核心就干了两件事:
1、取出消息,Message msg = queue.next();
next实现优先取异步消息(View的绘制就用异步消息),上层应用只能使用同步消息。
2、用msg.target也就是Handler调dispatchMessage方法把消息分发下去。

1.3、消息处理Handler.dispatchMessage

 public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

 public void handleMessage(@NonNull Message msg) {
 
 }
 
  private static void handleCallback(Message message) {
        message.callback.run();
  }

可以看出dispatchMessage核心就干了两件事

1、优先处理msg.callback,其实就是个Runnable;直接调run方法。

2、其实处理外面override或者直接传入的消息实现,override handleMessage和传入CallBack等价的。

1.4、Native部分的补充

经常翻阅Android源码的同学会发现,源码存在大量的native方法;深入系统原理很难脱离NDK编程;

native层的逻辑跟framework一致类似。弱化了NativeMessageQueue;基本实现都在NativeLooper中;
native层主要负责的是消息调度,何时阻塞线程、唤醒线程,避免不停的空循环导致的性能消耗过大。

native层消息机制流程图

native层消息接收

在 framework层的MessageQueue.next方法中,会调用一个native方法;
nativePollOnce(ptr, nextPollTimeoutMillis)来阻塞线程。

static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jlong ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
    mPollEnv = env;
    mPollObj = pollObj;
    mLooper->pollOnce(timeoutMillis);
    mPollObj = NULL;
    mPollEnv = NULL;

    if (mExceptionObj) {
        env->Throw(mExceptionObj);
        env->DeleteLocalRef(mExceptionObj);
        mExceptionObj = NULL;
    }
}

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    for (;;) {
        while (mResponseIndex < mResponses.size()) {
            //...省略一波代码
        }

        //...省略一波代码

        result = pollInner(timeoutMillis);
    }
}

pollInner(timeoutMillis)方法,这里就是最终的调用。

int Looper::pollInner(int timeoutMillis) {
        //...
    int result = POLL_WAKE;
   
                //...
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

    // No longer idling.
    mPolling = false;

    // Acquire lock.
    mLock.lock();

    //...省略一大波代码
    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        if (fd == mWakeEventFd) {
            if (epollEvents & EPOLLIN) {
                awoken();//通过awoken唤醒线程
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);
            }
        } else {
            //...
        }
    }


    // Release lock.
    mLock.unlock();
        //...
    return result;
}

这里的核心逻辑非常简单,干了两件事:

1、epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis)方法获取到事件到个数;如果个数为0,就继续阻塞;

2、如果不为0;则会遍历每一个事件,如果有mWakeEventFd并且是EPOLLIN事件,就会通过awoken方法真正地唤醒线程。

总结

  1. Handler消息机制是framework的核心机制之一,面试经常遇到;
  2. 好记性不如烂笔头,写博客记录下胜过理论温习一次。