Android体系课--Handler—按方法进行源码解析

简介: Handler源码解析

🔥 Hi,我是小余。

本文已收录到 GitHub · Androider-Planet 中。这里有 Android 进阶成长知识体系,关注公众号 [小余的自习室] ,在成功的路上不迷路!
Handler系列:

[Android体系课--Handler—按方法进行源码解析]

[Android体系课--Handler-Handler面试题]

Handler源码解析

1.构造函数
public Handler(Looper looper, Callback callback, boolean async) {
   
    mLooper = looper;1.传入的当前线程的looper对象
    mQueue = looper.mQueue;2.传入的当前线程的looper对象的MessageQueue
    mCallback = callback;3.传入的Handler.CallBack对象,在处理的时候会判断该对象是否存在还有返回值是否为true
    mAsynchronous = async;
}

总结:
1.哪个线程执行消息处理请求,是根据传入的looper来确认。

2.获取Message
Handler.obtainMessage
    Message.obtain(this, what){
   
            Message m = obtain();分析1:
            m.target = h;
            m.what = what;
            return m;
    }
    分析1public static Message obtain() {
   
    synchronized (sPoolSync) {
   
            if (sPool != null) {
   sPool指向消息池的头节点,如果不为空进入
                Message m = sPool; 使用一个临时变量m=sPool
                sPool = m.next; 让sPool指向m的next
                m.next = null; 打断m到sPool的指针,这样sPool还是指向链表的头结点,只是这个节点是之前sPool的next节点
                m.flags = 0; // clear in-use flag
                sPoolSize--消息池链表大小减1
                return m; 返回之前从消息池中取出的头结点,
            }
        }
        return new Message();如果消息池没有消息,则创建消息
    }

总结:Handler.obtainMessage方法可以从Message的消息池中获取消息,取出的是消息池的头结点消息,如果没有消息则创建消息,这个方法可以避免不必要的消息创建,重用消息池的消息减少内存开销

