Handler机制(一)

Handler基础

Handler机制是什么?

Handler是用来处理线程间通信的一套机制。

初级使用

第一步:在主线程中定义Handler

    private Handler mHandler = new Handler(Looper.myLooper()){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            if(msg.what == 0){
                ((Button) findViewById(R.id.bt_test)).setText((String)msg.obj); //定义的一个button
            }
        }
    };

第二步:在子线程中生成Message并发送

        findViewById(R.id.bt_test).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(2000);//模拟子线程耗时任务
                            Message msg = new Message();
                            msg.what = 0;
                            msg.obj = "end";
                            mHandler.sendMessage(msg);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }).start();
            }
        });

上面做了以下几个步骤:1.在主线程中创建Handler对象并实现handleMessage方法用来处理收到的Message消息。2.在子线程中创建Message消息并调用主线程中的Handler通过sendMessage发送给主线程。

Handler机制中的主要类

Message

Message用来封装线程之间的消息。Message会存储在MessageQuenue中,Message也是链表中的结点。
主要有以下数据

    public int what; //定义任务类型
    public int arg1; //发送Int类型消息
    public int arg2;
    public Object obj;//发送任意类型的消息
    /*package*/ Handler target; //用来处理消息的Handler对象
    /*package*/ Runnable callback;//用来处理消息的回调
     Message next;//指向下一个Message
     public long when;//注意,这是不是发送消息的时间,而是消息应该被处理的时间
     private static Message sPool;//用来存储使用过的Message对象
     private static int sPoolSize = 0;
     private static final int MAX_POOL_SIZE = 50;
Message的消息池设计

官方建议使用消息池来获取Message对象。消息池使用链表实现

private static final Object sPoolSync = new Object();//消息池的锁
private static Message sPool; //消息池
private static int sPoolSize = 0;//消息池中回收的消息数量
private static final int MAX_POOL_SIZE = 50;//消息池最大容量

sPool被static修饰, static 变量有以下特点:

  • 在类装载的时候进行初始化。
  • 多个实例的 static变量会共享同一块内存区域。
    所有Message对象共享这个消息池
    在使用消息池获得消息时,都会调用无参的obtain()方法。具体代码如下:
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next; //将消息池更新到下一个
                m.next = null;
                m.flags = 0; // 重新标识当前Message没有使用过
                sPoolSize--;
                return m;
            }
        }
        return new Message();//消息池为空,就直接创建一个新Message返回
    }

在Message消息回收中,消息的回收实际方法是:

    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE; //表示当前Message消息已经被使用过了
        //情空 Message中的数据
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;
        //将当前清空数据的的Message加入到消息池中
        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;//头插法,当前节点指向之前消息池的header
                sPool = this;//将消息池中的header替换为当前Message对象
                sPoolSize++;
            }
        }
    }

Handler核心方法

boolean sendMessage(@NonNull Message msg) :发送一个即时消息到消息队列,内部调用的是sendMessageDelayed(msg,0)
boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) :发送一个延时消息,当前时间+delayMillis时间后,handleMessage接收到消息,内部调用sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {
        msg.target = this; //给Message设置发送消息的Hanlder
        msg.workSourceUid = ThreadLocalWorkSource.getUid();
        // 如果此Handler是异步的,则发送的所有消息都是异步的。
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //核心步骤,将Message加入到消息队列
        return queue.enqueueMessage(msg, uptimeMillis);
    }

在这里插入图片描述

boolean post(@NonNull Runnable r) :将Runnable中的任务在Handler线程中执行,内部调用 sendMessageDelayed方法
在这里插入图片描述

MessageQueue

消息队列,用来存储Message消息,内部通过单链表实现。
为什么需要一个MessageQueue?
Handler在SendMessage时,会有延时消息,需要等待到指定时间发送,这个就需要MessageQueue来处理,存入和取出逻辑。Message需要延迟处理,那么MessageQueen应该通过时间戳的大小来顺序存储,时间戳小的Message放到队列的头部。

MessageQueue何时被初始化?

