欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Android 消息處理機(jī)制

 更新時(shí)間:2020年10月20日 14:29:04   作者:Codeing_ls  
這篇文章主要介紹了Android 消息處理機(jī)制的相關(guān)資料,幫助大家更好的進(jìn)行Android開(kāi)發(fā),感興趣的朋友可以了解下

摘要

Android應(yīng)用程序是通過(guò)消息來(lái)驅(qū)動(dòng)的,當(dāng)Android主線程啟動(dòng)時(shí)就會(huì)在內(nèi)部創(chuàng)建一個(gè)消息隊(duì)列。然后進(jìn)入一個(gè)無(wú)限循環(huán)中,輪詢是否有新的消息需要處理。如果有新消息就處理新消息。如果沒(méi)有消息,就進(jìn)入阻塞狀態(tài),直到消息循環(huán)被喚醒。
那么在Android系統(tǒng)中,消息處理機(jī)制是怎么實(shí)現(xiàn)的呢?在程序開(kāi)發(fā)時(shí),我們經(jīng)常會(huì)使用Handler處理Message(消息)。所以可以知道Handler是個(gè)消息處理者,Message是消息主體。除此之外還有消息隊(duì)列和消息輪詢兩個(gè)角色。它們分別是MessageQueue和Looper,MessageQueue就是消息隊(duì)列,Looper負(fù)責(zé)輪詢消息。

簡(jiǎn)介

我們已經(jīng)知道Android的消息機(jī)制處理主要由Handler、Message、MessageQueue、Looper四個(gè)類(lèi)的實(shí)現(xiàn)來(lái)完成。那么它們之間的關(guān)系是怎樣的?
其中,Message是消息主體,它負(fù)責(zé)存儲(chǔ)消息的各種信息,包括發(fā)送消息的Handler對(duì)象、消息信息、消息標(biāo)識(shí)等。MessageQueue就是消息隊(duì)列,在其內(nèi)部以隊(duì)列的形式維護(hù)一組Message(消息)。Handler負(fù)責(zé)發(fā)送和處理消息。Looper負(fù)責(zé)輪詢消息隊(duì)列。

Android消息機(jī)制原理

創(chuàng)建線程消息隊(duì)列

在Android應(yīng)用程序中,消息處理程序運(yùn)行前首先要?jiǎng)?chuàng)建消息隊(duì)列(也就是MessageQueue)。在主線程中,通過(guò)調(diào)用Looper類(lèi)的靜態(tài)成員函數(shù)prepareMainLooper()來(lái)創(chuàng)建消息隊(duì)列。在其他子線程中,通過(guò)調(diào)用靜態(tài)成員函數(shù)prepare()來(lái)創(chuàng)建。
prepareMainLooper()與prepare()的實(shí)現(xiàn):

  /**
   * Initialize the current thread as a looper, marking it as an
   * application's main looper. The main looper for your application
   * is created by the Android environment, so you should never need
   * to call this function yourself. See also: {@link #prepare()}
   * 用來(lái)初始化主線程中的Looper,有Android環(huán)境調(diào)用,不應(yīng)該有用戶調(diào)用.
   */
  public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
      if (sMainLooper != null) {
        throw new IllegalStateException("The main Looper has already been prepared.");
      }
      sMainLooper = myLooper();
    }
  }
  
  public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
  }

   /** Initialize the current thread as a looper.
   * This gives you a chance to create handlers that then reference
   * this looper, before actually starting the loop. Be sure to call
   * {@link #loop()} after calling this method, and end it by calling
   * {@link #quit()}.
   * 交給用戶自己調(diào)用,通過(guò)loop()方法開(kāi)啟消息循環(huán).同時(shí)當(dāng)不需要處理消息時(shí),需要手動(dòng)調(diào)用quit()方法退出循環(huán).
   */
  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));
  }

在這兩個(gè)函數(shù)調(diào)用的過(guò)程中,sThreadLocal變量都有被使用。這個(gè)變量是ThreadLocal類(lèi)型的,用來(lái)保存當(dāng)前線程中的Looper對(duì)象。也就是說(shuō)在Android應(yīng)用程序中每創(chuàng)建一個(gè)消息隊(duì)列,都有一個(gè)并且是唯一 一個(gè)與之對(duì)應(yīng)的Looper對(duì)象。而且我們可以從源碼中看到當(dāng)對(duì)象不唯一時(shí)就會(huì)拋出異常。

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed); //創(chuàng)建消息隊(duì)列
    mThread = Thread.currentThread();
  }

從上面的源碼中可以看到,當(dāng)Looper對(duì)象實(shí)例化的過(guò)程的同時(shí)會(huì)創(chuàng)建一個(gè)消息隊(duì)列。

