企业网站建设的公司,做h5的网站有哪些,网站推广的内容,温州哪里做网站目录 
基础了解#xff1a; 
相关概念解释 
整体流程图#xff1a; 
源码解析 
Looper 
总结#xff1a; 
sendMessage 
总结#xff1a; 
ThreadLocal 基础了解#xff1a; 
Handler是一套 Android 消息传递机制,主要用于线程间通信。实际上handler其实就是主线程在起了一…目录 
基础了解 
相关概念解释 
整体流程图 
源码解析 
Looper 
总结 
sendMessage 
总结 
ThreadLocal 基础了解 
Handler是一套 Android 消息传递机制,主要用于线程间通信。实际上handler其实就是主线程在起了一个子线程子线程运行并生成MessageLooper获取message并传递给HandlerHandler逐个获取子线程中的Message在这个机制下中包括了Looper、MessageQueueThreadLocal等 
相关概念解释 Handler、Message、Message Queue、Looper 
Message 代表一个行为what或者一串动作Runnable, 每一个消息在加入消息队列时,都有明确的目标Handler 
ThreadLocal 线程本地存储区Thread Local Storage简称为TLS每个线程都有自己的私有的本地存储区域不同线程之间彼此不能访问对方的TLS区域。ThreadLocal的作用是提供线程内的局部变量TLS,这种变量在线程的生命周期内起作用,每一个线程有他自己所属的值(线程隔离) 
MessageQueue (C层与Java层都有实现) 以队列的形式对外提供插入和删除的工作, 其内部结构是以双向链表的形式存储消息的 
Looper (C层与Java层都有实现) Looper是循环的意思,它负责从消息队列中循环的取出消息然后把消息交给Handler处理 
Handler 消息的真正处理者, 具备获取消息、发送消息、处理消息、移除消息等功能 
整体流程图 源码解析 
分析源码API 31 
Looper 
首先从new Handler点进去分析 可以看到handler的构造方法里先判断当前创建的handler是否为static的如果不是会弹log 
The following Handler class should be static or leaks might occur: 这块后面在解释为啥会这么设置 
同时声明了需要个looper然后这个looper是Looper.myLooper中获取的然后如果looper如果为空的话则会抛出异常 
Cant create handler inside thread   Thread.currentThread()  that has not called Looper.prepare() 
可以看出这个looper还是挺重要的然后发现消息队列也是在looper中声明的那么 我们就来看这个looper是怎么获取的点 
Looper.myLooper()方法进去看 发现是sThreadLocal中获取的 然后全局搜这块的实现 发现是在prepare(boolean)中赋值的这块主要是创建一个looper然后再looper的构造方法中创建了消息队列message queue最后添加到sThreadLocal中 同时发现这个set方法有点熟悉呀点进去看我们发现 主要是通过ThreadLocalMap的set值实现的这块是只进行一次set值从而保证了一个looper的存在。 
同时回到了之前的prepare方法中我们ctrl看看发现有个方法特别熟悉 
prepareMainLooper 点进去看发现这块已经在 ActivitThread中调用了这块之前通过其他大佬了解到其实当我们点击了app图标后根据启动流程会执行到ActivityThread中,这块也是通俗意义上说的主线程所以说如果是在主线程中使用handler的话是不需要在调用Looper.loop()方法的因为已经创建好了。 同时我们注意到ActivityThread中的另外一个方法 点击进去看发现  /*** Poll and deliver single message, return true if the outer loop should continue.*/SuppressWarnings(AndroidFrameworkBinderIdentity)private static boolean loopOnce(final Looper me,final long ident, final int thresholdOverride) {Message msg  me.mQueue.next(); // might blockif (msg  null) {// No message indicates that the message queue is quitting.return false;}// This must be in a local variable, in case a UI event sets the loggerfinal Printer logging  me.mLogging;if (logging ! null) {logging.println( Dispatching to   msg.target    msg.callback  :   msg.what);}// Make sure the observer wont change while processing a transaction.final Observer observer  sObserver;final long traceTag  me.mTraceTag;long slowDispatchThresholdMs  me.mSlowDispatchThresholdMs;long slowDeliveryThresholdMs  me.mSlowDeliveryThresholdMs;if (thresholdOverride  0) {slowDispatchThresholdMs  thresholdOverride;slowDeliveryThresholdMs  thresholdOverride;}final boolean logSlowDelivery  (slowDeliveryThresholdMs  0)  (msg.when  0);final boolean logSlowDispatch  (slowDispatchThresholdMs  0);final boolean needStartTime  logSlowDelivery || logSlowDispatch;final boolean needEndTime  logSlowDispatch;if (traceTag ! 0  Trace.isTagEnabled(traceTag)) {Trace.traceBegin(traceTag, msg.target.getTraceName(msg));}final long dispatchStart  needStartTime ? SystemClock.uptimeMillis() : 0;final long dispatchEnd;Object token  null;if (observer ! null) {token  observer.messageDispatchStarting();}long origWorkSource  ThreadLocalWorkSource.setUid(msg.workSourceUid);try {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);}}if (logSlowDelivery) {if (me.mSlowDeliveryDetected) {if ((dispatchStart - msg.when)  10) {Slog.w(TAG, Drained);me.mSlowDeliveryDetected  false;}} else {if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, delivery,msg)) {// Once we write a slow delivery log, suppress until the queue drains.me.mSlowDeliveryDetected  true;}}}if (logSlowDispatch) {showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, dispatch, msg);}if (logging ! null) {logging.println( Finished to   msg.target     msg.callback);}// Make sure that during the course of dispatching the// identity of the thread wasnt corrupted.final long newIdent  Binder.clearCallingIdentity();if (ident ! newIdent) {Log.wtf(TAG, Thread identity changed from 0x Long.toHexString(ident)   to 0x Long.toHexString(newIdent)   while dispatching to  msg.target.getClass().getName()    msg.callback   what  msg.what);}msg.recycleUnchecked();return true;} 
根据注释发现这个方法是在当前线程中允许一个message queue进行for死循环从消息队列中取出消息其中next方法会在下面分析然后分发给msg.target中的dispatchMessage方法其中msg.target是handler发送消息的时候赋值上去的将在下面会说到其中msg.target对应的就是handler然后dispatchMessage源码为 嗯这块代码不需要解释了吧如果没设置callback的话则会直接回调发送消息的handler所在线程的handlerMessage方法了 
总结 
1.一个线程中只能有一个looper这块是由ThreadLocal决定的因为ThreadLocal中包含了一个ThreadLocalMap在调用looper.prepare时将looper对象set进去ThreadLocal中 
2.UI线程就是主线程是不需要在调用looper.prepare跟looper.loop()的因为这2个在ActivityThread中的main方法中已经调用过了其中looper.prepare调用的为looper.prepareMainLooper() 
3.looper.prepare方法主要做的事就是赋值looper对象到ThreadLocal中其中looper对象实例化时创建了消息队列message queueLooper.loop方法主要做的事就是调用message queue.next方法循环拿消息如果有消息的话则将消息进行分发到msg.target上对应的handler.dispatchMessage中其中handler.dispatchMessage就是熟悉的handlerMessage 
sendMessage 
其实这块我们常见的handler的那些操作本质上都是调用sendMessage的只是换了个说法而已常见的handler操作handler.sendMessage(msg)、handler.sendEmptyMessage(1) 
handler.postDelay()、handler.post()下面我们一个个点进去看 
1.handler.sendMessage(msg) 2、sendEmptyMessage 3.handler.postDelay(Runable,time) 4.handler.post() 。是不是觉得跟postDelay一样Runable通过getPostMessage方法将传入的Runable变为 message的callback方法 可以发现这几个handler的方法都会调用sendsendMessageDelayed然后调用sendMessageAtTime最后调用enqueueMessage进行消息处理同时我们发现sendMessageAtTime中会设定个时间SystemClock.uptimeMillis()这块时间为手机开机后系统非深度休眠时间而不是手机时间。所以手机上修改当前时间是不会影响Message执行的。一般我们也可以通过这个值来获取手机开机多久。 
然后我们来看enqueueMessage其他那些looper、message queue已经在handler初始化的时候获取到了其中在handler初始化的时候通过looper.prepare已经创建好了 这块是跳转到message queue的enqueueMessage方法中 
boolean enqueueMessage(Message msg, long when) {if (msg.target  null) {throw new IllegalArgumentException(Message must have a target.);}//保证多线程入队先加锁synchronized (this) {//....msg.markInUse(); //标记正在使用msg.when  when; // when 属性Message p  mMessages; //拿到链表头部的消息boolean needWake;// 满足以下3种情况之一就把msg插入到链表头部//1.队列为null//2.当前时间没有延迟0//3.插入的时间比链表的头节点的when时间早if (p  null || when  0 || when  p.when) {msg.next  p;mMessages  msg;needWake  mBlocked; //如果处于阻塞状态需要唤醒} else {//唤醒标识 //4.如果p ! null且msg并不是最早触发的就在链表中找一个位置把msg插进去//5.如果处于阻塞状态并且链表头部是一个同步屏障target为null的Message并且插入消息是最早的异步消息需要唤醒   //队列为空或者队列头消息还未到执行时间且当前消息待执行时间小于队列头消息此时才需要唤醒needWake  mBlocked  p.target  null  msg.isAsynchronous();Message prev;for (;;) {prev  p;p  p.next;//当遍历到队尾、或者是  msg的时间比当前时间更早 if (p  null || when  p.when) {break;}//发现了异步消息的存在不需要唤醒if (needWake  p.isAsynchronous()) {needWake  false;}}//单链表、插入msg信息msg.next  p; // invariant: p  prev.nextprev.next  msg;}// 如果looper阻塞/休眠中则唤醒looper循环机制处理消息if (needWake) {nativeWake(mPtr);//唤醒}}return true;
} 
可以看到MessageQueue queue是通过链表形式对Message 进行存储并通过when 的大小对 Message 进行排序。 
其中遍历插入过程如图 遍历队列当某个数据的时间戳优先级低于插入数据时把数据插入否则把数据放在队列尾部。 注意 Handler可以无限插入数据没有大小限制。 
然后我们回到之前分析过的looper中的loop方法 
/*** Run the message queue in this thread. Be sure to call* {link #quit()} to end the loop.*/SuppressWarnings(AndroidFrameworkBinderIdentity)public static void loop() {final Looper me  myLooper();if (me  null) {throw new RuntimeException(No Looper; Looper.prepare() wasnt called on this thread.);}if (me.mInLoop) {Slog.w(TAG, Loop again would have the queued messages be executed  before this one completed.);}me.mInLoop  true;// 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();// Allow overriding a threshold with a system prop. e.g.// adb shell setprop log.looper.1000.main.slow 1  stop  startfinal int thresholdOverride SystemProperties.getInt(log.looper. Process.myUid()  . Thread.currentThread().getName() .slow, 0);me.mSlowDeliveryDetected  false;for (;;) {if (!loopOnce(me, ident, thresholdOverride)) {return;}}} 
SuppressWarnings(AndroidFrameworkBinderIdentity)
private static boolean loopOnce(final Looper me,final long ident, final int thresholdOverride) {// 从Looper中取出MessageQueue进行轮询获取消息msgMessage msg  me.mQueue.next(); if (msg  null) {// No message indicates that the message queue is quitting.return false;}//....try {// Message.target 就是对应的Handler.dispatchMessage回调消息msg.target.dispatchMessage(msg);}//....//回收消息msg.recycleUnchecked();return true;
} 核心就是这个next方法了这个方法主要是将存在message queue中的消息取出来的 
UnsupportedAppUsageMessage next() {// 如果消息循环已经退出并被处理请返回此处。// 如果应用程序尝试退出后不支持的循环程序则会发生这种情况。final long ptr  mPtr;if (ptr  0) {return null;}int pendingIdleHandlerCount  -1; // -1 only during first iterationint nextPollTimeoutMillis  0;//判断消息队列中是否有消息for (;;) {if (nextPollTimeoutMillis ! 0) {Binder.flushPendingCommands();}//就是在这里根据nextPollTimeoutMillis判断是否要阻塞// 阻塞方法主要是通过 native 层的 epoll 监听文件描述符的写入事件来实现的。// 如果 nextPollTimeoutMillis  -1一直阻塞不会超时。// 如果 nextPollTimeoutMillis  0不会阻塞立即返回。// 如果 nextPollTimeoutMillis  0最长阻塞nextPollTimeoutMillis毫秒(超时)如果期间有程序唤醒会立即返回。nativePollOnce(ptr, nextPollTimeoutMillis);synchronized (this) {// 尝试检索下一条消息。 如果找到则返回。final long now  SystemClock.uptimeMillis();Message prevMsg  null;Message msg  mMessages;if (msg ! null  msg.target  null) {// 被障碍挡住了。 在队列中查找下一条异步消息。do {prevMsg  msg;msg  msg.next;} while (msg ! null  !msg.isAsynchronous());}if (msg ! null) {//队列中拿到的消息不为nullif (now  msg.when) {// 下一条消息尚未准备好。 设置超时以使其在准备就绪时醒来。nextPollTimeoutMillis  (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else {// 正常返回处理...} else {// 队列中没有消息标记阻塞looper循环进入休眠nextPollTimeoutMillis  -1;}// 现在已处理所有挂起的消息处理退出消息。if (mQuitting) {dispose();return null;}// If first time idle, then get the number of idlers to run.// 空闲句柄仅在队列为空或将来要处理队列中的第一条消息可能是屏障时才运行。if (pendingIdleHandlerCount  0 (mMessages  null || now  mMessages.when)) {pendingIdleHandlerCount  mIdleHandlers.size();}...}...// 将空闲处理程序计数重置为0这样我们就不会再次运行它们。pendingIdleHandlerCount  0;// 在调用空闲处理程序时可能已经传递了一条新消息//因此返回并再次查找未处理消息而无需等待。nextPollTimeoutMillis  0;}} 然后找到后返回loopOnce将message分发给msg.target就是handler然后就是如果有消息的话则将消息进行分发到msg.target上对应的handler.dispatchMessage中其中handler.dispatchMessage就是熟悉的handlerMessage然后一切做完后会调用    msg.recycleUnchecked()进行消息的回收 
具体流程如下 总结 
1.handler.sendMessage、handler.sendEmptyMessage、handler.post、handler.postDelay本质上都是调用handler.sendsendMessageDelayed然后调用sendMessageAtTime最后调用enqueueMessage进行添加消息处理 
2.sendMessageAtTime中会设定个时间SystemClock.uptimeMillis()这块时间为手机开机后系统非深度休眠时间而不是手机时间。所以手机上修改当前时间是不会影响Message执行的。一般我们也可以通过这个值来获取手机开机多久 
3.MessageQueue queue是通过链表形式对Message 进行存储并通过when 的大小对 Message 进行排序。 
4.Looper属于某个线程而MessageQueue存储在Looper中所以MessageQueue通过Looper特定的线程上关联而Handler在构造中又与Looper和MessageQueue相互关联通过Handler发送消息的时候消息就会被插入到Handler关联的MessageQueue中而Looper会不断的轮询消息从MessageQueue中取出消息给相应的Handler处理所有最终通过Handler发送的消息就会被执行到Looper所在的线程上这就是 Handler线程切换的原理无论发送消息Handler对象处于什么线程最终处理消息都是在Looper所在的线程。 
5.handler 阻塞、唤醒 
阻塞条件 分析MessageQueue的next函数发生阻塞只会存在以上两种情况 
队列中消息个数为0且没有可处理的IdleHandler,此时会一直阻塞队列中消息个数不为0但是队列头消息还未到执行时间且没有IdleHandler要执行此时会阻塞时间nn表示队列头消息距离执行的时间
以上两种阻塞情况mBlock都为true.只有这两种情况需要唤醒。 
一言以蔽之没有IdleHandle要执行且队列中没有立即要可执行的消息时会阻塞 
唤醒条件 队列为空或者队列头消息还未到执行时间且当前消息待执行时间小于队列头消息此时才需要唤醒。一言以蔽之也就是当前消息插入到队列头部时才需要唤醒。  mBlocked为true也就是没有Idle消息要处理且当前队列头消息时同步屏障消息且当前消息时异步消息此时需要立即处理因为该异步消息对响应及时性要求比较高。 
其实关于这个还涉及到一个面试题“为啥looper.loop内部消息队列死循环不会导致应用卡死”通过这个总结就很清晰了吧 
这里涉及线程先说说说进程/线程进程每个app运行时前首先创建一个进程该进程是由Zygote fork出来的用于承载App上运行的各种Activity/Service等组件。进程对于上层应用来说是完全透明的这也是google有意为之让App程序都是运行在Android Runtime。大多数情况一个App就运行在一个进程中除非在AndroidManifest.xml中配置Android:process属性或通过native代码fork进程。 
线程线程对应用来说非常常见比如每次new Thread().start都会创建一个新的线程。该线程与App所在进程之间资源共享从Linux角度来说进程与线程除了是否共享资源外并没有本质的区别都是一个task_struct结构体在CPU看来进程或线程无非就是一段可执行的代码CPU采用CFS调度算法保证每个task都尽可能公平的享有CPU时间片。 
有了这么准备再说说死循环问题 
对于线程既然是一段可执行的代码当可执行代码执行完成后线程生命周期便该终止了线程退出。而对于主线程我们是绝不希望会被运行一段时间自己就退出那么如何保证能一直存活呢简单做法就是可执行代码是能一直执行下去的死循环便能保证不会被退出例如binder线程也是采用死循环的方法通过循环方式不同与Binder驱动进行读写操作当然并非简单地死循环无消息时会休眠。但这里可能又引发了另一个问题既然是死循环又如何去处理其他事务呢通过创建新线程的方式。 
真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长会导致掉帧甚至发生ANRlooper.loop本身不会导致应用卡死。 ThreadLocal 通过get和set方法不难发现其中频繁出现了一个ThreadLocalMap对象这个变量跟map有点类似是按键值对的方式对数据进行存储的其中 
key:指的当前ThreadLocal变量 
valueT 当前要存储的值 
然后在点getMap方法看ThreadLocalMap是如何获取的 然后我们发现原来每个线程的threadLocals就是ThreadLocalMap来着然后这个变量存储着ThreadLocal和对应的保存对象这样的话在不同的线程访问同一个ThreadLocal对象但是获取到的值是不一样的 这样就相对于用一个map存储所有线程的方式会好很多了因为那样的话管理也很混乱每个线程有联系的话也容易造成内存泄露 
参考文章 
Handler源码分析 - 简书 
Handler源码解析_Pioneer_Chang的博客-CSDN博客 
Handler源码解析_handler messagequeue_醉饮千觞不知愁的博客-CSDN博客 
Handler阻塞和唤醒条件 - 掘金 文章转载自: http://www.morning.rscrj.cn.gov.cn.rscrj.cn http://www.morning.osshjj.cn.gov.cn.osshjj.cn http://www.morning.grnhb.cn.gov.cn.grnhb.cn http://www.morning.nwpnj.cn.gov.cn.nwpnj.cn http://www.morning.ybyln.cn.gov.cn.ybyln.cn http://www.morning.rfhm.cn.gov.cn.rfhm.cn http://www.morning.bqmhm.cn.gov.cn.bqmhm.cn http://www.morning.xkwrb.cn.gov.cn.xkwrb.cn http://www.morning.xjmpg.cn.gov.cn.xjmpg.cn http://www.morning.zmpqt.cn.gov.cn.zmpqt.cn http://www.morning.qwdlj.cn.gov.cn.qwdlj.cn http://www.morning.myfwb.cn.gov.cn.myfwb.cn http://www.morning.bswnf.cn.gov.cn.bswnf.cn http://www.morning.mnwmj.cn.gov.cn.mnwmj.cn http://www.morning.ngcw.cn.gov.cn.ngcw.cn http://www.morning.fswml.cn.gov.cn.fswml.cn http://www.morning.dskmq.cn.gov.cn.dskmq.cn http://www.morning.twhgn.cn.gov.cn.twhgn.cn http://www.morning.mrfbp.cn.gov.cn.mrfbp.cn http://www.morning.trrhj.cn.gov.cn.trrhj.cn http://www.morning.wfdlz.cn.gov.cn.wfdlz.cn http://www.morning.xstfp.cn.gov.cn.xstfp.cn http://www.morning.kwxr.cn.gov.cn.kwxr.cn http://www.morning.stmkm.cn.gov.cn.stmkm.cn http://www.morning.dpppx.cn.gov.cn.dpppx.cn http://www.morning.dsncg.cn.gov.cn.dsncg.cn http://www.morning.rttp.cn.gov.cn.rttp.cn http://www.morning.mbrbg.cn.gov.cn.mbrbg.cn http://www.morning.nhbhc.cn.gov.cn.nhbhc.cn http://www.morning.xrpjr.cn.gov.cn.xrpjr.cn http://www.morning.skrxp.cn.gov.cn.skrxp.cn http://www.morning.fwjfh.cn.gov.cn.fwjfh.cn http://www.morning.mnsmb.cn.gov.cn.mnsmb.cn http://www.morning.cpmwg.cn.gov.cn.cpmwg.cn http://www.morning.hphrz.cn.gov.cn.hphrz.cn http://www.morning.dmcqy.cn.gov.cn.dmcqy.cn http://www.morning.fglxh.cn.gov.cn.fglxh.cn http://www.morning.rtpw.cn.gov.cn.rtpw.cn http://www.morning.mxdhy.cn.gov.cn.mxdhy.cn http://www.morning.bpmns.cn.gov.cn.bpmns.cn http://www.morning.rnht.cn.gov.cn.rnht.cn http://www.morning.lmdkn.cn.gov.cn.lmdkn.cn http://www.morning.xpgwz.cn.gov.cn.xpgwz.cn http://www.morning.kzslk.cn.gov.cn.kzslk.cn http://www.morning.hhqjf.cn.gov.cn.hhqjf.cn http://www.morning.wkjzt.cn.gov.cn.wkjzt.cn http://www.morning.fhhry.cn.gov.cn.fhhry.cn http://www.morning.hsksm.cn.gov.cn.hsksm.cn http://www.morning.nfmtl.cn.gov.cn.nfmtl.cn http://www.morning.qtfss.cn.gov.cn.qtfss.cn http://www.morning.sqqkr.cn.gov.cn.sqqkr.cn http://www.morning.grxbw.cn.gov.cn.grxbw.cn http://www.morning.rgnq.cn.gov.cn.rgnq.cn http://www.morning.rsjf.cn.gov.cn.rsjf.cn http://www.morning.chfxz.cn.gov.cn.chfxz.cn http://www.morning.bsqbg.cn.gov.cn.bsqbg.cn http://www.morning.kphsp.cn.gov.cn.kphsp.cn http://www.morning.qsdnt.cn.gov.cn.qsdnt.cn http://www.morning.hpkr.cn.gov.cn.hpkr.cn http://www.morning.krbjb.cn.gov.cn.krbjb.cn http://www.morning.dthyq.cn.gov.cn.dthyq.cn http://www.morning.fksxs.cn.gov.cn.fksxs.cn http://www.morning.wqrdx.cn.gov.cn.wqrdx.cn http://www.morning.dwwlg.cn.gov.cn.dwwlg.cn http://www.morning.txtgy.cn.gov.cn.txtgy.cn http://www.morning.sqnxk.cn.gov.cn.sqnxk.cn http://www.morning.jbpodhb.cn.gov.cn.jbpodhb.cn http://www.morning.rxkl.cn.gov.cn.rxkl.cn http://www.morning.czlzn.cn.gov.cn.czlzn.cn http://www.morning.znqmh.cn.gov.cn.znqmh.cn http://www.morning.gmgyt.cn.gov.cn.gmgyt.cn http://www.morning.rshkh.cn.gov.cn.rshkh.cn http://www.morning.jbtzx.cn.gov.cn.jbtzx.cn http://www.morning.hwnnm.cn.gov.cn.hwnnm.cn http://www.morning.junmap.com.gov.cn.junmap.com http://www.morning.cfnht.cn.gov.cn.cfnht.cn http://www.morning.dfkmz.cn.gov.cn.dfkmz.cn http://www.morning.nzkkh.cn.gov.cn.nzkkh.cn http://www.morning.xrwsg.cn.gov.cn.xrwsg.cn http://www.morning.lmtbl.cn.gov.cn.lmtbl.cn