3.发送sendMessage
sendMessageDelayed(msg, 0);
        sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);延迟时间加上系统时间组成when
                MessageQueue queue = mQueue;这个mQueue是在Handler构造函数中赋值
                enqueueMessage(queue, msg, uptimeMillis);
                        msg.target = this;将当前Handler赋值给msg.target
                        if (mAsynchronous) {
   如果是异步消息,则设置msg的异步标志
                                msg.setAsynchronous(true);
                        }
                        queue.enqueueMessage(msg, uptimeMillis){
   调用MessageQueue的enqueueMessage方法
                                if (msg.target == null) {
   判断handler是否为空
                                        throw new IllegalArgumentException("Message must have a target.");
                                }
                                if (msg.isInUse()) {
   判断msg是否被使用
                                        throw new IllegalStateException(msg + " This message is already in use.");
                                }
                                synchronized (this) {
   
                                        if (mQuitting) {
   判断是否调用了退出
                                                IllegalStateException e = new IllegalStateException(
                                                                msg.target + " sending message to a Handler on a dead thread");
                                                Log.w(TAG, e.getMessage(), e);
                                                msg.recycle();
                                                return false;
                                        }

                                        msg.markInUse();设置msg为使用状态,防止msg被重复使用
                                        msg.when = when;设置msg的延迟时间
                                        Message p = mMessages;获取消息池的头节点赋值给临时变量p
                                        boolean needWake; 是否唤醒looper的next
                                        if (p == null || when == 0 || when < p.when) {
    消息池为空或者延迟时间为0或者延迟时间小余头节点的延迟时间,则将其插入消息池的头节点
                                                // New head, wake up the event queue if blocked.
                                                msg.next = p;
                                                mMessages = msg;
                                                needWake = mBlocked; mBlocked是在消息处理next中赋值,如果有消息正在处理则mBlocked=false,如果空闲状态则mBlocked=true,即需要唤醒
                                        } else {
   
                                                // Inserted within the middle of the queue.  Usually we don't have to wake
                                                // up the event queue unless there is a barrier at the head of the queue
                                                // and the message is the earliest asynchronous message in the queue.
                                                needWake = mBlocked && p.target == null && msg.isAsynchronous();如果是空闲状态且p.target == null和msg是异步消息,则需要唤醒
                                                Message prev;
                                                for (;;) {
   遍历链表取出当前msg延迟时间小余其延迟时间的msg
                                                        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 将msg插入p的前面
                                                prev.next = msg;将msg插入prev后面,即插入prev和p的中间
                                        }

                                        // We can assume mPtr != 0 because mQuitting is false.
                                        if (needWake) {
   如果需要唤醒,则调用nativeWake唤醒next方法中的nativePoolOnce
                                                nativeWake(mPtr);
                                        }
                                }
                                return true;                                                        
                        }

总结:

1.消息插入机制:插入消息的顺序是延迟时间最小的放在消息池的头部
2.消息唤醒时机:
    2.1:如果当前消息插入的是头节点,则判断消息处理是不是空闲状态,如果是空闲则唤醒
    2.2:如果消息出入中间节点,则首先判断是不是空闲状态还有`p.target == null`和`msg`是异步消息,这三个条件都成立都可以把needWake置为true,
         之后还要判断当前消息是不是最早的异步消息,如果不是最早的,则needWake 置为 false即不需要唤醒,如果是最早的异步消息,则直接唤醒消息处理循环
4.消息获取过程:

Looper的loop方法:

public static void loop() {
   
    final Looper me = myLooper();获取当前线程的looper对象
    final MessageQueue queue = me.mQueue;获取MessageQueue对象
    for (;;) {
   
        Message msg = queue.next(); // might block获取msg
        if (msg == null) {
   没有消息的时候则退出循环,即主线程退出,应用退出
            // No message indicates that the message queue is quitting.
            return;
        }
        try {
   
            msg.target.dispatchMessage(msg);处理msg
        }
        msg.recycleUnchecked();回收msg
    }
}
void recycleUnchecked() {
   回收消息,并将消息放到消息池中前提是消息池没有满
    // Mark the message as in use while it remains in the recycled object pool.
    // Clear out all other details.
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = -1;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized (sPoolSync) {
   
        if (sPoolSize < MAX_POOL_SIZE) {
   
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}
MessageQueue.java:
Message next() {
   
    // Return here if the message loop has already quit and been disposed.
    // This can happen if the application tries to restart a looper after quit
    // which is not supported.
    final long ptr = mPtr;
    if (ptr == 0) {
   
        return null;
    }

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
   
        if (nextPollTimeoutMillis != 0) {
   
            Binder.flushPendingCommands();
        }
        在这里休眠,如果有消息并唤醒
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
   
            // Try to retrieve the next message.  Return if found.
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
   同步屏障消息的msg.target == null,循环遍历取出第一个异步消息处理
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                do {
   
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
   msg不为空
                if (now < msg.when) {
   当前时间小余msg的延迟时间,则等待:msg.when - now
                    // Next message is not ready.  Set a timeout to wake up when it is ready.
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
   当前时间大于取出的msg的延迟时间
                    // Got a message.
                    mBlocked = false;将mBlocked空闲时间置为false
                    if (prevMsg != null) {
   
                        prevMsg.next = msg.next;
                    } else {
   
                        mMessages = msg.next;取出msg后,将msg的next节点置为头节点
                    }
                    msg.next = null;打断msg到msg next的链表链接
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();将msg的使用标志置为true
                    return msg;返回msg
                }
            } else {
   msg为空表示没有消息需要处理
                // No more messages.
                nextPollTimeoutMillis = -1;
            }

            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {
   当调用了退出方法则返回null给上层
                dispose();内部调用nativeDestroy(mPtr);
                return null;
            }

            // If first time idle, then get the number of idlers to run.
            // Idle handles only run if the queue is empty or if the first message
            // in the queue (possibly a barrier) is due to be handled in the future.
            下面这些信息是处理对于设置了空闲消息处理任务的流程,这个可以用来提高Ui性能,即将主线程空闲状态来处理一些其他事情,充分利用资源
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
   
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
   
                // No idle handlers to run.  Loop and wait some more.
                mBlocked = true; 如果没有任何空闲状态事情处理后,将mBlocked置为true,表示是真正空闲状态,无任何处理事务包括空闲事务,这个值在消息插入的时候对是否唤醒消息处理有关系
                continue;
            }

            if (mPendingIdleHandlers == null) {
   
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        // Run the idle handlers.
        // We only ever reach this code block during the first iteration.
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
   
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler

            boolean keep = false;
            try {
   
                keep = idler.queueIdle();
            } catch (Throwable t) {
   
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }

            if (!keep) {
   
                synchronized (this) {
   
                    mIdleHandlers.remove(idler);
                }
            }
        }

        // Reset the idle handler count to 0 so we do not run them again.
        pendingIdleHandlerCount = 0;

        // While calling an idle handler, a new message could have been delivered
        // so go back and look again for a pending message without waiting.
        nextPollTimeoutMillis = 0;
    }
}

总结:

消息取出过程:
首先判断消息头是否是一个同步屏障消息msg.traget=null,
如果是取出链表中第一个异步消息进行处理,如果不是则直接取出消息池中第一个消息。
如果没有任何消息需要处理,则判断是否有空闲任务需要处理ideHandler,有就去处理空闲任务,没有就将最终空闲状态置为true
5.消息处理:
Looper.loop方法中:
msg.target.dispatchMessage(msg);msg.target =Handler
Handler.dispatchMessage(msg){
   
    if (msg.callback != null) {
   如果msg在创建过程中msg.callback不为null,则直接调用handleCallback(msg)---> message.callback.run();
        handleCallback(msg);
    } else {
   
        if (mCallback != null) {
   
            if (mCallback.handleMessage(msg)) {
   如果Handler在创建的时候传入的Handler.CallBack不为空则调用CallBack的handleMessage方法
                return;如果返回值为true则不会回调Handler的handleMessage,这里可以做一个消息拦截的处理
            }
        }
        handleMessage(msg);调用Handler的handleMessage
    }    
}
6.消息循环处理退出:调用Looper的quit方法
void quit(boolean safe) {
   
    if (!mQuitAllowed) {
   
        throw new IllegalStateException("Main thread not allowed to quit.");
    }

    synchronized (this) {
   
        if (mQuitting) {
   
            return;
        }
        mQuitting = true;将mQuitting标志置为true

        if (safe) {
   
            removeAllFutureMessagesLocked();待处理消息执行完再清理
        } else {
   
            removeAllMessagesLocked();直接清理,可能会有内存泄露风险
        }

        // We can assume mPtr != 0 because mQuitting was previously false.
        nativeWake(mPtr);唤醒消息处理线程
    }
}
7.View的绘制流程中:View绘制会走到scheduleTraversals中
/**
  *:ViewRootImpl.scheduleTraversals()
  */
    void scheduleTraversals() {
   
        if (!mTraversalScheduled) {
   
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();分析1

            // 通过mHandler.post()发送一个runnable,在run()方法中去处理绘制流程
            // 与ActivityThread的Handler消息传递机制相似
            // ->>分析7
            mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);分析2
        }
    }
    final class TraversalRunnable implements Runnable {
   
        @Override
        public void run() {
   
            doTraversal();
        }
    }
    void doTraversal() {
   
        if (mTraversalScheduled) {
   
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);分析3

            if (mProfile) {
   
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
   
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }
    分析1:MessageQueue->postSyncBarrier
    private int postSyncBarrier(long when) {
   
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it.
        synchronized (this) {
   
            final int token = mNextBarrierToken++;
            final Message msg = Message.obtain();去Message的消息池中获取中获取msg
            msg.markInUse();设置inuse
            msg.when = when;设置延迟时间
            msg.arg1 = token;设置token

            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
   这个判断内部其实是消息链表mMessages中取出延迟时间比当前msg的延迟时间更大的msg,
                while (p != null && p.when <= when) {
   
                    prev = p;
                    p = p.next;
                }
            }
            if (prev != null) {
    // 这里面其实是把msg插入消息链表mMessages中
                msg.next = p;
                prev.next = msg;
            } else {
   
                msg.next = p;
                mMessages = msg;
            }
            return token;返回msg的token
        }
    }