消息循環(huán)過(guò)程

在消息隊(duì)列建立完成之后,調(diào)用Looper對(duì)象的靜態(tài)成員方法loop()就開(kāi)始了消息循環(huán)。

  /**
   * Run the message queue in this thread. Be sure to call
   * {@link #quit()} to end the loop.
   */
  public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
      throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;

    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    for (;;) { //開(kāi)始消息循環(huán)
      Message msg = queue.next(); // 在接收消息時(shí)有可能阻塞
      if (msg == null) {
        //message為null時(shí),退出消息循環(huán)
        return;
      }

      // This must be in a local variable, in case a UI event sets the logger
      final Printer logging = me.mLogging;
      if (logging != null) {...}

      final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;

      final long traceTag = me.mTraceTag;
      if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {...}
      final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
      final long end;
      try {
        msg.target.dispatchMessage(msg); //處理消息
        end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
      } finally {
        if (traceTag != 0) {
          Trace.traceEnd(traceTag);
        }
      }
      if (slowDispatchThresholdMs > 0) {
        final long time = end - start;
        if (time > slowDispatchThresholdMs) {...}
      }

      if (logging != null) {...}

      // Make sure that during the course of dispatching the
      // identity of the thread wasn't corrupted.
      final long newIdent = Binder.clearCallingIdentity();
      if (ident != newIdent) {...}
      msg.recycleUnchecked();
    }
  }

上面的源碼就是消息循環(huán)的過(guò)程,只用調(diào)用了loop()方法消息循環(huán)才開(kāi)始起作用。當(dāng)循環(huán)開(kāi)始時(shí):

  • 獲取當(dāng)前線程的Looper對(duì)象,如果為null,拋出異常;
  • 獲取消息隊(duì)列,開(kāi)始進(jìn)入消息循環(huán);
  • 從消息隊(duì)列中獲取消息(調(diào)用MessageQueue的next()方法),如果為null,結(jié)束循環(huán);否則,繼續(xù)執(zhí)行;
  • 處理消息,回收消息資源( msg.recycleUnchecked())。

在消息循環(huán)過(guò)程中,通過(guò)MessageQueue的next()方法提供消息,在沒(méi)有信息時(shí)進(jìn)入睡眠狀態(tài),同時(shí)處理其他接口。這個(gè)過(guò)程至關(guān)重要,通過(guò)next()方法也決定了消息循環(huán)是否退出。

 Message next() {
    final long ptr = mPtr; //與native方法相關(guān),當(dāng)mPtr為0時(shí)返回null,退出消息循環(huán)
    if (ptr == 0) {
      return null;
    }

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0; //0不進(jìn)入睡眠,-1進(jìn)入書(shū)面
    for (;;) {
      if (nextPollTimeoutMillis != 0) {
        //處理當(dāng)前線程中待處理的Binder進(jìn)程間通信請(qǐng)求
        Binder.flushPendingCommands(); 
      }
      //native方法,nextPollTimeoutMillis為-1時(shí)進(jìn)入睡眠狀態(tài)
      nativePollOnce(ptr, nextPollTimeoutMillis); 
      synchronized (this) {
        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; //更新到睡眠狀態(tài)
        }

        // Process the quit message now that all pending messages have been handled.
        //消息循環(huán)退出
        if (mQuitting) {
          dispose();
          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.
        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;
          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.
      //非睡眠狀態(tài)下處理IdleHandler接口
      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;
    }
  }

消息循環(huán)退出過(guò)程

從上面可以看到loop()方法是一個(gè)死循環(huán),只有當(dāng)MessageQueue的next()方法返回null時(shí)才會(huì)結(jié)束循環(huán)。那么MessageQueue的next()方法何時(shí)為null呢?
在Looper類(lèi)中我們看到了兩個(gè)結(jié)束的方法quit()和quitSalely()。兩者的區(qū)別就是quit()方法直接結(jié)束循環(huán),處理掉MessageQueue中所有的消息,而quitSafely()在處理完消息隊(duì)列中的剩余的非延時(shí)消息(延時(shí)消息(延遲發(fā)送的消息)直接回收)時(shí)才退出。這兩個(gè)方法都調(diào)用了MessageQueue的quit()方法。

void quit(boolean safe) {
    if (!mQuitAllowed) {
      throw new IllegalStateException("Main thread not allowed to quit.");
    }

    synchronized (this) {
      if (mQuitting) {
        return;
      }
      mQuitting = true; //設(shè)置退出狀態(tài)

      //處理消息隊(duì)列中的消息
      if (safe) {
        removeAllFutureMessagesLocked(); //處理掉所有延時(shí)消息
      } else {
        removeAllMessagesLocked(); //處理掉所有消息
      }

      // We can assume mPtr != 0 because mQuitting was previously false.
      nativeWake(mPtr); // 喚醒消息循環(huán)
    }
  }

處理消息隊(duì)列中的消息:根據(jù)safe標(biāo)志選擇不同的處理方式。

  /**
   * API Level 1
   * 處理掉消息隊(duì)列中所有的消息
   */
  private void removeAllMessagesLocked() {
    Message p = mMessages;
    while (p != null) {
      Message n = p.next;
      p.recycleUnchecked(); //回收消息資源
      p = n;
    }
    mMessages = null;
  }

  /**
   * API Level 18
   * 處理掉消息隊(duì)列中所有的延時(shí)消息
   */
  private void removeAllFutureMessagesLocked() {
    final long now = SystemClock.uptimeMillis();
    Message p = mMessages;
    if (p != null) {
      if (p.when > now) {
        removeAllMessagesLocked();
      } else {
        Message n;
        for (;;) {
          n = p.next;
          if (n == null) {
            return;
          }
          if (n.when > now) {
            //找出延時(shí)消息
            break;
          }
          p = n;
        }
        p.next = null;
        //由于在消息隊(duì)列中按照消息when(執(zhí)行時(shí)間排序,所以在第一個(gè)延時(shí)消息后的所有消息都是延時(shí)消息)
        do {
          p = n;
          n = p.next;
          p.recycleUnchecked(); //回收消息資源
        } while (n != null);
      }
    }
  }

消息發(fā)送過(guò)程

在Android應(yīng)用程序中,通過(guò)Handler類(lèi)向線程的消息隊(duì)列發(fā)送消息。在每個(gè)Handler對(duì)象中持有一個(gè)Looper對(duì)象和MessageQueue對(duì)象。

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(); //獲取Looper對(duì)象
    if (mLooper == null) {...}
    mQueue = mLooper.mQueue; //獲取消息隊(duì)列
    mCallback = callback;
    mAsynchronous = async;
  }

