当前位置: 首页 > news >正文

沈阳网站制作公司排名青田县建设局网站

沈阳网站制作公司排名,青田县建设局网站,无极在线网站播放,公司找人做网站需要什么文章目录 前言一、Handler源码分析1、创建Handler2、发送消息3、取消息4、消息处理5、线程切换的方法#xff08;Handler异步消息处理机制流程#xff09;handler.sendMessage()handler.post()View.post()Activity中的runOnUiThread() 二、Handler高频面试题1、为什么要有Han… 文章目录 前言一、Handler源码分析1、创建Handler2、发送消息3、取消息4、消息处理5、线程切换的方法Handler异步消息处理机制流程handler.sendMessage()handler.post()View.post()Activity中的runOnUiThread() 二、Handler高频面试题1、为什么要有Handler2、为什么要有MessageQueue3、为什么要有Looper4、主线程的Looper和子线程Looper有什么不同5、一个线程可以有几个Handler几个looper6、主线程会为什么会一直阻塞7、ANR是什么发生条件8、Looper的死循环为什么不会让主线程卡死或ANR9、为什么Handler会造成内存泄露10、内存抖动如何解决11、安卓中的Looper.loop()阻塞为什么不会有问题 前言 线程间的通信两个线程使用公共的变量或者公共的其他东西都可以进行通信但是这种方式不是自主的不能够自主切换线程执行所以Handler的最终目的是为了线程间的切换线程异步消息处理 Android UI操作并不是线程安全的并且这些操作必须在UI线程执行。 如果非要在子线程中更新UI那会出现什么情况呢 android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.很容易抛一个CalledFromWrongThreadException异常。 如果在子线程访问UI线程Android提供了以下的方式 Activity.runOnUiThread(Runnable) View.post(Runnable) View.postDelayed(Runnable, long) Handler一、Handler源码分析 1、创建Handler 创建两个Handler对象一个在主线程中创建一个在子线程中创建代码如下所示 public class MainActivity extends AppCompatActivity {private Handler handler1;private Handler handler2;Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);handler1 new Handler();new Thread(new Runnable() {Overridepublic void run() {handler2 new Handler();}}).start();} }运行程序你会发现在子线程中创建的HandlerHandler2是会导致程序崩溃的 我们尝试在子线程中先调用一下Looper.prepare()运行程序成功不再报运行时异常 那这加上Looper.prepare()运行成功是为什么呢此时我们分析一下Handler(基于android13 API 33)源码 在第224行调用了Looper.myLooper()方法获取了一个Looper对象如果Looper对象为空则会抛出一个运行时异常。也就是我们上述出现的异常。 什么时候Looper对象会为空呢接着看Looper.myLooper()中的代码 sThreadLocal是一个关于Looper的ThreadLocal类 接着查找sThreadLocal查看是在哪里给sThreadLocal设置Looper发现是Looper.prepare()方法 可以看到首先判断sThreadLocal中是否存在Looper如果没有则创建一个新的Looper设置进去。这样也就完全解释了为什么我们要先调用Looper.prepare()方法才能创建Handler对象。同时也可以看出每个线程中最多只会有一个Looper对象。创建一个Looper对象会创建相应的MessageQueue并且获取当前线程故Thread——Looper——MessageQueue是唯一对应的 所以Looper.prepare()的作用是创建一个新的Looper对象并设置到sThreadLocal中 Q主线程中的Handler也没有调用Looper.prepare()方法为什么就没有崩溃呢 这是由于在程序启动的时候系统已经帮我们自动调用了Looper.prepare()方法。查看ActivityThread中的main()方法代码如下所示 public static void main(String[] args) {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ActivityThreadMain);// Install selective syscall interceptionAndroidOs.install();// CloseGuard defaults to true and can be quite spammy. We// disable it here, but selectively enable it later (via// StrictMode) on debug builds, but using DropBox, not logs.CloseGuard.setEnabled(false);Environment.initForCurrentUser();// Make sure TrustedCertificateStore looks in the right place for CA certificatesfinal File configDir Environment.getUserConfigDirectory(UserHandle.myUserId());TrustedCertificateStore.setDefaultUserDirectory(configDir);// Call per-process mainline module initialization.initializeMainlineModules();Process.setArgV0(pre-initialized);Looper.prepareMainLooper();// Find the value for {link #PROC_START_SEQ_IDENT} if provided on the command line.// It will be in the format seq114long startSeq 0;if (args ! null) {for (int i args.length - 1; i 0; --i) {if (args[i] ! null args[i].startsWith(PROC_START_SEQ_IDENT)) {startSeq Long.parseLong(args[i].substring(PROC_START_SEQ_IDENT.length()));}}}ActivityThread thread new ActivityThread();thread.attach(false, startSeq);if (sMainThreadHandler null) {sMainThreadHandler thread.getHandler();}if (false) {Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, ActivityThread));}// End of event ActivityThreadMain.Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);Looper.loop();throw new RuntimeException(Main thread loop unexpectedly exited); }可以看到在第20行调用了Looper.prepareMainLooper()方法而这个方法又会再去调用Looper.prepare()方法代码如下所示 我们应用程序的主线程开启的时候就会创建一个Looper对象从而不需要再手动去调用Looper.prepare()方法了。 这样基本就将Handler的创建过程完全搞明白了总结一下就是在主线程中可以直接创建Handler对象而在子线程中需要先调用ooper.prepare()才能创建Handler对象。 2、发送消息 Override protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);handler1 new Handler();new Thread(new Runnable() {Overridepublic void run() {Message message Message.obtain();message.arg1 1;Bundle bundle new Bundle();bundle.putString(data, data);message.setData(bundle);handler1.sendMessage(message);}}).start(); }这里Handler到底是把Message发送到哪里去了呢为什么之后又可以在Handler的handleMessage()方法中重新得到这条Message呢看来又需要通过阅读源码才能解除我们心中的疑惑了 调用了sendMessageDelayed 接着调用了sendMessageAtTime这个方法的源码如下所示 sendMessageAtTime()方法接收两个参数其中msg参数就是我们发送的Message对象而uptimeMillis参数则表示发送消息的时间它的值等于自系统开机到当前时间的毫秒数再加上延迟时间如果你调用的不是sendMessageDelayed()方法延迟时间就为0然后将这两个参数都传递到MessageQueue的enqueueMessage()方法中。 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) {// New head, wake up the event queue if blocked.msg.next p;mMessages msg;needWake mBlocked;} else {// Inserted within the middle of the queue. Usually we dont 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;if (p null || when p.when) {break;}if (needWake p.isAsynchronous()) {needWake false;}}msg.next p; // invariant: p prev.nextprev.next msg;}// We can assume mPtr ! 0 because mQuitting is false.if (needWake) {nativeWake(mPtr);}}return true; }MessageQueue并没有使用一个集合把所有的消息都保存起来它只使用了一个mMessages对象表示当前待处理的消息。然后观察上面的代码我们就可以看出所谓的入队其实就是将所有的消息按时间来进行排序这个时间就是msg.when。具体的操作方法就根据时间的顺序调用msg.next从而为每一个消息指定它的下一个消息是什么。当然如果你是通过sendMessageAtFrontOfQueue()方法来发送消息的它也会调用enqueueMessage()来让消息入队只不过时间为0这时会把mMessages赋值为新入队的这条消息然后将这条消息的next指定为刚才的mMessages这样也就完成了添加消息到队列头部的操作。 3、取消息 入队操作我们就已经看明白了那出队操作是在哪里进行的呢?这个就需要看一看Looper.loop()方法的源码了如下所示 此方法最后进入了一个死循环然后不断地调用loopOnce()方法这个方法作用为 Poll and deliver single message, return true if the outer loop should continue.轮询并传递单个消息如果外部循环应该继续则返回true。 它的简单逻辑就是如果当前MessageQueue中存在mMessages(即待处理消息)就将这个消息出队然后让下一条消息成为mMessages否则就进入一个阻塞状态一直等到有新的消息入队。每当有一个消息出队就将它传递到msg.target的dispatchMessage()方法中那这里msg.target又是什么呢其实就是Handler查看Message类源码 PS这里msg.target通过target将Handler存入Message是为了解决在多个Hander的情况无法找到处理当前消息的Handler问题。实际上是一种架构设计上的妥协我们常见的Hander内存泄漏问题也是源于此。最终导致Activity无法及时回收 Thread–Looper–MessageQue–Message.target–mHandler–Activity 4、消息处理 那么发送消息后最终又是怎么调用到handleMessage的呢接下来看一下Handler中dispatchMessage()方法的源码如下所示 在第101行进行判断如果mCallback不为空则调用mCallback的handleMessage()方法否则直接调用Handler的handleMessage()方法并将消息对象作为参数传递过去。这样我相信大家就都明白了为什么handleMessage()方法中可以获取到之前发送的消息了吧 5、线程切换的方法Handler异步消息处理机制流程 handler.sendMessage() 我们接下来继续分析一下为什么使用异步消息处理的方式就可以对UI进行操作了呢这是由于Handler总是依附于创建时所在的线程比如我们的Handler是在主线程中创建的而在子线程中又无法直接对UI进行操作于是我们就通过一系列的发送消息、入队、出队等环节最后调用到了Handler的handleMessage()方法中这时的handleMessage()方法已经是在主线程中运行的因而我们当然可以在这里进行UI操作了。整个异步消息处理流程的示意图如下图所示 handler.post() 还是调用了sendMessageDelayed()方法去发送一条消息啊并且还使用了getPostMessage()方法将Runnable对象转换成了一条消息我们来看下这个方法的源码 在这个方法中将消息的callback字段的值指定为传入的Runnable对象。咦这个callback字段看起来有些眼熟啊在Handler的dispatchMessage()方法中原来有做一个检查如果Message的callback等于null才会去调用handleMessage()方法否则就调用handleCallback()方法。那我们快来看下handleCallback()方法中的代码吧 竟然就是直接调用了一开始传入的Runnable对象的run()方法。因此在子线程中通过Handler的post()方法进行UI操作就可以这么写 public class MainActivity extends Activity {private Handler handler;Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);handler new Handler();new Thread(new Runnable() {Overridepublic void run() {handler.post(new Runnable() {Overridepublic void run() {// 在这里进行UI操作}});}}).start();} }虽然写法上简洁很多但是原理是完全一样的我们在Runnable对象的run()方法里更新UI效果完全等同于在handleMessage()方法中更新UI。 View.post() 原来就是调用了Handler中的post()方法 Activity中的runOnUiThread() 如果当前的线程不等于UI线程(主线程)就去调用Handler的post()方法否则就直接调用Runnable对象的run()方法 二、Handler高频面试题 1、为什么要有Handler 主要目的是要解决线程切换问题handler里的Message机制解决了线程间通信 2、为什么要有MessageQueue MessageQueue是一个单向链表next()调用nativePollOnce-lunx的epoll_wait()等待实现阻塞时队列 队列的出现解决了处理消息阻塞到发送消息的问题由于队列是生产者消费者模式而要使用队列需要至少两个线程与一个死循环 一个线程负责生产消息一个线程消费消息死循环需要取出放入队列里的消息 3、为什么要有Looper 为了循环取出队列里的消息 4、主线程的Looper和子线程Looper有什么不同 子线程Looper是可以退出的主线程不行 5、一个线程可以有几个Handler几个looper 多个handler每个handler都会配一个MessageQueue Lopper和MessageQueue绑定为防止创建多个messageQueueLooper创建也只能被调用一次; 一个Looper放在ThreadLocalMap中 假如Looper对象由Handler创建每创建一个Handler就有一个Looper那么调用Looper.loop()时开启死循环在外边调用Looper的地方就会阻塞 一个线程可以有多个Handler并且每一个Handler都可以处理消息队列中的消息。每个Handler在创建时会与当前线程的消息队列相关联因此可以通过Handler向该线程的消息队列发送消息。 需要注意的是不同的Handler可能会被关联到相同的Looper消息循环器上也可能不同的Handler使用各自独立的Looper来实现消息处理。例如一个Activity可能会创建多个Handler对象其中一些Handler会在主线程上执行而另一些Handler则会在新建的子线程上执行它们分别使用了不同的Looper来处理消息队列中的消息。 因此可以说一个线程可以拥有多个Handler这取决于应用程序设计的具体情况和需要。但是由于在Android中每个线程都只有一个消息队列因此多个Handler之间处理消息时可能会存在竞争和同步问题需要开发者进行合理的规划和处理以避免出现不必要的问题。 looper的生命周期是当前线程的生命周期长度如何保证一个线程中只有一个Looper可以通过线程ThreadLocalThreadLocal中会有一个ThreadLocalMap保存一个Looper通过调用ThreadLocal的get()来判断是否能获取到Looper如果能得到说明已经有了Looper直接返回一个异常通知已经有了Looper Looper.prepare()保证只有一个Looper。存入Looper存Looper时ThreadLocalMap的key为ThreadLocalvalue为Looper sThreadLocal为ThreadLocal类 进入ThreadLocal类获取当前线程Thread.currentThread() 进入Thread类 ThreadLocalMap类似于HashMap每个Thread对象都有一个对应的ThreadLocalMap Looper.loop()循环提取消息并最终调用handlerMessage()去处理 6、主线程会为什么会一直阻塞 是的如果主线程不进行looper.loop()阻塞一下子执行完成整个程序就直接结束了不可能有机会去执行其他的任务了。 Android是事件为驱动的操作系统事件过来就去handler里执行没有事件就阻塞在那里显示界面 sendMessage是生产者handlerMessage是消费者消息在队列中排队MessageQueue这样解决大量的消息过来的问题不会造成主线程sendMessage阻塞所有消息都会直接放在队列中排队等候执行 7、ANR是什么发生条件 ANRApplication Not Responding指的是应用程序无响应的错误它表示应用程序在执行某个操作时长时间没有响应。在Android系统中如果一个应用程序在主线程中执行了耗时的操作而导致主线程被阻塞那么系统就会弹出一个对话框警告用户当前应用程序出现了ANR错误并提示用户选择“等待”或“关闭应用程序”。 ANR通常是由于一些长时间的I/O操作、耗时的计算或者其他阻塞主线程的原因引起的。当主线程被阻塞时应用程序的用户界面就会无响应用户无法与应用程序进行交互这就给用户带来了不好的体验。 ANR发生条件是 Activity应用在 5 秒内未响应用户的输入事件如按键或者触摸 BroadCastReceiver BroadcastReceiver 未在 10 秒内完成相关的处理 Service20 秒均为前台。Service 在20 秒内无法处理完成如果Handler收到以上三个相应事件在规定时间内完成了则移除消息不会ANR若没完成则会超时处理弹出ANR对话框为了避免ANR错误开发人员可以采取以下措施 将耗时的操作放在子线程中执行避免在主线程中执行。使用异步任务或线程池等机制来执行耗时的操作从而避免阻塞主线程。在主线程中使用Handler或者AsyncTask等机制来更新UI界面。优化应用程序的代码减少不必要的计算和I/O操作。 8、Looper的死循环为什么不会让主线程卡死或ANR 我们的UI线程主线程其实是ActivityThread所在的线程而一个线程只会有一个Looper ActivityThread.java的main函数是一个APP进程的入口如果不一直循环则在main函数执行完最后一行代码后整个应用进程就会退出 android是以事件为驱动的操作系统当有事件来时就去做对应的处理没有时就显示静态界面 App进程的入口为ActivityThread.java的main()函数注意ActivityThread不是一个线程 应用的UI主线程实际是调用ActivityThread.java的main()函数执行时所在的线程而这个线程对我们不可见但是这就是主线程 在ActivityThread.java的main()函数中会调用Looper.prepareMainLooper()Looper.prepareMainLooper()会创建一个Looper并放到主线程的变量threadLocals中进行绑定threadLocals是一个ThreadLocal.ThreadLocalMap在ActivityThread.java的main()函数结尾开启Looper.loop()进行死循环不让main函数结束从而让App进程不会结束Android系统是以事件作为驱动的操作系统当有事件来时就去做对应处理没有事件时就显示当前界面不做其他多余操作浪费资源在Looper.loop()的死循环中不仅要取用户发的事件还要取系统内核发的事件如屏幕亮度改变等等在调用Looper.loop()时从MessageQueue.next()中获取事件若没有则阻塞有则分发MessageQueue其实不是一个队列用epoll机制实现了阻塞。在Looper.prepareMainLooper()时会调用c函数 epoll_create()将App注册进epoll机制的红黑树中得到fd的值 epoll_ctl()给每个App注册事件类型并监听fd值是否改变Linux中事件都会被写入文件中如触摸屏幕事件会写入到dev/input/event0文件中fd有改变时唤醒epoll_wait epoll_wait()有事件时就分发没事件就阻塞 9、为什么Handler会造成内存泄露 内存泄漏由于疏忽或错误造成程序未能释放已经不再使用的内存的情况 内存泄漏的原因在Activity中将Handler声明成非静态内部类或匿名内部类这样Handle默认持有外部类Activity的引用。如果Activity在销毁时Handler还有未执行完或者正在执行的Message而Handler又持有Activity的引用导致GC无法回收Activity导致内存泄漏。如以下两种情形可能导致内存泄漏 1、在Activity内将Handler声明成匿名内部类 //匿名内部类private Handler mHandler new Handler() {Overridepublic void handleMessage(NonNull Message msg) {super.handleMessage(msg);}};new Handler().postDelayed(new Runnable() {Overridepublic void run() {//大量的操作activity要销毁时还没结束}},1000);2、在Activity内将Handler声明成非静态内部类 //非静态内部类private class MyHandler extends Handler{Overridepublic void handleMessage(NonNull Message msg) {super.handleMessage(msg);}}private MyHandler mHandler new MyHandler();内存泄露的本质长生命周期持有短生命周期造成短生命周期得不到释放就会造成内存泄露线程的生命周期长Activity的生命周期短Activity运行完Handler得不到释放 1.在Activity中实例化Handler导致了Activity中持有handler对象2.Message和Handler的持有是由于在Lopper中进行循环遍历的时候Message需要被执行所以要使用handler的handleMessage()3.MessageQueue是Message的集合对象所以造成持有关系4.Looper又和MessageQueue进行了绑定造成了Looper对MessageQueue的持有最终线程-----Looper-----MessageQueue-----Message-----Handler------Activity一系列持有造成的内存泄露内存泄露两大解决方案 1、静态内部类 弱引用 private static class MyHandler extends Handler {//弱引用在垃圾回收时activity可被回收private WeakReferenceMainActivity mWeakReference;public MyHandler(MainActivity activity) {mWeakReference new WeakReference(activity);}Overridepublic void handleMessage(NonNull Message msg) {super.handleMessage(msg);}}2、在Activity销毁时清空Handler中未执行或正在执行的Callback以及Message Overrideprotected void onDestroy() {super.onDestroy();//清空handler管道和队列mHandler.removeCallbacksAndMessages(null);}10、内存抖动如何解决 内存抖动根本的解决方式是复用handler.obtainMessage() Message的创建方式有两种 1.new Message() 2.obtainMessage() 内存抖动是为啥因为短时间创建大量的对象并销毁。 使用obtainMessage创建一个Message会有复用的作用涉及到一个回收池回收池中存放的是Message会有一定的数量使用单项链表MessageQueue来存放这些Message。每个message对象指向下一个Message对象 obtainMessage()创建对象是从回收池中获取没有的才会进行创建回收池中获取一个Message需要将管理回收池的列表同这个取出来的Message的关联进行切断所以需要将此获取的Message的next引用置为空。并且sPool变量的引用将会变成下一个Message同时单向列表的size-1 public static Message obtain() {synchronized (sPoolSync) {if (sPool ! null) {Message m sPool;sPool m.next;m.next null;m.flags 0; // clear in-use flag sPoolSize--; return m; } return new Message();} }new Message()产生的对象是不会进回收池的。 从Looper的回收池中取MessageMessageQueue是一个单向链表MessageQueue不是一个单纯的对象而是一个链表集合最大长度固定50个 11、安卓中的Looper.loop()阻塞为什么不会有问题 Android是事件为驱动的操作系统事件过来就去handler里执行(handler处理包括了创建服务创建广播结束服务等等事件处理)如果没有事件过来就阻塞在那里显示静止界面有事件就去执行事件 利用epoll机制可以定位到是哪个app接受这个事件运用到红黑树事件过来之后查找app来执行这个事件事件带有一个标记查找对应的app。
http://www.dnsts.com.cn/news/255490.html