MessageQueue并不需要我们手动初始化,Handler中mQueue是来自于Looper,由Looper在构造函数中初始化。

    /**
     * 插入队列
     * @param msg message
     * @param when Handler handleMessage 时间
     * @return false 插入失败 成功
     */
    boolean enqueueMessage(android.os.Message msg, long when) {

        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }

        //加锁,线程同步,因为Handler可能在不同线程中调用这个方法
        synchronized (this) {
            //message 是 in-use状态时,是不允许入列
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }
            ///如果当前MessageQueen已经退出抛出异常并释放Message
            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(); //回收Message并放入Message回收池
                return false;
            }
            //将Message设置为in-use状态
            msg.markInUse();
            //设置Message应该被处理的时间
            msg.when = when;
            //获取MessageQueen的Header
            Message p = mMessages;
            //是否需要唤醒nativeWake方法
            boolean needWake;
            //p == null  代表链表为空
            // when == 0 表示要立即处理该消息
            //when < p.when 表示Message的时间戳比当前队列的Header的时间戳小,那么应该插入到链表最前面
            //满足以上任意一个条件,应该将该msg插入到队列头部
            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 {
               //需要唤起nativeWake,需要满足一下3个条件:线程已经被阻塞&&Handler为空&&异步消息
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                android.os.Message prev;
                //通过for循环将,当前msg插入到合适的位置中
                for (;;) {
                    prev = p;//从队列的Header开始查找
                    p = p.next;
                    //p == null 表示到队列末尾
                    //when < p.when 时,msg应该插入到p端的前面
                    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);
            }
        }
        return true;
    }

enqueueMessage方法做了哪些工作
1.判断需要入列的消息是否满足要求:1)msg.target != null 2)msg.isInUse()为false 3)MessageQueen的mQuitting 为 false
2.按照Message中的when加入队列中
3.判断是否需要调用nativeWake

既然有存消息入队列,那么也一定有方法取出消息,取出消息通过next方法

Message next() {
        //如果消息循环已经退出并被处理,请返回此处
        //mPtr是从native方法中得到的NativeMessageQueue地址
        //如果mptr等于0说明队列不存在或者被清除掉了
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
        //待处理的IdleHandler数量,因为代表数量,所以只有第一次初始化时为-1
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        //线程将被阻塞的时间
        //-1 一直被阻塞
        //0 : 不阻塞
        //>:0 :阻塞nextPollTimeoutMillis毫秒
        int nextPollTimeoutMillis = 0;
        //进入死循环
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            //阻塞线程操作
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                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) {
                    if (now < msg.when) { //当前时间小于msg执行的时间
                        // Message未到执行时间,计算线程需要堵塞的时间
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 已经到执行时间,可以直接取出Message
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        //将msg行消息队列中剥离出来
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        //设置in-use状态
                        msg.markInUse();
                        //返回取出的消息,结束循环,结束next()方法
                        return msg;
                    }
                } else {
                    // 消息队列为空,,nextPollTimeoutMillis为-1,让线程一直阻塞
                    nextPollTimeoutMillis = -1;
                }

                // 如果队列已经退出了直接注销和结束方法
                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 android.os.MessageQueue.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 android.os.MessageQueue.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;
        }
    }

从代码上面可以看出,当MessageQueen中没有合适的Message出列,会堵塞线程直到有合适的Message出列。

Looper

Looper翻译过来就是循环器,我们上文已经分析了,消息载体(Message),消息队列(MessageQueen),那么如何从MessageQueen中取出消息并分发给MessageQueen?
这个部分工作由Looper完成。
初始化

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

Looper的构造函数是private的,外部无法通过构造函数直接创建Looper对象。Looper的对象是在prepare(boolean quitAllowed)方法中创建的

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {//判断是否已经存在Looper对象
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //将Looper对象存储到ThreadLocal中
        sThreadLocal.set(new Looper(quitAllowed));
    }

从上面代码可以看出Looper有以下特点:
1.一个线程只能有一个Looper
2.Looper对象是放到ThreadLocal中。
在这里插入图片描述
我们平常在主线程中使用Handler时并没有初始化Looper,在主线程中,何时进行初始化操作?
主线程中Looper的初始化操作是在ActivityThread中进行的。

 public static void main(String[] args) {
 /**
 *省略代码
 **/
 Looper.prepareMainLooper();
  /**
 *省略代码
 **/
  Looper.loop();
 }

可以看到主线程是调用Looper.prepareMainLooper()创建了Looper,而且不需要用户手动创建,在Activity启动时就已经创建了,子线程是需要通过prepare()手动创建Looper,并会创建一个MessageQueen能够退出的消息队列。

public static void prepare() {
        prepare(true);
    }

Looper创建后,怎么启动Looper读取MessageQueen?ActivityThread中调用了Looper.loop(),loop()方法就是循环读取MessageQueue中的Message

    public static void loop() {
        //获取当前线程的Looper对象
        final Looper me = myLooper();
        //如果线程中的Looper还未初始化,抛出异常
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //一个线程中只能调用一次loop()
        if (me.mInLoop) {
            Slog.w(TAG, "Loop again would have the queued messages be executed"
                    + " before this one completed.");
        }

        me.mInLoop = true;

        //得到当前线程的唯一标识(uid+pid),作用是下面每次循环时都检查一下线程有没有被切换
        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 && start'
        final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);

        me.mSlowDeliveryDetected = false;
        //开启死循环
        for (;;) {
            if (!loopOnce(me, ident, thresholdOverride)) { //返回false,就结束循环
                return;
            }
        }
    }

