iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >Android Loop机制中Looper与handler怎么使用
  • 215
分享到

Android Loop机制中Looper与handler怎么使用

2023-07-04 14:07:27 215人浏览 薄情痞子
摘要

今天小编给大家分享一下Android Loop机制中Looper与handler怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我

今天小编给大家分享一下Android Loop机制中Looper与handler怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。

    Looper是什么

    用于为线程运行消息循环的类。默认情况下,线程没有与之关联的消息循环。要创建一个,在要运行循环的线程中调用 prepare(),然后调用loop()让它处理消息,直到循环停止为止。与消息循环的大多数交互是通过 Handler类进行的。

    意思大概就是让线程有处理消息的能力,并且这种能力是无限循环的,直到被停止为止。

    简单使用

     public Handler handler; public void looperThread(){     new Thread(new Runnable() {         @Override         public void run() {             Looper.prepare();             handler = new Handler(Looper.myLooper(),new Handler.Callback() {                 @Override                 public boolean handleMessage(Message msg) {                     Log.e(TAG,"收到发送过来的消息:"+msg.obj.toString());                     return false;                 }             });             Looper.loop();         }     }).start(); } @Override public void onClick(View view) {     Message message = Message.obtain();     message.obj = "点击事件消息时间戳:"+System.currentTimeMillis()%10000;     handler.sendMessage(message); }

    创建一个具有消息循环的线程,该线程中创建一个和该looper绑定的handler对象,然后点击事件中不断的去发送消息给looper循环,看下最后的效果如下:

    17:45.459 12495-12538/com.example.myapplication E/[MainActivity]: 收到发送过来的消息:点击事件消息时间戳:5458
    18:17:45.690 12495-12538/com.example.myapplication E/[MainActivity]: 收到发送过来的消息:点击事件消息时间戳:5690
    18:17:45.887 12495-12538/com.example.myapplication E/[MainActivity]: 收到发送过来的消息:点击事件消息时间戳:5886
    ...省略
    18:18:40.010 12495-12538/com.example.myapplication E/[MainActivity]: 收到发送过来的消息:点击事件消息时间戳:9
    18:18:40.840 12495-12538/com.example.myapplication E/[MainActivity]: 收到发送过来的消息:点击事件消息时间戳:839
    18:18:41.559 12495-12538/com.example.myapplication E/[MainActivity]: 收到发送过来的消息:点击事件消息时间戳:1558

    可以看到我一直点击,一直有消息可以被处理,那么说明我创建的线程是一直运行的,并没有结束。那么looper具体是怎么实现的这样的功能的呢?

    源码了解loop原理

    在分析源码之前,先看下整体的类图关系:

    Android Loop机制中Looper与handler怎么使用

    loop分析

    我们从Looper.prepare();这句代码开始分析:

    Looper.prepare();
    public final class Looper {    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();    private static Looper sMainLooper;  // guarded by Looper.class    final MessageQueue MQueue;    final Thread mThread;    ...省略    public static void prepare() {        prepare(true);    }  ...省略

    可以看到调用了prepare()方法后,接着调用了有参函数prepare:

    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));}

    sThreadLocal的泛型参数是Looper,那么知道Looper保存在了线程所持有的map容器中,首先就是判断sThreadLocal.get()是否为空,这个方法在上一章说过,是根据当前线程来获取的,如果这个prepare方法在ui线程中调用那么返回的就是ui线程中的Looper,如果调用的是子线程中,那么返回的就是子线程的Looper了,如果不为空,抛出异常,意思就是一个线程只能持有一个Looper对象;如果为空的话,那么调用sThreadLocal的set方法将创建的Looper对象存放到对应线程的map容器中。

    接着调用了loop函数:

    Looper.loop();
        public static void loop() {        final Looper me = myLooper();        ...省略        final MessageQueue queue = me.mQueue;        for (;;) {            Message msg = queue.next(); // might block            if (msg == null) {                 return;            }        ...省略            try {                msg.target.dispatchMessage(msg);            } finally {            ...省略            }  ...省略             msg.recycleUnchecked();        }    }

    大概是这样的,其中去掉了一些和业务无关的代码。

    myLooper()

    第一步调用myLooper()方法:

    final Looper me = myLooper();final MessageQueue queue = me.mQueue;public static @Nullable Looper myLooper() {    return sThreadLocal.get();}

    获取当前线程的sThreadLocal中的Looper对象。从Looper对象获取队列。

    第二步开始for循环,Message msg = queue.next(); // might block 在循环中不断的从queue中取Message消息,

    获取msg判断是否为空,空的话直接返回,不为空的话,调用msg的Target的dispatchMessage方法。最后msg使用完毕之后就回收msg对象。

    首先来看下

    Message msg = queue.next(); // might block
    next()

    调用的是MessageQueue的next方法,代码如下:

     Message next() {    ...省略        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) {                    // 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) {                        // 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;                } ...省略            }             ...省略        }    }

    首先调用nativePollOnce(ptr, nextPollTimeoutMillis); 这个方法是调用的native方法,意思就是阻塞当前线程,在延迟nextPollTimeoutMillis时长后唤醒当前线程。

    接着调用:

    final long now = SystemClock.uptimeMillis();Message prevMsg = null;Message msg = mMessages;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());}

    其中的判断是msg.target == null这个条件,这个条件说明当前的msg是没有设置Target的,msg的Target一般是handler,如果这里是空的话,那么这个msg就是同步屏障消息,用于拦截同步消息的,让异步消息有优先处理权。如果当前是同步屏障的话,那么while循环,一直向后遍历msg节点,条件是这个msg非空和非异步消息,所以这里能够跳出循环的情况就是msg到了尾部为空了,要么就是向后遍历发现了异步消息。接着往下看:

    if (msg != null) {    if (now < msg.when) {        // 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;}

    分为两种情况:

    (1)如果msg为空的话,先设置延迟时长nextPollTimeoutMillis = -1;接着这趟for循环结束,回到起点的位置,又开始执行nativePollOnce(ptr, nextPollTimeoutMillis);延迟时间是-1那么线程就会阻塞下去,直到被唤醒,不会执行for循环了(msg在进入队列的时候会去唤醒线程的,所以这里不会一直阻塞的)。

    (2)如果msg不为空的话,假设消息设置的时间点大于现在的时间点,那么设置nextPollTimeoutMillis 为时间差和整数最大值中的最小值。这样的话,线程在下次循环中的开头就会阻塞到可以执行该消息的when时间节点再次运行(线程在阻塞的时候不会去轮转cpu时间片所以可以节约cpu资源,同样的,如果阻塞期间有消息进来可以马上运行,那么还是会被唤醒的);假设消息设置的时间点小于现在的时间点,那么从msg消息链中把该消息摘取出来,msg标记为使用中,将msg返回。

    思考:队列中头部msg是同步屏障的话,那么优先从前往后去查找异步消息进行处理,所以在同步屏障消息之后的同步消息不会被执行,直到被移除为止。队列头部是普通的消息的时候,是根据when时间节点来判断,是直接返回msg,还是等待when-now时间差在去循环一遍查找头结点msg。

    handler.dispatchMessage
    handler = new Handler(Looper.myLooper(),new Handler.Callback() {           @Override           public boolean handleMessage(Message msg) {               Log.e(TAG,"收到发送过来的消息:"+msg.obj.toString());               return false;           }       });

    handler在创建的参数是Looper和Callback,接着再来看下dispatchMessage是如何实现的:

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

    如果msg存在callback的话,直接调用callbakc的run方法,这里不存在我们传递msg没有设置callback,那么走下面的那个逻辑,我们给handler设置了mCallback,那么就直接回调handler的mCallback.handleMessage的方法:

        @Override    public boolean handleMessage(Message msg) {        Log.e(TAG,"收到发送过来的消息:"+msg.obj.toString());        return false;    }

    这样也就出现了我们开头demo中的打印消息了。

    handler分析

    我们通过上面的next方法分析了如何从队列中获取消息,那么我们还没有分析消息是如何入队的,接下来我们来分析下handler的几个关键的问题,(1)handler的消息一个分为几种;(2)handler发送消息到哪去了。

    我们从handler的构造函数入手:

    handler = new Handler(Looper.myLooper(),new Handler.Callback() {               @Override               public boolean handleMessage(Message msg) {                   Log.e(TAG,"收到发送过来的消息:"+msg.obj.toString());                   return false;               }           });public Handler(Looper looper, Callback callback) {    this(looper, callback, false);}public Handler(Looper looper, Callback callback, boolean async) {    mLooper = looper;    mQueue = looper.mQueue;    mCallback = callback;    mAsynchronous = async;}

    我们可以看到,handler一共持有四个关键变量,Looper循环(和looper关联,handler发送的消息只会发到这个队列中),mQueue 持有Looper的队列,mCallback 用于处理消息的回调函数,mAsynchronous 标志这个handler发送的消息是同步的还是异步的。

    我们再来看一下消息是怎么发送的:

    Message message = Message.obtain();message.obj = "点击事件消息时间戳:"+System.currentTimeMillis()%10000;handler.sendMessage(message);

    首先从Message中获取一个message,这个Message其实里面保存着msg的链表,遍历链表,返回的是回收的msg,其中flags整数变量标志着msg是否正在使用中,是否是异步消息等等状态。

    handler.sendMessage(message);

    然后使用handler去发送一个msg对象、接着进去看下:

    public final boolean sendMessage(Message msg) {    return sendMessageDelayed(msg, 0);}public final boolean sendMessageDelayed(Message msg, long delayMillis){if (delayMillis < 0) {    delayMillis = 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);}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);}

    msg初始状态下是同步消息,sendMessage方法发送出去的消息delayMillis 延迟时间是0;

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

    在入队列之前,将msg的Target设置为当前handler,然后根据handler是否是异步的,设置msg是否是异步的,然后调用队列的入队函数,将消息入队。

    这里先回答第二个问题,如何入队的:

    消息入队
      boolean enqueueMessage(Message msg, long when) {        synchronized (this) {            msg.markInUse();            msg.when = when;            Message p = mMessages;            boolean needWake;            if (p == null || when == 0 || when < p.when) {                msg.next = p;                mMessages = msg;                needWake = mBlocked;            } 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;            }            if (needWake) {                nativeWake(mPtr);            }        }        return true;    }

    首先判断当前入队msg的when时间是否比队列中的头结点的when时间节点靠前,靠前的话,就将入队的msg加入到队列的头部,并且调用nativeWake(mPtr);方法唤醒looper所在的线程,那么next()开始执行了,可以马上遍历队列,消耗msg消息。如果当前消息msg的时间节点when大于头部节点,首先设置needWake标志, 是否需要唤醒分为:如果队列头部是同步屏障,并且入队消息msg是异步消息,那么就需要唤醒线程,其他情况不需要唤醒;接着执行for循环,循环里面寻找队列中第一个节点时间是大于msg消息的时间节点的(这意味着队列中消息是按照时间节点排序的),循环结束后,将入队的msg插入到队列中,最后根据需要是否唤醒线程。

    同步屏障

    同步屏障功能是让队列中的同步消息暂时不执行,直到同步屏障被移除,异步消息可以不受影响的被执行,相当于排队买票的队列中头部有个人一直卡着不走,只有vip的人才能正常在窗口中买票,其他普通人买不了票,如果那个头部卡着的那个人不走的话。这个同步屏障非常有用,用于优先执行某些任务。

    同步屏障我们使用的比较少,但是安卓frame层代码有使用这个同步屏障的功能,例如ViewRootImp中:

    ViewRootImp中:    void scheduleTraversals() {    ...省略            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();            mChoreographer.postCallback(                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);            ...省略    }Choreographer中:   private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) {          ...省略            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);             if (dueTime <= now) {                scheduleFrameLocked(now);            } else {                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);                msg.arg1 = callbackType;                msg.setAsynchronous(true);                mHandler.sendMessageAtTime(msg, dueTime);            }        }    }

    向队列中发送一个同步屏障getQueue().postSyncBarrier();看下源码如何实现的:

       public int postSyncBarrier() {        return postSyncBarrier(SystemClock.uptimeMillis());    }    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 Message msg = Message.obtain();            msg.markInUse();            msg.when = when;            msg.arg1 = token;            Message prev = null;            Message p = mMessages;            if (when != 0) {                while (p != null && p.when <= when) {                    prev = p;                    p = p.next;                }            }            if (prev != null) { // invariant: p == prev.next                msg.next = p;                prev.next = msg;            } else {                msg.next = p;                mMessages = msg;            }            return token;        }    }

    同步屏障的时间节点是当前时间,还可以知道同步屏障消息的Target是空的,成员变量arg1保存的是同步屏障的自增值。接下来就是找到队列中第一个时间节点比自己大的节点位置,然后插入到队列中,所以屏障也是按照时间来排列的,没有特殊待遇。

    接着使用handler向Looper中发送了一个异步消息:

       Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);   msg.arg1 = callbackType;   msg.setAsynchronous(true);   mHandler.sendMessageAtTime(msg, dueTime);

    可以看到异步消息需要设置msg.setAsynchronous(true);

    执行ui的任务使用异步消息去执行,为啥要用异步,因为在5.0以上的安卓系统中已经开始使用了垂直同步技术了,所以重绘页面的操作需要按照屏幕刷新率来执行,假如一个16ms里面有多次重绘请求,最终也只会抛弃掉,只保留一个重绘消息,所以,为了保证重绘操作能够在收到同步信号的时间节点马上执行,必须使用同步屏障,这样前面排队的同步消息暂时不执行,优先执行我们的重绘界面的异步消息,这样可以保证我们的界面尽量能够及时刷新,避免丢帧。、

    再来看下handler.post()方法:

        public final boolean post(Runnable r){       return  sendMessageDelayed(getPostMessage(r), 0);    }    private static Message getPostMessage(Runnable r) {        Message m = Message.obtain();        m.callback = r;        return m;    }

    可以看到,其实也是封装了一个msg对象,将callback传递给它,我们在dispatchMessge函数中也知道,如果msg如果有自己的callback 就会调用这个回调处理消息,不会使用handler自己的callback 来处理消息。

    以上就是“Android Loop机制中Looper与handler怎么使用”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注编程网精选频道。

    --结束END--

    本文标题: Android Loop机制中Looper与handler怎么使用

    本文链接: https://www.lsjlt.com/news/345969.html(转载时请注明来源链接)

    有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

    本篇文章演示代码以及资料文档资料下载

    下载Word文档到电脑,方便收藏和打印~

    下载Word文档
    猜你喜欢
    • Android Loop机制中Looper与handler怎么使用
      今天小编给大家分享一下Android Loop机制中Looper与handler怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我...
      99+
      2023-07-04
    • Android的Looper怎么使用
      在Android中,Looper是用来将一个线程与一个消息队列关联起来的类。它可以使线程能够处理来自消息队列的消息。下面是一些使用L...
      99+
      2023-10-18
      Android
    • Android消息机制Handler如何使用
      这篇文章主要介绍“Android消息机制Handler如何使用”,在日常操作中,相信很多人在Android消息机制Handler如何使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Android消息机制Ha...
      99+
      2023-06-21
    • Android中Handler与Message如何使用
      这期内容当中小编将会给大家带来有关Android中Handler与Message如何使用,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。Message:Message是在线程之间传递的消息,它可以在内部携带...
      99+
      2023-05-30
      android handler message
    • Android在子线程中怎么调用Handler
      这篇文章主要介绍“Android在子线程中怎么调用Handler”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Android在子线程中怎么调用Handler”文章能帮助大家解决问题。简介如果是Han...
      99+
      2023-07-04
    • ES6中如何使用let声明变量以及let loop机制
      这篇文章主要为大家展示了“ES6中如何使用let声明变量以及let loop机制”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“ES6中如何使用let声明变量以及...
      99+
      2024-04-02
    • Go语言中循环Loop怎么使用
      本篇内容介绍了“Go语言中循环Loop怎么使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!Go语言和其他语言不一样,它只有一种循环方式,就...
      99+
      2023-07-02
    • android开发中使用Handler怎么实现预加载功能
      这篇文章将为大家详细讲解有关android开发中使用Handler怎么实现预加载功能,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。在进行Android客户端界面开发时,我们常常会需要将从服务...
      99+
      2023-05-31
      android handler roi
    • Android中怎么利用Handler防止内存泄露
      今天就跟大家聊聊有关Android中怎么利用Handler防止内存泄露,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。 Handler可能导致的内存泄露及其优化 &...
      99+
      2023-05-30
      android handler
    • Netty的Handler链调用机制是什么及怎么组织
      这篇“Netty的Handler链调用机制是什么及怎么组织”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Netty的Hand...
      99+
      2023-07-05
    • 如何在Android中使用Handler与Countdowntimer实现一个倒计时功能
      本篇文章为大家展示了如何在Android中使用Handler与Countdowntimer实现一个倒计时功能,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。实现方法去除actionBar闪屏页面一般都...
      99+
      2023-05-31
      android handler countdowntimer
    • android沙箱机制怎么应用
      Android沙箱机制是指将每个应用程序都限制在自己的运行环境中,不能访问其他应用程序的资源和数据。应用程序之间相互隔离,提高了系统...
      99+
      2023-09-20
      android
    • android中recyclerview复用机制是什么
      Android中的RecyclerView是一种高效的列表视图控件,它通过复用已经创建好的视图来减少内存开销和提高性能。 Recyc...
      99+
      2024-03-08
      android
    • MySQL中怎么使用LOOP循环进行数据清洗
      在MySQL中,可以使用存储过程和游标来实现循环遍历数据并进行数据清洗操作。以下是一个使用存储过程和游标进行数据清洗的示例: 创建...
      99+
      2024-04-30
      MySQL
    • MySQL中怎么使用LOOP语句执行重复操作
      在MySQL中,可以使用WHILE循环语句来执行重复操作,示例如下: DELIMITER // CREATE PROCEDURE ...
      99+
      2024-04-30
      MySQL
    • C++中怎么使用反射机制
      今天就跟大家聊聊有关C++中怎么使用反射机制,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。在Java编程中,我们经常要用到反射,通过反射机制实现在配置文件中的灵活配置, 但在C++编...
      99+
      2023-06-17
    • android休眠唤醒机制怎么应用
      Android的休眠和唤醒机制是指系统在一段时间内处于低功耗的休眠状态,以节省电量,而当需要进行某些操作时,系统会被唤醒。以下是几种...
      99+
      2023-09-08
      android
    • Android中怎么实现图片缓存机制
      这期内容当中小编将会给大家带来有关Android中怎么实现图片缓存机制,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。Android 图片缓存机制的深入理解Android加载一张图片到用户界面是很简单的,但...
      99+
      2023-05-30
      android
    • 怎么在Python中使用handler方法输出日志
      今天就跟大家聊聊有关怎么在Python中使用handler方法输出日志,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。Python主要用来做什么Python主要应用于:1、Web开发;...
      99+
      2023-06-14
    • android应用中onTouchEvent处理机制是什么
      本篇文章为大家展示了android应用中onTouchEvent处理机制是什么,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。一、 概述只有view,ViewGroup,Activity 具有事件分发...
      99+
      2023-05-31
      android ontouchevent roi
    软考高级职称资格查询
    编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
    • 官方手机版

    • 微信公众号

    • 商务合作