Q1:Handler.postDelayed(runnable, time),假设delay time == 3min,如果把手机时间调整为比当前时间快3min, runnable会立马执行吗?
答:不会。postDelayed最终会调用到sendMessageDelayed,注意此时用到的时间被处理成 SystemClock.uptimeMillis() + delayMillis,表示系统启动到当前的毫秒数+延时毫秒数。所以修改系统时间并不会改变这个数值,也就是说修改系统时间不影响handler的延时任务
// Handler.java
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
Q2:如果消息队列中的消息都剩下延时任务,最近的延时也要等到2s后,在此期间消息队列还会一直循环取消息吗?
答:不会 ,Looper.loop()消息循环取消息的过程,重点在于nextPollTimeoutMillis的赋值,如果有消息未到执行时间,nextPollTimeoutMillis 赋值为需要等待的时间,传递给nativePollOnce方法,告知native空闲等待。nativePollOnce该函数可以看做睡眠阻塞的入口,该函数是一个native函数
// 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 (;;) {
//如果nextPollTimeoutMillis !=0 ,执行空闲等待阻塞循环;
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//告知native底层,到达nextPollTimeoutMillis 的时候,再唤醒循环
nativePollOnce(ptr, nextPollTimeoutMillis);
//synchronized同步,所以没消息的情况下,message.next()可能会阻塞
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//优先处理异步消息;异步消息没有设置target且msg.setAsynchronous(true);
if (msg != 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) {
if (now < msg.when) {
// 消息没到执行时间,则将nextPollTimeoutMillis设置为需要空闲等待阻塞时间。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 {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
....
// 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;
}
}
Q3.卡顿检测原理:
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
....
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
....
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);
}
}
....
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
msg.recycleUnchecked();
}
}
可以看到在msg.target.dispatchMessage(msg);执行前后,都调用了logging.print方法,这个logging 还可以自定义设置自己的logging,
Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor);
class MyLooperMonitor implements Printer {
....
@Override
public void println(String x) {
if (!mPrintingStarted) {
mStartTimestamp = System.currentTimeMillis();
mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
mPrintingStarted = true;
startDump(); //收集卡顿堆栈信息 thread.getStackTrace
} else {
final long endTime = System.currentTimeMillis();
mPrintingStarted = false;
if (isBlock(endTime)) { //判断消息执行时间是否超时,如果endTime - mStartTimestamp超过3s,视为卡顿。
notifyBlockEvent(endTime);
}
stopDump();
}
}
本地检测卡顿工具:
adb shell dumpsys gfxinfo <PACKAGE_NAME>
可打印出帧率,FPS 等数据。
Q4:ThreadLocal会造成内存泄漏吗?
ThreadLocal:线程变量副本;每个线程保存自己的副本,互不干扰。
引用关系:Thread--- 内部变量:ThreadLocalMap map--->entry--->WeakReference<ThreadLocal> + value
public class Thread implements Runnable {
// 每个Thread实例都持有一个ThreadLocalMap的属性
ThreadLocal.ThreadLocalMap threadLocals = null;
}
//ThreadLocal.java
//ThreadLocalMap内部类 ,持有Entry, entry持有threadlocal的弱引用和对value的引用
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
}
为什么使用弱引用?会造成内存泄漏吗?如何避免?多线程?
-
假设threadlocal使用强引用,对外部强引用 threadlocalInstance = null 后,如果线程还没结束,由于threadlocalmap的entry还持有threadlocal的强引用,导致threadlocalInstance依然不能被回收,这样就无法真正达到业务逻辑的目的,出现逻辑错误;
-
使用弱引用,如果threadLocal外部强引用被置为null(threadLocalInstance=null)的话,因为threadLocalMap.entry只持有threadlocal的弱引用,threadLocal实例就没有一条引用链路可达,很显然在GC (垃圾回收)的时候势必会被回收,因此entry就存在key为null的情况,无法通过一个Key为null去访问到该entry的value。同时,就存在了这样一条引用链:threadRef->currentThread->threadLocalMap->entry->valueRef->valueMemory,导致在垃圾回收的时候进行可达性分析的时候,value可达从而不会被回收掉,但是该value永远不能被访问到,这样就存在了内存泄漏**。当然,如果线程执行结束后,threadLocal,threadRef会断掉,因此threadLocal,threadLocalMap,entry都会被回收掉。可是,在实际使用中我们都是会用线程池去维护我们的线程,比如在Executors.newFixedThreadPool()时创建线程的时候,为了复用线程是不会结束的,所以threadLocal内存泄漏就值得我们关注.
val sThreadLocal = ThreadLocal<String>()
val pool = Executors.newSingleThreadExecutor()
for (i in 0 until 10) {
if (i == 0) {
pool.execute(Runnable {
sThreadLocal.set("lijing")
// sThreadLocal.remove()
})
} else {
pool.execute(Runnable {
Log.d("sThreadLocal", Thread.currentThread().name + sThreadLocal.get())
})
}
}
假设Entry弱引用threadLocal,尽管会出现内存泄漏的问题,但是在threadLocal的生命周期里(set,getEntry,remove)里,都会针对key为null的脏entry进行处理。
从以上的分析可以看出,使用弱引用的话在threadLocal生命周期里会尽可能的保证不出现内存泄漏的问题,达到安全的状态。
ThreadLocal最佳实践
每次使用完ThreadLocal,都调用它的remove()方法,清除数据。
在使用线程池的情况下,没有及时清理ThreadLocal,不仅是内存泄漏的问题,更严重的是可能导致业务逻辑出现问题。所以,使用ThreadLocal就跟加锁完要解锁一样,用完就清理。
参考
网友评论