loop() 方法主要是就是开启死循环,调用loopOnce方法

    private static boolean loopOnce(final Looper me,
                                    final long ident, final int thresholdOverride) {
        //从MessageQueue中获取需要处理的Message,获取过程中可能会阻塞线程
        android.os.Message msg = me.mQueue.next(); // might block
        //消息为空,说明队列已经退出了,直接结束循环.结束方法
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return false;
        }

        /**
         * 中间省略部分代码,只要是记录分发时间和进行性能追踪,防止分发时间过慢
         */
        //向Handler分发数据
        try {
            //调用Handler的dispatchMessage方法
            msg.target.dispatchMessage(msg);
            if (observer != null) {
                observer.messageDispatched(token, msg);
            }
            //通知Observer消息分发结束
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } catch (Exception exception) {
            if (observer != null) {
                // 通知Observer消息分发异常
                observer.dispatchingThrewException(token, msg, exception);
            }
            throw exception;
        } finally {
            // 恢复当前线程的Uid
            ThreadLocalWorkSource.restore(origWorkSource);
            if (traceTag != 0) {
                // 结束追踪
                Trace.traceEnd(traceTag);
            }
        }
        
        //如果本次循环所在的线程和最开始的不一样,打印日志
        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;
    }

Looper的功能很简单,核心方法Looper.loop()就是不断地从消息队列中分发对应的宿主Handler,它与对应的MessageQueue息息相关,一起创建,一起退出。
Looper中使用ThreadLocal保证每一个线程只有一个Looper的实例;

整体流程

在这里插入图片描述
Handler发送Message到MessageQueue,Looper通过loop从MessageQueue中获取到时间的Message发送给Handler处理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/76662.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言 字符指针

1、介绍 概念&#xff1a; 字符指针&#xff0c;就是字符类型的指针&#xff0c;同整型指针&#xff0c;指针指向的元素表示整型一样&#xff0c;字符指针指向的元素表示的是字符。 假设&#xff1a; char ch a;char * pc &ch; pc 就是字符指针变量&#xff0c;字符指…

Spring系列篇--关于IOC【控制反转】的详解

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于Spring的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 一.什么是Spring 二.Spring的特点 三.什…

用node.js搭建一个视频推流服务

由于业务中有不少视频使用的场景&#xff0c;今天来说说如何使用node完成一个视频推流服务。 先看看效果&#xff1a; 这里的播放的视频是一个多个Partial Content组合起来的&#xff0c;每个Partial Content大小是1M。 一&#xff0c;项目搭建 &#xff08;1&#xff09;初…

Docker 安装和架构说明

Docker 并非是一个通用的容器工具&#xff0c;它依赖于已存在并运行的Linux内核环境。 Docker实质上是在已经运行的Liunx下制造了一个隔离的文件环境&#xff0c;因此他的执行效率几乎等同于所部署的linux主机。因此Docker必须部署在Linux内核系统上。如果其他系统想部署Docke…

使用maven打包时如何跳过test,有三种方式

方式一 针对spring项目&#xff1a; <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <skipTests>true</skipTests> </configuration> …

ios swift alert 自定义弹框 点击半透明部分弹框消失