总结:

postSyncBarrier的作用是去消息池中获取一个msg,设置了msg.arg1 = token是同步屏障消息的token值,且msg.target = null;并将这个msg放入到消息池mMessages中,下次处理线程被唤醒时会判断消息池的第一个msg的target是不是空并去后面取第一个异步任务,这个异步任务其实是一个view的绘制流程同步屏障实现了view优先绘制

分析2:mChoreographer.postCallback
    postCallbackDelayed(callbackType, action, token, 0);
            postCallbackDelayedInternal(callbackType, action, token, delayMillis);{
   
                    synchronized (mLock) {
   
        final long now = SystemClock.uptimeMillis();
        final long dueTime = now + delayMillis;
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

        if (dueTime <= now) {
   
            scheduleFrameLocked(now);//分析8
        } else {
   
            Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);获取一个msg
            msg.arg1 = callbackType;设置arg1参数类型
            msg.setAsynchronous(true);设置为异步消息
            mHandler.sendMessageAtTime(msg, dueTime);发送消息
        }
    }
    分析8scheduleFrameLocked(now);
    private void scheduleFrameLocked(long now) {
   
            ...
            scheduleVsyncLocked();
            ...
    }
    private void scheduleVsyncLocked() {
   
    mDisplayEventReceiver.scheduleVsync();//这个mDisplayEventReceiver = FrameDisplayEventReceiver对象
}
    public void scheduleVsync() {
   
    if (mReceiverPtr == 0) {
   
        Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
                + "receiver has already been disposed.");
    } else {
   
        nativeScheduleVsync(mReceiverPtr);//这里调用nativeScheduleVsync注册了一个Vsync事件接收器,接收者为前面的mDisplayEventReceiver
    }
}
    private final class FrameDisplayEventReceiver extends DisplayEventReceiver{
   
            @Override
    public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
   
                    Message msg = Message.obtain(mHandler, this);
        msg.setAsynchronous(true);
        mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);//这里发送了一个异步信号,每16ms接收到一次信号,并绘制ui
            }

    }

