我的编程空间,编程开发者的网络收藏夹
学习永远不晚

源码分析Android的消息机制

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

源码分析Android的消息机制

一、引言

​Android消息机制主要指的是Handler的运行机制,是一块很有意思,也很有研究意义的内容。本文计划在较短的篇幅内,通过一定的源码,分析Android消息机制,并在结尾说点”题外话“,帮助我们理解消息机制在安卓应用中的作用。

二、Android消息队列与四大组件的关系

​Android的消息机制伴随着安卓四大组件的生命周期,Activity启动,Service绑定与注册,Broadcast广播的发送,ContentProvider的启动等过程,先是依靠Binder将主线程切到Binder线程池的线程,系统完成组件对象的创建、绑定以及其他的准备工作后,再通过消息机制将线程切回至主线程,在主线程调用组件的生命周期方法。例如Activity、Service的onCreate(),ServiceConnection的onServiceConnected()等方法,就是系统通过发送消息给消息队列,由消息队列回调在使得这些方法运行在主线程。

​另外由于Android开发规范的限制,我们并不能在子线程中访问UI控件,通过主线程(也就是UI线程)的Handler,可以将更新UI的操作切换到主线程中执行。

如果我们要用一句话来总结安卓消息机制的作用:极大地简化在子线程中访问主线程操作,更容易地在主线程中控制四大组件的生命周期,也使得UI线程对于用户的响应更加高效、流畅。

三、Android消息机制分析

​在引言我们说了,消息机制主要指的是Handler的运行机制,而

Handler
运行需要底层的
MessageQueue
Looper
的支撑,那么这三者的关系如何呢?相互之间是怎么配合的?这里我们先给出结论:Handler发送消息到MessageQueue中存储,Looper从MessageQueue中取出消息并执行。 接下来我们逐步通过源码进行分析。

3.1 MessageQueue

​MessageQueue,也就是消息队列,主要包含两个操作:插入和读取。插入和读取对应的方法分别为

enqueueMessage
next
,其中读取操作同时还伴随着消息的移除操作。我们说消息队列,然而MessageQueue的内部并不是通过队列来存储消息的,为了提高消息插入和删除的效率,实际上MessageQueue是通过单链表的形式来维护消息列表的,我们来看看enqueueMeesage()的源码:

3.1.1 enqueueMessage
boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {	// target就是此消息的接收方
        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.when = when;			// 消息的延迟时间
        Message p = mMessages;// 单链表消息队列的头部,也就是第一个消息
        boolean needWake;			// 是否需要唤醒线程
        // 如果消息队列为空(p = null),或者新消息是一个即时消息
        if (p == null || when == 0 || when < p.when) {
            // 把msg作为消息队列的新头部,第一个执行
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;	// 如果mBlocked = true,线程阻塞了,就需要唤醒线程
        } else {
            // 向单链表插入一条延时消息:根据消息的延时时间when,将消息插入链表一个合适的位置
          	// 原理是when越小,消息所处的位置越靠前
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
              	// 找到链表中第一个小于when的位置
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
          	// 插入new msg
            msg.next = p; 
            prev.next = msg;
        }
        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {	
            nativeWake(mPtr);		// 唤醒线程
        }
    }
    return true;
}

​通过源码中的注释我们已经能够理解enqueueMessage的插入过程,接下来我们看next():

3.1.2 next()
Message next() {
    // 当调用Looper的quit()方法后,即Looper停止后,mPtr为0
    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();
        }
      	// 阻塞方法,nextPollTimeoutMillis为最长阻塞时间
      	// nextPollTimeoutMillis = 0, 不阻塞,立即往下执行
      	// nextPollTimeoutMillis = -1, 一直阻塞,不会超时
      	// nextPollTimeoutMillis > 0,  最长阻塞nextPollTimeoutMillis(ms),如果期间线程被唤醒,立即往下执行
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            // 手机开机到现在的时间
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                // msg.target = null 说明此消息是通过postSyncBarrier()发送的消息,是个同步屏障
              	// looper遇到同步屏障后会忽略同步消息,只处理异步消息
              	// 同步屏障实际上是让消息机制优先处理异步消息
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    // 当前这个消息还没到指定的处理时间时,继续延迟,剩余延迟时间 msg.when - now
                    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 {
                // 单链表内没有消息
                nextPollTimeoutMillis = -1;
            }
            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {
                dispose();
                return null;
            }
          	// 如果执行到这里,说明单链表中没有消息,接下来处理一些不紧急的任务(Idle Handler)
            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.
        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;
    }
}