在Handler類(lèi)中,我們可以看到多種sendMessage方法,而它們最終都調(diào)用了同一個(gè)方法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;
    }
    //向消息隊(duì)列中添加消息
    return enqueueMessage(queue, msg, uptimeMillis); 
  }
  
  private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
      msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
  }

可以看出這兩個(gè)方法十分容易理解,就是通過(guò)MessageQueue對(duì)象調(diào)用enqueueMessage()方法向消息隊(duì)列中添加消息。

boolean enqueueMessage(Message msg, long when) {
    // Handler為null
    if (msg.target == null) {
      throw new IllegalArgumentException("Message must have a target.");
    }
    //消息已經(jīng)被消費(fèi)
    if (msg.isInUse()) {
      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.when = when;
      Message p = mMessages;
      boolean needWake;
      if (p == null || when == 0 || when < p.when) {
        // New head, wake up the event queue if blocked.
        // 隊(duì)列沒(méi)有消息,直接加入
        msg.next = p;
        mMessages = msg;
        needWake = mBlocked;
      } 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();
        Message prev;
        for (;;) {
          prev = p;
          p = p.next;
          // 根據(jù)執(zhí)行時(shí)間插入到相應(yīng)位置
          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); //喚醒消息循環(huán)
      }
    }
    return true;
  }

從源碼可以看出,一個(gè)消息插入到消息隊(duì)列中需要以下步驟:

消息持有的Handler對(duì)象為null,拋出異常;當(dāng)消息已經(jīng)被消費(fèi),拋出異常;
當(dāng)消息隊(duì)列沒(méi)有消息時(shí),直接插入;
當(dāng)消息隊(duì)列存在消息時(shí),通過(guò)比較消息的執(zhí)行時(shí)間,將消息插入到相應(yīng)的位置;
判斷是否需要喚醒消息循環(huán)。

消息處理過(guò)程

在消息循環(huán)過(guò)程中,如果有新的消息加入,就開(kāi)始處理消息。從上面的分析中,我們可以看到在消息循環(huán)中,目標(biāo)消息會(huì)調(diào)用其Handler對(duì)象的dispatchMessage()方法,這個(gè)就是處理消息的方法。

 /**
   * Handle system messages here.
   */
  public void dispatchMessage(Message msg) {
    // 消息Callback接口不為null,執(zhí)行Callback接口
    if (msg.callback != null) {
      handleCallback(msg);
    } else {
      if (mCallback != null) {
        //Handler Callback接口不為null,執(zhí)行接口方法
        if (mCallback.handleMessage(msg)) {
          return;
        }
      }
      handleMessage(msg); //處理消息
    }
  }