总结:
>

postCallback内部主要实现的是获取一个msg,并设置msg为异步消息,最后发送消息给MessageQueue
注册了vsync信号回调,每16ms获取到vsync信号,并更新ui,所以ondraw方法是在接收到vsync信号后才调用的,会每16ms回调一次ondraw方法

分析3:mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
    public void removeSyncBarrier(int token) {
   
        // Remove a sync barrier token from the queue.
        // If the queue is no longer stalled by a barrier then wake it.
        synchronized (this) {
   
            Message prev = null;
            Message p = mMessages;
            while (p != null && (p.target != null || p.arg1 != token)) {
   其实是取出target==null且p.arg1=传入的token的值,即之前插入消息链表mMessages的msg
                prev = p;
                p = p.next;
            }
            if (p == null) {
   
                throw new IllegalStateException("The specified message queue synchronization "
                        + " barrier token has not been posted or has already been removed.");
            }
            final boolean needWake;

            if (prev != null) {
   这个if是将p在链表中去除,prev不为null说明p不在表头
                prev.next = p.next;
                needWake = false;
            } else {
   为null说明p在表头。
                mMessages = p.next;
                needWake = mMessages == null || mMessages.target != null;如果mMessages == null || mMessages.target != null;表头数据不是同步屏障消息,或者消息池数据为空,则唤醒消息处理线程
            }
            p.recycleUnchecked();回收消息到消息池sPool中

            // If the loop is quitting then it is already awake.
            // We can assume mPtr != 0 when mQuitting is false.
            if (needWake && !mQuitting) {
   
                nativeWake(mPtr);
            }
        }
    }