​可以看到next()是一个无线循环的方法,如果队列中没有消息,那么next()会一直阻塞在这里。当有新消息到来时,next()方法会返回这条消息并将其从单链表中移除。

3.2 Looper

​我们分析了MessageQueue主要是对消息进行插入和移除,分别使用enqueueMessage()和next()方法,那谁来调用消息队列的这些方法呢,其实就是Looper,Looper在消息机制中扮演着消息循环的角色,具体来说就是它会不停地从MessageQueue中查看是否有新消息,如果有新消息就会立刻处理,否则就一直阻塞在那里。

​Looper和MessageQueue关系很紧密,在Looper的构造方法中,它会创建一个MessageQueue对象,并将该对象保存起来:

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

​可以看到,作为Looper中唯一的一个构造方法,这个方法居然是

private
的,仅允许类内部创建它的实例。我们如果要创建一个Looper,需要调用它的prepare()方法,Looper除了prepare()以外,还提供了prepareMainLooper(),这个方法主要是给主线程(ActivityThread)创建Looper使用的,但本质上还是调用的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));
}

​这里涉及到一个ThreadLocal对象,它并不是线程,它的作用是可以在每个线程中存储数据,不同线程的数据互不干扰。在消息机制中,ThreadLocal就存储了在不同线程中创建的Looper对象,这些对象是互不干扰的,我们通过ThreadLocal.get()方法就可以得到当前线程中的Looper对象。关于当前属于哪个线程的判断,由get()方法内部处理,这里我们不对ThreadLocal做过多的分析。

​同时,我们可以看出,在一个线程中只有一个Looper对象,重复创建会报运行时异常。Looper对象只会创建一次,因此在Looper构造方法内部中的MessageQueue对象也只有一个,而Handler是可以多次创建的。

​接下来我们看看Looper中最重要的loop()方法,只有调用了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.");
    }
  	// mQueue对象
    final MessageQueue queue = me.mQueue;
    // ...
    for (;;) {
        // 调用mQueue.next()读取消息队列中的消息,没有新消息的话就会堵塞在这
        Message msg = queue.next(); // might block
      	// loop()方法唯一出口
        if (msg == null) {
            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);
        }
        // ....
        try {
          	// 在Looper中调用Handler的dispatchMessage()方法,使得将代码逻辑切换到Looper所在线程中执行
            msg.target.dispatchMessage(msg);
            if (observer != null) {
                observer.messageDispatched(token, msg);
            }
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } catch (Exception exception) {
            if (observer != null) {
                observer.dispatchingThrewException(token, msg, exception);
            }
            throw exception;
        } finally {
            ThreadLocalWorkSource.restore(origWorkSource);
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
      	// ....
    }
}

​Looper的loop方法也很好理解,我们省去一些不必要的代码。可以看出,loop()实际上也是一个死循环,唯一跳出循环的方式就是MessageQueue的next()方法返回null。当Looper的退出方法被调用时,Looper就会调用MessageQueue的quit(boolean)方法,将队列的mQuitting变量置为true,这样当MessageQueue执行下一次循环的时候会发现mQuiting标志为true,进而使得next()返回null,Looper的loop()也随之跳出循环结束。读者可以结合MessageQueue的next()源码,更容易理解loop的退出过程。

​Looper提供了两个退出方法:quit()和quitSafely()。二者的区别是,quit()会直接退出Looper,而quitSafely只是设定一个退出标记,然后把消息队列中已有的消息处理完毕后才安全的退出。Looper退出后,通过Handler发送的消息会失败,这个时候Handler的send()方法会返回false。

在子线程中,如果我们手动为其创建了Looper,那么在所有的事情完成以后应该调用quit()方法来终止消息循环,否则这个子线程会一直处于等待状态,而如果退出Looper后,这个线程就会立刻终止,所以建立在不需要Looper的时候终止它,减少不必要的线程开支。

​再回头看看我们loop()的源码,里面还有一句