相关文章:

  • 谢岗仿做网站百度搜索不到网站
  • wordpress 企业网站做蛋糕视频教学网站
  • 做网站一般哪里找长春南关网站建设
  • 妇女之家网站建设方案新闻热点事件2024最新
  • 什么网站能接单做网站如何做网站左侧导航条
  • 阿里云网站搭建教程网站主页流动图片怎么做
  • ASP网站建设实训报告总结如何修改asp网站栏目
  • 万网云服务器怎么上传网站吗高清装饰画图片素材网
  • 工业设计网站设计深圳网站建设公司fantodo
  • 湖南建设信誉查询网站国内网站建设需要多少钱
  • 商城网站建设公司哪家好建站的步骤
  • 建设网站的必要与可行性做网站之前需要准备什么条件
  • 关于小学网站建设的论文对网站建设好学吗
  • 四川省城乡建设信息网站证件查询wordpress 直播
  • 淘宝客静态网站怎么开网店拼多多
  • asp.net网站开发期末复习题wordpress主题取消
  • 盐城网站优化推广服务网站积分解决方案
  • 微信网站开发源代码网站建设学生选课课程设计报告
  • h5作品网站广西网站建设工具
  • 网站怎样才能在百度被搜索到网站用cms
  • 网站建设分几种类型设计深圳网站制作
  • 新绛做网站中国互联网数据平台
  • 各大网站热搜榜排名wordpress影视站主题
  • 方圆网通网站建设wordpress排版错误
  • 做网站的常识wordpress中文开发文档
  • 广西建设工会网站广东微信网站制作价格
  • 大学网站建设课程课综公司邮箱签名模板
  • 企业网站 的网络营销方法有做网站公司的年终总结
  • wordpress视频解析主题seo方式包括
  • 班级网站怎么做网页制作wordpress在线安装主题