总结:根据token值移除消息链表中的msg并根据情况唤醒消息处理线程

分析1和分析2,3可知View的绘制流程其实就是在View绘制流程启动前,给消息池发送一个msg.target为空的消息,然后给View的绘制任务的msg设置为异步消息,下次在Handler取消息的过程中优先判断消息池的msg.target是不是空,如果是,则去消息池中取出第一个异步消息执行。执行前先把同步屏障消息移除。这就是消息同步屏障机制

7.ThreadLocal机制:sThreadLocal.set(new Looper(quitAllowed));
public void set(T value) {
   
        //(1)获取当前线程(调用者线程)
        Thread t = Thread.currentThread();
        //(2)以当前线程作为key值,去查找对应的线程变量,找到对应的map
        ThreadLocalMap map = getMap(t);
        //(3)如果map不为null,就直接添加本地变量,key为当前定义的ThreadLocal变量的this引用,值为添加的本地变量值
        if (map != null)
                map.set(this, value);
        //(4)如果map为null,说明首次添加,需要首先创建出对应的map
        else
                createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
   
        return t.threadLocals; //获取线程自己的变量threadLocals,并绑定到当前调用线程的成员变量threadLocals上
}

void createMap(Thread t, T firstValue) {
   
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}


public T get() {
   
        //(1)获取当前线程
        Thread t = Thread.currentThread();
        //(2)获取当前线程的threadLocals变量
        ThreadLocalMap map = getMap(t);
        //(3)如果threadLocals变量不为null,就可以在map中查找到本地变量的值
        if (map != null) {
   
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
   
                        @SuppressWarnings("unchecked")
                        T result = (T)e.value;
                        return result;
                }
        }
        //(4)执行到此处,threadLocals为null,调用该更改初始化当前线程的threadLocals变量
        return setInitialValue();
}

private T setInitialValue() {
   
        //protected T initialValue() {return null;}
        T value = initialValue();
        //获取当前线程
        Thread t = Thread.currentThread();
        //以当前线程作为key值,去查找对应的线程变量,找到对应的map
        ThreadLocalMap map = getMap(t);
        //如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值
        if (map != null)
                map.set(this, value);
        //如果map为null,说明首次添加,需要首先创建出对应的map
        else
                createMap(t, value);
        return value;
}

总结:

ThreadLocal其实是一种用空间换时间的机制:
ThreadLocal内部的其实都是针对当前线程的ThreadLocalMap做的操作,一个线程只有一个Thread,一个Thread只有一个ThreadLocalMap,所以其内部存储的数据都是线程隔离的。
而且在
static final ThreadLocal<Looper> sThreadLocal = newThreadLocal<Looper>();
可以看到这个sThreadLocal在所有线程中只有一个,所以获取value的时候key``都是同一个,只是这个ThreadLocalMap是在每个线程中有一份,所以获取的值是不同线程中的value
sThreadLocal同一个对象中,相当于一个统一入口,内部操作获取value的和设置value都是针对当前线程来操作的,所以在不用线程中获取的是当前线程的值