msg.target.dispatchMessage(msg)
,msg.target 就是我们的Handler对象,我们在Looper中调用Handler.dispatchMessage()方法,那么Looper在哪个线程中创建,dispatchMessage()就会运行在哪个线程中,这样,一个消息取出后,就被切换到指定的线程中处理了。

3.3 Handler

​在分析Handler工作前,我们先来看看Handler有哪些构造方法:

public Handler() {														// 无参构造方法,默认是同步Handler
  	this(null, false);
}
public Handler(@Nullable Callback callback) {	// 指定一个callback
    this(callback, false);
}
public Handler(@NonNull Looper looper) {			// 指定一个looper,一般在子线程中调用
    this(looper, null, false);
}
public Handler(@NonNull Looper looper, @Nullable Callback callback) {	// 指定looper, callback
    this(looper, callback, false);
}
public Handler(boolean async) {								// async = true,创建一个处理异步消息的Handler
    this(null, async);
}
public Handler(@Nullable Callback callback, boolean async) {		// 用当前线程的Looper来创建Handler
    if (FIND_POTENTIAL_LEAKS) {
        final Class 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;
}
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

​从上面这些构造方法,我们可以自己指定Looper对象来创建Handler,可以创建一个专门处理发送异步消息的Handler,同时我们也可以给Handler设置Callback回调来创建Handler,如果调用的是无参构造方法,那么系统就使用当前线程的Looper对象来创建一个Handler对象,一个线程可以创建多个Handler对象。

​Handler的工作主要是消息的发送和接受。消息的发送可以通过post的一系列方法以及send的一系列方法来实现,大部分的发送方法,它们最终都会来到

sendMessageAtTime()
,我们来看看它的源码:

public boolean sendMessageAtTime(@NonNull 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);
}

​其内部调用了enqueueMessage(),我们跟进一下看看:

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;		// 同步消息,target指向自己
    msg.workSourceUid = ThreadLocalWorkSource.getUid();
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

​在enqueueMessage()内部,我们给msg设置了target,就是当前的Handler对象,同时调用mQueue的enqueueMessage()方法,将消息插入到消息队列中。

3.3.1 dispatchMessage()

​Looper收到消息后最终会调用Handler的dispatchMessage()进行处理,这时Handler就进入了处理消息的阶段,我们来看看dispatchMessage的具体实现:

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

​首先,检查Message的callback是否为null,不为null就通过handleCallback来处理消息,Message的callback是一个Runnable对象,实际上就是Handler的post()方法所传递Runnable参数。

public final boolean post(@NonNull Runnable r) {
   return  sendMessageDelayed(getPostMessage(r), 0);
}

handleCallback(msg)
的内部实现也很简单,就是调用Runnable的run()方法,这里就不贴代码了。

​接下来检查mCallback是否为空,这个mCallback就是我们创建Handler时可选的Callback对象,Callback是个接口,里面只有handleMessage()一个待实现的方法:

public interface Callback {
        boolean handleMessage(@NonNull Message msg);
}

​值得注意的是,如果mCallback.handleMessage(msg)返回的是false,那么还是会执行到Handler的handleMessage()方法。具体怎么利用方法的返回值,就要看具体的使用场景了,一般很少有这两个方法同时使用的场景。

实际上,通过Callback,我们可以采用如下方式来创建Handler对象:Handler handler = new Handler(callback)。那么这样做的意义是什么呢?在日常开发中,创建Handler最常见的方式就是派生一个Handler的子类,并重写其handlerMessage()方法来处理具体的消息,而Callback给我们提供了另一种使用Handler的方式,当我们不想派生子类时,就可以通过Callback来实现。

​我们将Handler处理消息个过程可以总结为一个流程图,如下所示:

原创文章 3获赞 1访问量 95 关注 私信 展开阅读全文
作者:ANGOPENEDD_


免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

源码分析Android的消息机制

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

下载Word文档

猜你喜欢

源码分析Android的消息机制

一、引言 ​Android消息机制主要指的是Handler的运行机制,是一块很有意思,也很有研究意义的内容。本文计划在较短的篇幅内,通过一定的源码,分析Android消息机制,并在结尾说点”题外话“,帮助我们理解消息机制在安卓应用中的作用。
2022-06-06