從源碼可以看出,Handler處理消息分為3中情況。

  1. 當(dāng)Message中的callback不為null時(shí),執(zhí)行Message中的callback中的方法。這個(gè)callback時(shí)一個(gè)Runnable接口。
  2. 當(dāng)Handler中的Callback接口不為null時(shí),執(zhí)行Callback接口中的方法。
  3. 直接執(zhí)行Handler中的handleMessage()方法。

當(dāng)Looper開(kāi)始調(diào)用loop()時(shí)主線程為什么不會(huì)卡死

在進(jìn)行完上面的分析后,我們都知道Looper.loop()進(jìn)入到了一個(gè)死循環(huán),那么在主線程中執(zhí)行這個(gè)死循環(huán)為什么沒(méi)有造成主線程卡死或者說(shuō)在主線程中的其他操作還可以順利的進(jìn)行,比如說(shuō)UI操作。因?yàn)锳ndroid應(yīng)用程序是通過(guò)消息驅(qū)動(dòng)的,所以Android應(yīng)用程序的操作也是通過(guò)Android的消息機(jī)制來(lái)實(shí)現(xiàn)的。這個(gè)時(shí)候就需要分析一下Android程序啟動(dòng)的入口類(lèi)ActivityThread。我們都知道當(dāng)Android程序啟動(dòng)時(shí)在Java層就是以ActivityThread的main()方法為入口的,這也就是我們所說(shuō)的主線程。

public static void main(String[] args) {
    ...
    ...
    ...
    Looper.prepareMainLooper();

    ActivityThread thread = new ActivityThread();
    thread.attach(false); //建立Binder通道 (創(chuàng)建新線程),與ActivityManagerService建立鏈接

    if (sMainThreadHandler == null) {
      sMainThreadHandler = thread.getHandler();
    }
    ...
    ...
    ...
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
  }

從ActivityThread的main()方法中我們可以看到Looper的初始化以及消息循環(huán)的開(kāi)始,同時(shí)還有一個(gè)關(guān)鍵的方法attach()與ActivityManagerService建立鏈接,這里建立鏈接是為了之后相應(yīng)Activity中各種事件的發(fā)生。講到這里還涉及到Native層Looper的初始化,在Looper初始化時(shí)會(huì)建立一個(gè)管道來(lái)維護(hù)消息隊(duì)列的讀寫(xiě)并通過(guò)epoll機(jī)制監(jiān)聽(tīng)讀寫(xiě)事件(一種IO多路復(fù)用機(jī)制)。

  • 當(dāng)沒(méi)有新消息需要處理時(shí),主線程就會(huì)阻塞在管道上,直到有新的消息需要處理;
  • 當(dāng)其他線程有消息發(fā)送到消息隊(duì)列時(shí)會(huì)通過(guò)管道來(lái)寫(xiě)數(shù)據(jù);

在我們調(diào)試程序時(shí),我們通過(guò)函數(shù)的調(diào)用棧就可以發(fā)現(xiàn)其中的道理:

這也印證了開(kāi)始的那句話——Android應(yīng)用程序是通過(guò)消息來(lái)驅(qū)動(dòng)的。

是否所有的消息都會(huì)在指定時(shí)間開(kāi)始執(zhí)行

這個(gè)問(wèn)題的意思是當(dāng)我們像消息隊(duì)列中發(fā)送消息時(shí)(比如延時(shí)1000ms執(zhí)行一個(gè)消息postDelay(action, 1000)),是不是會(huì)在1000ms后去執(zhí)行這個(gè)消息。
答案是不一定。我們只Android的消息是按照時(shí)間順序保存在消息隊(duì)列中的,如果我們向隊(duì)列中添加多個(gè)消息,比如10000個(gè)延時(shí)1000ms執(zhí)行的消息,那么其實(shí)最后一個(gè)執(zhí)行的消息和第一個(gè)執(zhí)行的消息的執(zhí)行時(shí)間是不一樣的。

總結(jié)

至此Android系統(tǒng)的消息處理機(jī)制就分析完畢了。在Android應(yīng)用程序中消息處理主要分為3個(gè)過(guò)程:

  1. 啟動(dòng)Looper中的消息循環(huán),開(kāi)始監(jiān)聽(tīng)消息隊(duì)列。
  2. 通過(guò)Handler發(fā)送消息到消息隊(duì)列。
  3. 通過(guò)消息循環(huán)調(diào)用Handler對(duì)象處理新加入的消息。