文章目录 1.BaseAlertVC2.BindFrameNumAlertVC 1.BaseAlertVC import UIKitclass BaseAlertVC: GLBaseViewController {let centerView UIView()override func viewDidLoad() {super.viewDidLoad()view.backgroundColor UIColor(displayP3Red: 0, green: 0, blue: 0, alpha:…

【JVM】JVM垃圾收集器

文章目录 什么是JVM垃圾收集器四种垃圾收集器&#xff08;按类型分&#xff09;1.串行垃圾收集器(效率低&#xff09;2.并行垃圾收集器(JDK8默认使用此垃圾回收器&#xff09;3.CMS&#xff08;并发&#xff09;垃圾收集器(只针对老年代垃圾回收的&#xff09;4.G1垃圾回收器(在…

常见的路由协议之RIP协议与OSPF协议

目录 RIP OSPF 洪泛和广播的区别 路由协议是用于在网络中确定最佳路径的一组规则。它们主要用于在路由器之间交换路由信息&#xff0c;以便找到从源到目标的最佳路径。 常见的路由协议&#xff1a; RIP (Routing Information Protocol)&#xff1a;RIP 是一种基于距离向量算…

强训第33天

选择 C A ping是TCP/IP协议族的一部分&#xff0c;使用ICMP协议&#xff0c;ICMP底层使用IP协议。如果要ping其他网段&#xff0c;则需要设置网关。 如果是二层交换机故障&#xff0c;则ping同网段的也会不通。 C Dos攻击被称之为“拒绝服务攻击”&#xff0c;其目的是使计算机…

Windows 安装 pandoc 将 jupyter 导出 pdf 文件

Windows 安装 pandoc 将 jupyter 导出 pdf 文件 1. 下载 pandoc 安装文件2. 安装 pandoc3. 安装 nbconvert4. 使用 pandoc 1. 下载 pandoc 安装文件 访问 https://github.com/jgm/pandoc/releases&#xff0c;下载最新版安装文件&#xff0c;例如&#xff0c;3.1.6.1 版&#…

学习篇之React Fiber概念及原理

什么是React Fibber&#xff1f; React Fiber 是 React 框架的一种底层架构&#xff0c;为了改进 React 的渲染引擎&#xff0c;使其更加高效、灵活和可扩展。 传统上&#xff0c;React 使用一种称为堆栈调和递归算法来处理虚拟 DOM 的更新&#xff0c;这种方法在大型应用或者…

request发送http请求

今天正式开始为大家介绍接口自动化&#xff0c;相信很多做测试的朋友&#xff0c;都用过一些工具&#xff0c;比如jmeter&#xff0c;loadrunner&#xff0c;postman等等&#xff0c;所以今天先给那些基础不太好的同学&#xff0c;先讲讲postman如何来测接口以及如何用pthon代码…

div 中元素居中的N种常用方法

本文主要记录几种常用的div盒子水平垂直都居中的方法。本文主要参考了该篇博文并实践加以记录说明以加深理解记忆 css之div盒子居中常用方法大全 本文例子使用的 html body结构下的div 盒子模型如下&#xff1a; <body><div class"container"><div c…

前端跨域的原因以及解决方案(vue),一文让你真正理解跨域

跨域这个问题,可以说是前端的必需了解的,但是多少人是知其然不知所以然呢&#xff1f; 下面我们来梳理一下vue解决跨域的思路。 什么情况会跨域&#xff1f; ​ 跨域的本质就是浏览器基于同源策略的一种安全手段。所谓同源就是必须有以下三个相同点&#xff1a;协议相同、域名…

C语言 棱形图案

目录 一、问题分析 上部分&#xff1a; 下部分&#xff1a; 二、代码演示 一、问题分析 如上图所示&#xff0c;我们可以将棱形进行拆解&#xff0c;分为上下两个部分。 上部分&#xff1a; 通过观察&#xff0c;我们得到 单边空格数 上半部分总行数 - 行数 - 1 …

金蝶软件实现导入Excel数据分录行信息到单据体分录行中

>>>适合KIS云专业版V16.0|KIS云旗舰版V7.0|K/3 WISE 14.0等版本<<< 金蝶软件中实现[导入Excel数据业务分录行]信息到[金蝶单据体分录]中,在采购订单|采购入库单|销售订单|销售出库单等类型单据中,以少量的必要字段在excel表格中按模板填列好,很方便快捷地从…

dingding机器人

“自定义机器人”只支持消息发送&#xff0c;自动回复需要“企业内部机器人” 消息发送 import requests import jsonres requests.post(https://oapi.dingtalk.com/robot/send?access_token036a339axxx,data json.dumps({"text": {"content":"h…

DAY04_SpringMVC—SpringMVC简介PostMan和ApiFox工具使用SpringMVC请求与响应REST风格

目录 一 SpringMVC简介1 SpringMVC概述问题导入1.1 SpringMVC概述 2 入门案例问题导入2.0 回顾Servlet技术开发web程序流程2.1 使用SpringMVC技术开发web程序流程2.2 代码实现【第一步】创建web工程&#xff08;Maven结构&#xff09;【第二步】设置tomcat服务器&#xff0c;加…

ssm+vue网上花店设计源码和论文

ssmvue网上花店设计源码和论文017 开发工具&#xff1a;idea 数据库mysql5.7 数据库链接工具&#xff1a;navcat,小海豚等 技术&#xff1a;ssm 摘 要 网络技术和计算机技术发展至今&#xff0c;已经拥有了深厚的理论基础&#xff0c;并在现实中进行了充分运用&#xf…