Android Handler消息派发机制源码分析

注:这里只是说一下sendmessage的一个过程,post就类似的 如果我们需要发送消息,会调用sendMessage方法public final boolean sendMessage(Message msg) {return send
2022-06-06

Android中消息机制分析

本文中的源码基于Android 29; 一、概述 对于Android开发者而言,我们处理异步消息用的最多的也是轻车熟路的一种方式,就是使用Handler进行消息的分发和处理。但是我们在一个页面(Activity 或者 Fragment)中可
2022-06-06

android的消息处理机制(图文+源码分析)—Looper/Handler/Message

这篇文章写的非常好,深入浅出,关键还是一位大三学生自己剖析的心得。这是我喜欢此文的原因。下面请看正文: 作为一个大三的预备程序员,我学习android的一大乐趣是可以通过源码学习google大牛们的设计思想。android源码中包含了大量的
2022-06-06

RocketMQ源码分析之Broker过期消息清理机制

这篇文章主要为大家介绍了RocketMQ源码分析之Broker过期消息清理机制示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-05-19

Android消息机制原理深入分析

这篇文章主要介绍了Android消息机制原理,Android的消息机制主要是指Handler的运行机制以及Handler所附带的MessageQueue和Looper的工作过程
2022-12-09

Android消息循环机制源码深入理解

Android消息循环机制源码前言:搞Android的不懂Handler消息循环机制,都不好意思说自己是Android工程师。面试的时候一般也都会问这个知识点,但是我相信大多数码农肯定是没有看过相关源码的,顶多也就是网上搜搜,看看别人的文章
2023-05-31

Android编程之消息机制实例分析

本文实例讲述了Android编程之消息机制。分享给大家供大家参考,具体如下: 一、角色描述 1.Looper: 一个线程可以产生一个Looper对象,由它来管理此线程里的Message Queue(消息队列)。 2.Handler: 你可以
2022-06-06

Android的消息机制

一、简介 Android的消息机制主要是指Handler的运行机制,那么什么是Handler的运行机制那?通俗的来讲就是,使用Handler将子线程的Message放入主线程的Messagequeue中,在主线程使用。 二、学习内容 学习A
2022-06-06

深入浅析Android消息机制

在Android中,线程内部或者线程之间进行信息交互时经常会使用消息,这些基础的东西如果我们熟悉其内部的原理,将会使我们容易、更好地架构系统,避免一些低级的错误。 每一个Android应用在启动的时候都会创建一个线程,这个线程被称为主线程或
2022-06-06

RocketMQ消息存储文件的加载与恢复机制源码分析

这篇文章主要介绍了RocketMQ源码分析之消息存储文件的加载与恢复机制详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-05-19

【Android】事件分发机制源码解析

文章目录1. 分发顺序2.源码分析2.1 Activity中的分发流程dispatchTouchEventonTouchEvent总结2.2 ViewGroup中的分发流程dispatchTouchEventonInterceptTouch
2022-06-06

深入浅析Android中的消息机制

本篇文章给大家分享的是有关深入浅析Android中的消息机制,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。一、简介Android的消息机制主要是指Handler的运行机制,那么
2023-05-31

Android源码分析——ViewGroup的事件分发机制(二)

通过前一篇博客View的事件分发机制,从dispatchTouchEvent说起(一)的介绍相信大家对 Android View 事件的分发机制有了很深的理解。我们知道 Android 中 View 是存在于 Activity。 今天我们继
2022-06-06

CocosCreator消息分发机制的示例分析

这篇文章将为大家详细讲解有关CocosCreator消息分发机制的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。概述本篇开始介绍游戏业务架构相关的内容。在游戏业务层,所有需要隔离的系统和模块间通信
2023-06-14

深入剖析Android消息机制原理

在Android中,线程内部或者线程之间进行信息交互时经常会使用消息,这些基础的东西如果我们熟悉其内部的原理,将会使我们容易、更好地架构系统,避免一些低级的错误。在学习Android中消息机制之前,我们先了解与消息有关的几个类:1.Mess
2022-06-06

RocketMQ 源码分析Broker消息刷盘服务

这篇文章主要为大家介绍了RocketMQ 源码分析Broker消息刷盘服务示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-05-19

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录