在使用消息隊(duì)列時(shí),主線程中在程序啟動(dòng)時(shí)就會(huì)創(chuàng)建消息隊(duì)列,所以我們使用主線程中的消息機(jī)制時(shí),不需要初始化消息循環(huán)和消息隊(duì)列。在子線程中我們需要初始化消息隊(duì)列,并且注意在不需要使用消息隊(duì)列時(shí),應(yīng)該及時(shí)調(diào)用Looper的quit或者quitSafely方法關(guān)閉消息循環(huán),否則子線程可能一直處于等待狀態(tài)。

以上就是詳解Android 消息處理機(jī)制的詳細(xì)內(nèi)容,更多關(guān)于Android 消息處理機(jī)制的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Android獲取手機(jī)信息的工具類(lèi)

    Android獲取手機(jī)信息的工具類(lèi)

    這篇文章主要為大家詳細(xì)介紹了Android獲取手機(jī)信息的工具類(lèi),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-09-09
  • C#中利用正則表達(dá)式將人民幣金額轉(zhuǎn)換為大寫(xiě)漢字

    C#中利用正則表達(dá)式將人民幣金額轉(zhuǎn)換為大寫(xiě)漢字

    這篇文章主要介紹了C#中利用正則表達(dá)式將人民幣金額轉(zhuǎn)換為大寫(xiě)漢字的方法,需要的朋友可以參考下
    2016-02-02
  • Android多渠道打包神器ProductFlavor詳解

    Android多渠道打包神器ProductFlavor詳解

    最近一直在學(xué)習(xí)Android Gradle 相關(guān)的知識(shí)點(diǎn),今天剛好看到了 ProductFlavor 這節(jié),ProductFlavor 的出現(xiàn)非常友好的幫助我們開(kāi)發(fā)者解決了版本區(qū)分的問(wèn)題
    2022-07-07
  • Android 單例模式的四種實(shí)現(xiàn)方式

    Android 單例模式的四種實(shí)現(xiàn)方式

    單例模式作為設(shè)計(jì)模式之一,使用場(chǎng)景非常多。本文講述了Android實(shí)現(xiàn)單例模式的幾種方式
    2021-05-05
  • Android應(yīng)用中使用ViewPager實(shí)現(xiàn)類(lèi)似QQ的界面切換效果

    Android應(yīng)用中使用ViewPager實(shí)現(xiàn)類(lèi)似QQ的界面切換效果

    這篇文章主要介紹了Android應(yīng)用中使用ViewPager實(shí)現(xiàn)類(lèi)似QQ的界面切換效果的示例,文中的例子重寫(xiě)了PagerAdapter,并且講解了如何解決Android下ViewPager和PagerAdapter中調(diào)用notifyDataSetChanged失效的問(wèn)題,需要的朋友可以參考下
    2016-03-03
  • Android開(kāi)發(fā)之完全隱藏軟鍵盤(pán)的方法

    Android開(kāi)發(fā)之完全隱藏軟鍵盤(pán)的方法

    這篇文章主要介紹了Android開(kāi)發(fā)之完全隱藏軟鍵盤(pán)的方法的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2016-06-06
  • Android實(shí)現(xiàn)文字消除效果

    Android實(shí)現(xiàn)文字消除效果

    由于項(xiàng)目和語(yǔ)音識(shí)別相關(guān),有時(shí)候人在不經(jīng)意間交流的無(wú)效音頻會(huì)被識(shí)別出來(lái),并展示于界面,為了美觀,客戶要求我們將這些無(wú)效的識(shí)別文本用一個(gè)從右到左的動(dòng)畫(huà)給清除,于是便有了下述的技術(shù)實(shí)現(xiàn)。感興趣的朋友可以參考下
    2021-06-06
  • Android中EditText setText方法的踩坑實(shí)戰(zhàn)

    Android中EditText setText方法的踩坑實(shí)戰(zhàn)

    這篇文章主要給大家分享了一些關(guān)于Android中EditText setText方法的踩坑記錄,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)各位Android開(kāi)發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-07-07
  • Android條目拖拽刪除功能實(shí)例代碼

    Android條目拖拽刪除功能實(shí)例代碼

    最近做項(xiàng)目遇到這樣的需求,要做條目條目拖拽刪除效果,實(shí)際效果和QQ消息刪除一樣,側(cè)滑有制定和刪除,下面通過(guò)本文給大家分享Android條目拖拽刪除功能,需要的朋友參考下吧
    2017-08-08
  • Android?WebView預(yù)渲染介紹

    Android?WebView預(yù)渲染介紹

    這篇文章主要介紹了Android?WebView預(yù)渲染介紹,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-09-09

最新評(píng)論