`
chriszeng87
  • 浏览: 745343 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Android中Thread、Handler、Looper、MessageQueue的原理分析

阅读更多

在Android开发当中,Thread、Handler、Looper这几个类是特别常见,在刚开始学习Android的时候对这些类可能并不是很清晰。下面我们就一起从源码的角度剖析一下这几个类的工作原理。

Thread 

首先是Thread, 我们都知道一个Thread就是一个线程对象,只要在run方法中填写自己的代码然后启动该线程就可以实现多线程操作。例如 :

 

[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. new Thread(){  
  2.     public void run() {  
  3.         // 耗时的操作  
  4.     };  
  5. }.start();  

我们知道,针对上面的代码中,当执行完run中的操作时,整个线程就会结束,并不会一直执行下去。而我们的应用程序会一直执行,除非你退出或者应用程序抛出异常。这又引入了另外一个概念,即消息队列。在Android应用启动时,会默认有一个主线程(UI线程),在这个线程中会关联一个消息队列,所有的操作都会被封装成消息然后交给主线程来处理。为了保证主线程不会主动退出,会将取消息的操作放在一个死循环中,这样程序就相当于一直在执行死循环,因此不会退出。

 

示例图如下 :


Android应用程序的入口为ActivityThread.main方法,详情请参考Android应用程序进程启动过程的源代码分析,UI线程的消息循环就是在这个方法中创建的,源码如下:

 

[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. public static void main(String[] args) {  
  2.      SamplingProfilerIntegration.start();  
  3.      CloseGuard.setEnabled(false);  
  4.   
  5.      Environment.initForCurrentUser();  
  6.   
  7.      // Set the reporter for event logging in libcore  
  8.      EventLogger.setReporter(new EventLoggingReporter());  
  9.   
  10.      Process.setArgV0("<pre-initialized>");  
  11.   
  12.      Looper.prepareMainLooper();// 1、创建消息循环Looper  
  13.   
  14.      ActivityThread thread = new ActivityThread();  
  15.      thread.attach(false);  
  16.   
  17.      if (sMainThreadHandler == null) {  
  18.          sMainThreadHandler = thread.getHandler(); // UI线程的Handler  
  19.      }  
  20.   
  21.      AsyncTask.init();  
  22.   
  23.      if (false) {  
  24.          Looper.myLooper().setMessageLogging(new  
  25.                  LogPrinter(Log.DEBUG, "ActivityThread"));  
  26.      }  
  27.   
  28.      Looper.loop();   // 2、执行消息循环  
  29.   
  30.      throw new RuntimeException("Main thread loop unexpectedly exited");  
  31.  }  

执行ActivityThread.main方法后,应用程序就启动了,并且会一直从消息队列中取消息,然后处理消息。那么系统是如何将消息投递到消息队列中的?又是如何从消息队列中获取消息并且处理消息的呢? 答案就是Handler。

 

 

Handler

在我们在子线程中执行完耗时操作后很多情况下我们需要更新UI,但我们都知道,不能在子线程中更新UI。此时最常用的手段就是通过Handler将一个消息post到UI线程中,然后再在Handler的handleMessage方法中进行处理。但是有一个点要注意,那就是该Handler必须在主线程中创建!!简单示例如下:

 

 

[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. class MyHandler extends Handler {  
  2.     @Override  
  3.     public void handleMessage(Message msg) {  
  4.       // 更新UI  
  5.         
  6.     }  
  7. }   
  8.   
  9. MyHandler mHandler = new MyHandler() ;  
  10. // 开启新的线程  
  11. new Thread(){  
  12.         public void run() {  
  13.             // 耗时操作  
  14.             mHandler.sendEmptyMessage(123) ;  
  15.         };  
  16.     }.start();  

 

为什么必须要这么做呢?其实每个Handler都会关联一个消息队列,消息队列被封装在Lopper中,而每个Looper又会关联一个线程(ThreadLocal),也就是每个消息队列会关联一个线程。Handler就是一个消息处理器,将消息投递给消息队列,然后再由对应的线程从消息队列中挨个取出消息,并且执行。默认情况下,消息队列只有一个,即主线程的消息队列,这个消息队列是在ActivityThread.main方法中创建的,通过Lopper.prepareMainLooper()来创建,然后最后执行Looper.loop()来启动消息循环。那么Handler是如何关联消息队列以及线程的呢?我们看看如下源码 :

 

[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. public Handler() {  
  2.     if (FIND_POTENTIAL_LEAKS) {  
  3.         final Class<? extends Handler> klass = getClass();  
  4.         if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&  
  5.                 (klass.getModifiers() & Modifier.STATIC) == 0) {  
  6.             Log.w(TAG, "The following Handler class should be static or leaks might occur: " +  
  7.                 klass.getCanonicalName());  
  8.         }  
  9.     }  
  10.   
  11.     mLooper = Looper.myLooper();   // 获取Looper  
  12.     if (mLooper == null) {  
  13.         throw new RuntimeException(  
  14.             "Can't create handler inside thread that has not called Looper.prepare()");  
  15.     }  
  16.     mQueue = mLooper.mQueue;       // 获取消息队列  
  17.     mCallback = null;  
  18. }  

从Handler默认的构造函数中我们可以看到,Handler会在内部通过Looper.getLooper()来获取Looper对象,并且与之关联,最重要的就是消息队列。那么Looper.getLooper()又是如何工作的呢?我们继续往下看.

 

 

[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. /** 
  2.  * Return the Looper object associated with the current thread.  Returns 
  3.  * null if the calling thread is not associated with a Looper. 
  4.  */  
  5. public static Looper myLooper() {  
  6.     return sThreadLocal.get();  
  7. }  
  8.   
  9. /** 
  10.  * Initialize the current thread as a looper, marking it as an 
  11.  * application's main looper. The main looper for your application 
  12.  * is created by the Android environment, so you should never need 
  13.  * to call this function yourself.  See also: {@link #prepare()} 
  14.  */  
  15. public static void prepareMainLooper() {  
  16.     prepare();  
  17.     setMainLooper(myLooper());  
  18.     myLooper().mQueue.mQuitAllowed = false;  
  19. }  
  20.   
  21. private synchronized static void setMainLooper(Looper looper) {  
  22.     mMainLooper = looper;  
  23. }  
  24.   
  25.  /** Initialize the current thread as a looper. 
  26.   * This gives you a chance to create handlers that then reference 
  27.   * this looper, before actually starting the loop. Be sure to call 
  28.   * {@link #loop()} after calling this method, and end it by calling 
  29.   * {@link #quit()}. 
  30.   */  
  31. public static void prepare() {  
  32.     if (sThreadLocal.get() != null) {  
  33.         throw new RuntimeException("Only one Looper may be created per thread");  
  34.     }  
  35.     sThreadLocal.set(new Looper());  
  36. }  

我们看到myLooper()方法是通过sThreadLocal.get()来获取的,关于ThreadLocal的资料请参考ThreadLocal多线程实例详解。那么Looper对象又是什么时候存储在sThreadLocal中的呢?  眼尖的朋友可能看到了,上面贴出的代码中给出了一个熟悉的方法,prepareMainLooper(),在这个方法中调用了prepare()方法,在这个方法中创建了一个Looper对象,并且将该对象设置给了sThreadLocal。这样,队列就与线程关联上了!!!不同的线程是不能访问对方的消息队列的。再回到Handler中来,消息队列通过Looper与线程关联上,而Handler又与Looper关联,因此Handler最终就和线程、线程的消息队列关联上了。这就能解释上面提到的问题了,“为什么要更新UI的Handler必须要在主线程中创建?”。就是因为Handler要与主线程的消息队列关联上,这样handleMessage才会执行在UI线程,此时更新UI才是线程安全的!!!

Looper与MessageQueue

创建了Looper后,如何执行消息循环呢?通过Handler来post消息给消息队列( 链表 ),那么消息是如何被处理的呢?答案就是在消息循环中,消息循环的建立就是通过Looper.loop()方法。源码如下 : 

 

[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. /** 
  2.  * Run the message queue in this thread. Be sure to call 
  3.  * {@link #quit()} to end the loop. 
  4.  */  
  5. public static void loop() {  
  6.     Looper me = myLooper();  
  7.     if (me == null) {  
  8.         throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");  
  9.     }  
  10.     MessageQueue queue = me.mQueue;    // 1、获取消息队列  
  11.       
  12.     // 代码省略  
  13.    
  14.       
  15.     while (true) {                   // 2、死循环,即消息循环  
  16.         Message msg = queue.next(); // 3、获取消息 (might block )  
  17.         if (msg != null) {  
  18.             if (msg.target == null) {  
  19.                 // No target is a magic identifier for the quit message.  
  20.                 return;  
  21.             }  
  22.   
  23.             long wallStart = 0;  
  24.             long threadStart = 0;  
  25.   
  26.             // This must be in a local variable, in case a UI event sets the logger  
  27.             Printer logging = me.mLogging;  
  28.             if (logging != null) {  
  29.                 logging.println(">>>>> Dispatching to " + msg.target + " " +  
  30.                         msg.callback + ": " + msg.what);  
  31.                 wallStart = SystemClock.currentTimeMicro();  
  32.                 threadStart = SystemClock.currentThreadTimeMicro();  
  33.             }  
  34.   
  35.             msg.target.dispatchMessage(msg);    // 4、处理消息  
  36.   
  37.            // 代码省略  
  38.               
  39.             msg.recycle();  
  40.         }  
  41.     }  
  42. }  

可以看到,loop方法中实质上就是建立一个死循环,然后通过从消息队列中挨个取出消息,最后处理消息的过程。对于Looper我们总结一下 : 通过Looper.prepare()来创建Looper对象(消息队列封装在Looper对象中),并且保存在sThreadLoal中,然后通过Looper.loop()来执行消息循环,这两步通常是成对出现的!!   

 


最后我们看看消息处理机制,我们看到代码中第4步通过msg.target.dispatchMessage(msg)来处理消息。其中msg是Message类型,我们看源码 :

 

[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. public final class Message implements Parcelable {  
  2.   
  3.     public int what;  
  4.   
  5.     public int arg1;   
  6.   
  7.     public int arg2;  
  8.   
  9.     public Object obj;  
  10.   
  11.   
  12.     int flags;  
  13.   
  14.     long when;  
  15.       
  16.     Bundle data;  
  17.       
  18.     Handler target;         // target处理  
  19.       
  20.     Runnable callback;      // Runnable类型的callback  
  21.       
  22.     // sometimes we store linked lists of these things  
  23.     Message next;           // 下一条消息,消息队列是链式存储的  
  24.   
  25.   
  26.     // 代码省略 ....  
  27.     }  


从源码中可以看到,target是Handler类型。实际上就是转了一圈,通过Handler将消息投递给消息队列,消息队列又将消息分发给Handler来处理。我们继续看

[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. /** 
  2.  * Subclasses must implement this to receive messages. 
  3.  */  
  4. public void handleMessage(Message msg) {  
  5. }  
  6.   
  7. private final void handleCallback(Message message) {  
  8.     message.callback.run();  
  9. }  
  10.   
  11. /** 
  12.  * Handle system messages here. 
  13.  */  
  14. public void dispatchMessage(Message msg) {  
  15.     if (msg.callback != null) {  
  16.         handleCallback(msg);  
  17.     } else {  
  18.         if (mCallback != null) {  
  19.             if (mCallback.handleMessage(msg)) {  
  20.                 return;  
  21.             }  
  22.         }  
  23.         handleMessage(msg);  
  24.     }  
  25. }  

可以看到,dispatchMessage只是一个分发的方法,如果Runnable类型的callback为空则执行handlerMessage来处理消息,该方法为空,我们会将更新UI的代码写在该函数中;如果callback不为空,则执行handleCallback来处理,该方法会调用callback的run方法。其实这是Handler分发的两种类型,比如我们post(Runnable callback)则callback就不为空,当我们使用Handler来sendMessage时通常不会设置callback,因此也就执行handlerMessage这个分支。我们看看两种实现 : 

 

 

[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. public final boolean post(Runnable r)  
  2. {  
  3.    return  sendMessageDelayed(getPostMessage(r), 0);  
  4. }  
  5. private final Message getPostMessage(Runnable r) {  
  6.     Message m = Message.obtain();  
  7.     m.callback = r;  
  8.     return m;  
  9. }  
  10.   
  11. public final boolean sendMessageDelayed(Message msg, long delayMillis)  
  12. {  
  13.     if (delayMillis < 0) {  
  14.         delayMillis = 0;  
  15.     }  
  16.     return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);  
  17. }  
  18.   
  19. public boolean sendMessageAtTime(Message msg, long uptimeMillis)  
  20. {  
  21.     boolean sent = false;  
  22.     MessageQueue queue = mQueue;  
  23.     if (queue != null) {  
  24.         msg.target = this;      // 设置消息的target为当前Handler对象  
  25.         sent = queue.enqueueMessage(msg, uptimeMillis);  // 将消息插入到消息队列  
  26.     }  
  27.     else {  
  28.         RuntimeException e = new RuntimeException(  
  29.             this + " sendMessageAtTime() called with no mQueue");  
  30.         Log.w("Looper", e.getMessage(), e);  
  31.     }  
  32.     return sent;  
  33. }  


可以看到,在post(Runnable r)时,会将Runnable包装成Message对象,并且将Runnable对象设置给Message对象的callback字段,最后会将该Message对象插入消息队列。sendMessage也是类似实现 : 

 

 

[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. public final boolean sendMessage(Message msg)  
  2. {  
  3.     return sendMessageDelayed(msg, 0);  
  4. }  

不管是post一个Runnbale还是Message,都会调用sendMessageDelayed(msg, time)方法。

 

子线程中创建Handler为何会抛出异常 ?

我们看如下代码 : 
[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. new Thread(){  
  2.     Handler handler = null;  
  3.     public void run() {  
  4.         handler = new Handler();  
  5.     };  
  6. }.start();  
上面的代码有问题吗 ?
如果你能够发现并且解释上述代码的问题,那么应该说您对Handler、Looper、Thread这几个概念已经很了解了。如果您还不太清楚,那么我们一起往下学习。
前面说过,Looper对象是ThreadLocal的,即每个线程都有自己的Looper,这个Looper可以为空。但是当你要在子线程中创建Handler对象时,如果Looper为空,那么就会抛出“Can't create handler inside thread that has not called Looper.prepare()”异常,为什么会这样呢?我们一起看源码吧。
[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. /** 
  2.   * Default constructor associates this handler with the queue for the 
  3.   * current thread. 
  4.   * 
  5.   * If there isn't one, this handler won't be able to receive messages. 
  6.   */  
  7.  public Handler() {  
  8.     // 代码省略   
  9.   
  10.      mLooper = Looper.myLooper();    // 获取myLooper  
  11.      if (mLooper == null) {  
  12.          throw new RuntimeException(  
  13.              "Can't create handler inside thread that has not called Looper.prepare()");// 抛出异常  
  14.      }  
  15.      mQueue = mLooper.mQueue;  
  16.      mCallback = null;  
  17.  }  

我们可以看到,当mLooper对象为空时,抛出了该异常。这是因为该线程中的Looper对象还没有创建,因此sThreadLocal.get()会返回null。解决方法如下 : 
[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. new Thread(){  
  2.     Handler handler = null;  
  3.     public void run() {  
  4.         Looper.prepare();    // 1、创建Looper,并且会绑定到ThreadLocal中  
  5.         handler = new Handler();  
  6.         Looper.loop();       // 2、启动消息循环  
  7.     };  
  8. }.start();  
在代码中我们加了2处,第一是通过Looper.prepare()来创建Looper,第二是通过Looper.loop()来启动消息循环。这样该线程就有了自己的Looper,也就是有了自己的消息队列。如果之创建Looper,而不启动消息循环,虽然不会抛出异常,但是你通过handler来post或者sendMessage也不会有效,因为虽然消息被追加到消息队列了,但是并没有启动消息循环,也就不会从消息队列中获取消息并且执行了!
 

总结

在应用启动时,会开启一个主线程(UI线程),并且启动消息循环,应用不停地从该消息队列中取出、处理消息达到程序运行的效果。Looper对象封装了消息队列,Looper对象是ThreadLocal的,不同线程之间的Looper对象不能共享与访问。而Handler通过与Looper对象绑定来实现与执行线程的绑定,handler会把Runnable(包装成Message)或者Message对象追加到与线程关联的消息队列中,然后在消息循环中挨个取出消息,并且处理消息。当Handler绑定的Looper是主线程的Looper,则该Handler可以在handleMessage中更新UI,否则更新UI则会抛出异常!
其实我们可以把Handler、Looper、Thread想象成一个生产线,工人(搬运工)相当于Handler,负责将货物搬到传输带上(Handler将消息传递给消息队列);传送带扮演消息队列的角色,负责传递货物,货物会被挨取出,并且输送到目的地 ( target来处理 );而货物到达某个车间后再被工人处理,车间就扮演了Thread这个角色,每个车间有自己独立的传送带,车间A的货物不能被车间B的拿到,即相当于ThreadLocal( 车间独有 )。
转自:http://blog.csdn.net/bboyfeiyu/article/details/38555547
 
分享到:
评论

相关推荐

    受激拉曼散射计量【Stimulated-Raman-Scattering Metrology】 附Matlab代码.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

    MMC整流器技术解析:基于Matlab的双闭环控制策略与环流抑制性能研究,Matlab下的MMC整流器技术文档:18个子模块,双闭环控制稳定直流电压,环流抑制与最近电平逼近调制,优化桥臂电流波形,高效

    MMC整流器技术解析:基于Matlab的双闭环控制策略与环流抑制性能研究,Matlab下的MMC整流器技术文档:18个子模块,双闭环控制稳定直流电压,环流抑制与最近电平逼近调制,优化桥臂电流波形,高效并网运行。,MMC整流器(Matlab),技术文档 1.MMC工作在整流侧,子模块个数N=18,直流侧电压Udc=25.2kV,交流侧电压6.6kV 2.控制器采用双闭环控制,外环控制直流电压,采用PI调节器,电流内环采用PI+前馈解耦; 3.环流抑制采用PI控制,能够抑制环流二倍频分量; 4.采用最近电平逼近调制(NLM), 5.均压排序:电容电压排序采用冒泡排序,判断桥臂电流方向确定投入切除; 结果: 1.输出的直流电压能够稳定在25.2kV; 2.有功功率,无功功率稳态时波形稳定,有功功率为3.2MW,无功稳定在0Var; 3.网侧电压电流波形均为对称的三相电压和三相电流波形,网侧电流THD=1.47%<2%,符合并网要求; 4.环流抑制后桥臂电流的波形得到改善,桥臂电流THD由9.57%降至1.93%,环流波形也可以看到得到抑制; 5.电容电压能够稳定变化 ,工作点关键词:MMC

    Boost二级升压光伏并网结构的Simulink建模与MPPT最大功率点追踪:基于功率反馈的扰动观察法调整电压方向研究,Boost二级升压光伏并网结构的Simulink建模与MPPT最大功率点追踪:基

    Boost二级升压光伏并网结构的Simulink建模与MPPT最大功率点追踪:基于功率反馈的扰动观察法调整电压方向研究,Boost二级升压光伏并网结构的Simulink建模与MPPT最大功率点追踪:基于功率反馈的扰动观察法调整电压方向研究,Boost二级升压光伏并网结构,Simulink建模,MPPT最大功率点追踪,扰动观察法采用功率反馈方式,若ΔP>0,说明电压调整的方向正确,可以继续按原方向进行“干扰”;若ΔP<0,说明电压调整的方向错误,需要对“干扰”的方向进行改变。 ,Boost升压;光伏并网结构;Simulink建模;MPPT最大功率点追踪;扰动观察法;功率反馈;电压调整方向。,光伏并网结构中Boost升压MPPT控制策略的Simulink建模与功率反馈扰动观察法

    STM32F103C8T6 USB寄存器开发详解(12)-键盘设备

    STM32F103C8T6 USB寄存器开发详解(12)-键盘设备

    2011-2020广东21市科技活动人员数

    科技活动人员数专指直接从事科技活动以及专门从事科技活动管理和为科技活动提供直接服务的人员数量

    Matlab Simulink仿真探究Flyback反激式开关电源性能表现与优化策略,Matlab Simulink仿真探究Flyback反激式开关电源的工作机制,Matlab Simulimk仿真

    Matlab Simulink仿真探究Flyback反激式开关电源性能表现与优化策略,Matlab Simulink仿真探究Flyback反激式开关电源的工作机制,Matlab Simulimk仿真,Flyback反激式开关电源仿真 ,Matlab; Simulink仿真; Flyback反激式; 开关电源仿真,Matlab Simulink在Flyback反激式开关电源仿真中的应用

    基于Comsol的埋地电缆电磁加热计算模型:深度解析温度场与电磁场分布学习资料与服务,COMSOL埋地电缆电磁加热计算模型:温度场与电磁场分布的解析与学习资源,comsol 埋地电缆电磁加热计算模型

    基于Comsol的埋地电缆电磁加热计算模型:深度解析温度场与电磁场分布学习资料与服务,COMSOL埋地电缆电磁加热计算模型:温度场与电磁场分布的解析与学习资源,comsol 埋地电缆电磁加热计算模型,可以得到埋地电缆温度场及电磁场分布,提供学习资料和服务, ,comsol;埋地电缆电磁加热计算模型;温度场分布;电磁场分布;学习资料;服务,Comsol埋地电缆电磁加热模型:温度场与电磁场分布学习资料及服务

    ibus-table-chinese-yong-1.4.6-3.el7.x64-86.rpm.tar.gz

    1、文件内容:ibus-table-chinese-yong-1.4.6-3.el7.rpm以及相关依赖 2、文件形式:tar.gz压缩包 3、安装指令: #Step1、解压 tar -zxvf /mnt/data/output/ibus-table-chinese-yong-1.4.6-3.el7.tar.gz #Step2、进入解压后的目录,执行安装 sudo rpm -ivh *.rpm 4、更多资源/技术支持:公众号禅静编程坊

    基于51单片机protues仿真的汽车智能灯光控制系统设计(仿真图、源代码)

    基于51单片机protues仿真的汽车智能灯光控制系统设计(仿真图、源代码) 一、设计项目 根据本次设计的要求,设计出一款基于51单片机的自动切换远近光灯的设计。 技术条件与说明: 1. 设计硬件部分,中央处理器采用了STC89C51RC单片机; 2. 使用两个灯珠代表远近光灯,感光部分采用了光敏电阻,因为光敏电阻输出的是电压模拟信号,单片机不能直接处理模拟信号,所以经过ADC0832进行转化成数字信号; 3. 显示部分采用了LCD1602液晶,还增加按键部分电路,可以选择手自动切换远近光灯; 4. 用超声模块进行检测距离;

    altermanager的企业微信告警服务

    altermanager的企业微信告警服务

    MyAgent测试版本在线下载

    MyAgent测试版本在线下载

    Comsol技术:可调BIC应用的二氧化钒VO2材料探索,Comsol模拟二氧化钒VO2的可调BIC特性研究,Comsol二氧化钒VO2可调BIC ,Comsol; 二氧化钒VO2; 可调BIC

    Comsol技术:可调BIC应用的二氧化钒VO2材料探索,Comsol模拟二氧化钒VO2的可调BIC特性研究,Comsol二氧化钒VO2可调BIC。 ,Comsol; 二氧化钒VO2; 可调BIC,Comsol二氧化钒VO2材料:可调BIC技术的关键应用

    C++学生成绩管理系统源码.zip

    C++学生成绩管理系统源码

    基于Matlab与Cplex的激励型需求响应模式:负荷转移与电价响应的差异化目标函数解析,基于Matlab与CPLEX的激励型需求响应负荷转移策略探索,激励型需求响应 matlab +cplex 激励

    基于Matlab与Cplex的激励型需求响应模式:负荷转移与电价响应的差异化目标函数解析,基于Matlab与CPLEX的激励型需求响应负荷转移策略探索,激励型需求响应 matlab +cplex 激励型需求响应采用激励型需求响应方式对负荷进行转移,和电价响应模式不同,具体的目标函数如下 ,激励型需求响应; matlab + cplex; 负荷转移; 目标函数。,Matlab与Cplex结合的激励型需求响应模型及其负荷转移策略

    scratch介绍(scratch说明).zip

    scratch介绍(scratch说明).zip

    深度学习模型的发展历程及其关键技术在人工智能领域的应用

    内容概要:本文全面介绍了深度学习模型的概念、工作机制和发展历程,详细探讨了神经网络的构建和训练过程,包括反向传播算法和梯度下降方法。文中还列举了深度学习在图像识别、自然语言处理、医疗和金融等多个领域的应用实例,并讨论了当前面临的挑战,如数据依赖、计算资源需求、可解释性和对抗攻击等问题。最后,文章展望了未来的发展趋势,如与量子计算和区块链的融合,以及在更多领域的应用前景。 适合人群:对该领域有兴趣的技术人员、研究人员和学者,尤其适合那些希望深入了解深度学习原理和技术细节的读者。 使用场景及目标:①理解深度学习模型的基本原理和结构;②了解深度学习模型的具体应用案例;③掌握应对当前技术挑战的方向。 阅读建议:文章内容详尽丰富,读者应在阅读过程中注意理解各个关键技术的概念和原理,尤其是神经网络的构成及训练过程。同时也建议对比不同模型的特点及其在具体应用中的表现。

    day02供应链管理系统-补充.zip

    该文档提供了一个关于供应链管理系统开发的详细指南,重点介绍了项目安排、技术实现和框架搭建的相关内容。 文档分为以下几个关键部分: 项目安排:主要步骤包括搭建框架(1天),基础数据模块和权限管理(4天),以及应收应付和销售管理(5天)。 供应链概念:供应链系统的核心流程是通过采购商品放入仓库,并在销售时从仓库提取商品,涉及三个主要订单:采购订单、销售订单和调拨订单。 大数据的应用:介绍了数据挖掘、ETL(数据抽取)和BI(商业智能)在供应链管理中的应用。 技术实现:讲述了DAO(数据访问对象)的重用、服务层的重用、以及前端JS的继承机制、jQuery插件开发等技术细节。 系统框架搭建:包括Maven环境的配置、Web工程的创建、持久化类和映射文件的编写,以及Spring配置文件的实现。 DAO的需求和功能:供应链管理系统的各个模块都涉及分页查询、条件查询、删除、增加、修改操作等需求。 泛型的应用:通过示例说明了在Java语言中如何使用泛型来实现模块化和可扩展性。 文档非常技术导向,适合开发人员参考,用于构建供应链管理系统的架构和功能模块。

    清华大学104页《Deepseek:从入门到精通》

    这份长达104页的手册由清华大学新闻与传播学院新媒体研究中心元宇宙文化实验室的余梦珑博士后及其团队精心编撰,内容详尽,覆盖了从基础概念、技术原理到实战案例的全方位指导。它不仅适合初学者快速了解DeepSeek的基本操作,也为有经验的用户提供了高级技巧和优化策略。

    MXTU MAX仿毒舌自适应主题源码 苹果CMSv10模板.zip

    主题说明: 1、将mxtheme目录放置根目录 | 将mxpro目录放置template文件夹中 2、苹果cms后台-系统-网站参数配置-网站模板-选择mxpro 模板目录填写html 3、网站模板选择好之后一定要先访问前台,然后再进入后台设置 4、主题后台地址: MXTU MAX图图主题,/admin.php/admin/mxpro/mxproset admin.php改成你登录后台的xxx.php 5、首页幻灯片设置视频推荐9,自行后台设置 6、追剧周表在视频数据中,节目周期添加周一至周日自行添加,格式:一,二,三,四,五,六,日

    基于matlab平台的数字信号处理GUI设计.zip

    运行GUI版本,可二开

Global site tag (gtag.js) - Google Analytics