相关文章
|
2月前
|
数据采集 监控 API
告别手动埋点!Android 无侵入式数据采集方案深度解析
传统的Android应用监控方案需要开发者在代码中手动添加埋点,不仅侵入性强、工作量大,还难以维护。本文深入探讨了基于字节码插桩技术的无侵入式数据采集方案,通过Gradle插件 + AGP API + ASM的技术组合,实现对应用性能、用户行为、网络请求等全方位监控,真正做到零侵入、易集成、高稳定。
515 41
|
7月前
|
Android开发 开发者
Android自定义view之利用drawArc方法实现动态效果
本文介绍了如何通过Android自定义View实现动态效果,重点使用`drawArc`方法完成圆弧动画。首先通过`onSizeChanged`进行测量,初始化画笔属性,设置圆弧相关参数。核心思路是不断改变圆弧扫过角度`sweepAngle`,并调用`invalidate()`刷新View以实现动态旋转效果。最后附上完整代码与效果图,帮助开发者快速理解并实践这一动画实现方式。
192 0
|
5月前
|
安全 数据库 Android开发
在Android开发中实现两个Intent跳转及数据交换的方法
总结上述内容,在Android开发中,Intent不仅是活动跳转的桥梁,也是两个活动之间进行数据交换的媒介。运用Intent传递数据时需注意数据类型、传输大小限制以及安全性问题的处理,以确保应用的健壯性和安全性。
403 11
|
6月前
|
安全 Java Android开发
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
297 0
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
|
7月前
|
XML 搜索推荐 Android开发
Android改变进度条控件progressbar的样式(根据源码修改)
本文介绍了如何基于Android源码自定义ProgressBar样式。首先分析了系统源码中ProgressBar样式的定义,发现其依赖一张旋转图片实现动画效果。接着分两步指导开发者实现自定义:1) 模仿源码创建一个旋转动画XML文件(放置在drawable文件夹),修改图片为自定义样式;2) 在UI控件中通过`indeterminateDrawable`属性应用该动画。最终实现简单且个性化的ProgressBar效果,附带效果图展示。
474 2
|
7月前
|
消息中间件 Android开发
Android Handler的使用方式以及其机制的简单介绍
Handler 是 Android 中实现线程间通信的重要机制,可传递任意两线程数据。常用场景包括子线程向主线程(UI 线程)传递结果,以及主线程向子线程发送消息。其核心涉及四个类:Handler(发送/接收消息)、Message(消息载体)、MessageQueue(消息队列)和 Looper(消息循环泵)。基本流程为:Handler 发送 Message 至 MessageQueue,Looper 从队列中按 FIFO 取出并处理。
227 0
|
8月前
|
NoSQL 应用服务中间件 PHP
布谷一对一直播源码android版环境配置流程及功能明细
部署需基于 CentOS 7.9 系统,硬盘不低于 40G,使用宝塔面板安装环境,包括 PHP 7.3(含 Redis、Fileinfo 扩展)、Nginx、MySQL 5.6、Redis 和最新 Composer。Swoole 扩展需按步骤配置。2021.08.05 后部署需将站点目录设为 public 并用 ThinkPHP 伪静态。开发环境建议 Windows 操作系统与最新 Android Studio,基础配置涉及 APP 名称修改、接口域名更换、包名调整及第三方登录分享(如 QQ、微信)的配置,同时需完成阿里云与腾讯云相关设置。
|
9月前
|
XML JavaScript Android开发
【Android】网络技术知识总结之WebView,HttpURLConnection,OKHttp,XML的pull解析方式
本文总结了Android中几种常用的网络技术,包括WebView、HttpURLConnection、OKHttp和XML的Pull解析方式。每种技术都有其独特的特点和适用场景。理解并熟练运用这些技术,可以帮助开发者构建高效、可靠的网络应用程序。通过示例代码和详细解释,本文为开发者提供了实用的参考和指导。
334 15
|
9月前
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
|
Java 调度 Android开发
安卓与iOS开发中的线程管理差异解析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自拥有独特的魅力。如同东西方文化的差异,它们在处理多线程任务时也展现出不同的哲学。本文将带你穿梭于这两个平台之间,比较它们在线程管理上的核心理念、实现方式及性能考量,助你成为跨平台的编程高手。

推荐镜像

更多