1.1.背景

Android的事件处理的三种方法:

1、基于监听的事件处理机制

setOnClickListener,setOnLongClickListener、setOnTouchListener

注意:如果onTouchEvent方法return true,则单击事件和长摁事件不再执行;若onLongClick方法返回true,则单击事件不再处理。

2、基于回调的事件处理机制

需要定义继承组件的类,重写回调方法Touch方法执行时,先被Activity捕获,DispatchTouchEvent方法处理。return false,交给上层的onTouchEvent方法处理;return super.dispatchTouchEvent(ev),则传递给最外层的View。

View用Dispatch方法处理,return false,由上层的onTouchEvent方法处理。如果返回super.dispatchTouchEvent(ev),则本层的onInterceptTouchEvent拦截,如果拦截true,则拦截,false不拦截,传递给子View的DispatchTouchEvent处理。

常用的回调方法:onKeyDown,onKeyLongPress,onKeyUp,onTouchEvent,onTrackballEvent(轨迹球事件)监听和回调同时存在时,先调用监听。

1.2.Android基于监听

基于监听的时间处理机制模型

流程模型图

监听三要素:

Event source 事件源 Event 事件 Event Listener 事件监听器 下面我们来看一下点击事件和触摸事件的监听三要素具体是那部分:

  • 点击时间( 由于点击事件比较简单,系统已经帮我们处理了,并没有找到具体事件是哪个 )

  • 触摸事件

归纳:

事件监听机制是一种委派式的事件处理机制,事件源(组件)事件处理委托给事件监听器 当事件源发生指定事件时,就通知指定事件监听器,执行相应的操作

常⽤监听接⼝

View.OnClickListener 单击事件监听器必须实现的接⼝ View.OnCreateContextMenuListener 创建上下⽂菜单事件 View.OnFocusChangeListener 焦点改变事件 View.OnKeyListener 按键事件监听器 View.OnLongClickListener 长按事件监听器 View.OnTouchListener 触摸屏事件监听器

  • 基于监听的事件处理机制

⾸先,事件监听机制中由事件源,事件,事件监听器三类对象组成。

事件监听器处理流程:

  1. 为事件源(例如:button)设置⼀个监听器,⽤于监听⽤户的操作(点击操作等)
  2. ⽤户做出的操作触发事件源的监听器
  3. ⾃动⽣成对应的事件对象
  4. 将事件源对象作为参数传给事件监听器
  5. 事件监听器对事件对象进⾏判断,执⾏对应的事件处理器(处理⽅法)在此以OnClickListener单击事件为例使用intent来实现页面的跳转

内部类形式实现监听

<TextView
//id值android:android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="10dp"android:text="内部类"android:gravity="center"android:textSize="20dp"android:textColor="#fff"/>public class MainActivity extends AppCompatActivity{//定义一个TextView对象private TextView textView2;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);
//获得事件源textView = findViewById(R.id.test2);
//确定事件为点击事件,绑定监听器到事件源textView.setOnClickListener(new myListener());}//内部类实现页面跳转private class myListener implements View.OnClickListener {@Overridepublic void onClick(View v) {//采用显示Intent启动第二个页面
startActivity(new Intent(MainActivity.this,internalActivity.class));}}
}

匿名内部类实现

<TextViewandroid:android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="10dp"android:text="匿名内部类"android:gravity="center"android:textSize="20dp"android:textColor="#fff"/>public class MainActivity extends AppCompatActivity {private TextView textView1;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);
//获得事件源textView1 = findViewById(R.id.test3);//匿名内部类实现跳转 (实现监听器,绑定监听器到事件源要同步进行)textView1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {startActivity(new Intent(MainActivity.this,anonymousActivity.class));
}});}
}

类本身实现监听器

<TextViewandroid:android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="10dp"android:text="类本身打开浏览器"android:gravity="center"android:textSize="20dp"android:textColor="@color/colorWhite"/>public class MainActivity extends AppCompatActivity implements View.OnClickListener{private TextView textView2;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//获得事件源textView2 = findViewById(R.id.test4);//绑定监听器到事件源textView2.setOnClickListener(this);}
//类本身实现 浏览器跳转@Overridepublic void onClick(View v) {switch (v.getId()){
case R.id.test4:
//采用隐式intentIntent intent = new Intent();intent.setAction(Intent.ACTION_VIEW);intent.setData(Uri.parse("http://www.baidu.com"));startActivity(intent);break;}}
}

本节给大家介绍了Android中的事件处理机制,例子中的是onClickListener点击事件,当然除了这个以外还有其他的事件,比如onItemClickListener,凡是需要通过setXxxListener这些,基本上都是基于事件监听的!

1.3.Android基于回调

回调事件处理原理

监听事件处理是事件源与事件监听器分开的而基于回调的事件处理UI组件不但是事件源,而且还是事件监听器,通过组件的相关回调方法处理对应的事件。

回调事件应用步骤

Ⅰ. 自定义View类,继承自需要的View UI类。ex :自定义 MyButton按钮类 extends 基础Button类

Ⅱ. 复写回调函数。ex:public boolean onTouchEvent(MotionEvent event)

每一个事件回调方法都会返回一个boolean值,①.如果返回true:表示该事件已被处理,不再继续向外扩散,②.如果返回false:表示事件继续向外扩散

而说到基于回调就离不开监听机制

回调机制与监听机制的区别:

如果说事件监听机制是⼀种委托式的事件处理,那么回调机制则恰好与之相反:对于基于回调机制的事件处理模型来说,事件源与事件监听器是统⼀的,或者说事件监听器完全消失了。

当⽤户在GUI组件上激发某个事件时,组件⾃⼰特定的⽅法将会负责处理该事件。

监听机制的事件源与事件监听是分开的。我们需要自己设置一个监听器,回调机制的事件源与事件监听是绑定在一起的。

  • boolean类型

几乎所有基于回调的事件处理方法都有一个boolean类型的返回值,该返回值用于表示该处理方法是否能完全处理该事件。 如果处理事件的回调方法返回true,表明该处理方法已经完全处理改事件,该事件不会传播出去。 如果处理事件的回调方法返回false,表明该处理方法并未完全处理该事件,该事件会传播出去。 对于基于回调的时间传播而言,某组件上所发生的事件不仅会激发该组件上的回调方法,也会触发该组件所在Activity的回调方法——只要事件能传播到该Activity。

实例:

MyButton 子类

public class MyButton extends AppCompatButton {public MyButton(Context context , AttributeSet set){super(context , set);}@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event){super.onKeyDown(keyCode , event);Log.v("-MyButton-", "the onKeyDown in MyButton");// 返回false,表明并未完全处理该事件,该事件依然向外扩散return true;}
}
  • MainActivity
public class MainActivity extends AppCompatActivity {@Overridepublic void onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Button bn = (Button) findViewById(R.id.bn);Log.v("-Listener-", "the onKeyDown in Listener");// 为bn绑定事件监听器bn.setOnKeyListener(new OnKeyListener() {@Override
public boolean onKey(View source, int keyCode, KeyEvent event) {// 只处理按下键的事件if (event.getAction() == KeyEvent.ACTION_DOWN) {Log.v("-Listener-", "the onKeyDown in Listener");}// 返回false,表明该事件会向外传播return false;
}});}// 重写onKeyDown方法,该方法可监听它所包含的所有组件的按键被按下事件@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event){super.onKeyDown(keyCode , event);Log.v("-Activity-" , "the onKeyDown in Activity");//返回false,表明并未完全处理该事件,该事件依然向外扩散return true;}
}

这里是在模拟器里进行的测试,这里按下键盘(而不是点击),会看到 logcat 中的输出,如下:

V/-Listener-: the onKeyDown in Listener
V/-MyButton-: the onKeyDown in MyButton
V/-Activity-: the onKeyDown in Activity
  • Override组件类的事件处理函数实现事件的处理。
举例:

View类实现了KeyEvent.Callback接口中的一系列回调函数,因此,基于回调的事件处理机制通过自定义View来实现,自定义View时重写这些事件处理方法即可。

public interface Callback { // 几乎所有基于回调的事件处理函数都会返回一个boolean类型值,该返回值用于 // 标识该处理函数是否能完全处理该事件 ` `// 返回true,表明该函数已完全处理该事件,该事件不会传播出去 // 返回false,表明该函数未完全处理该事件,该事件会传播出去
booleanonKeyDown( int keyCode, KeyEvent event);
boolean onKeyLongPress( int keyCode, KeyEvent event);
boolean onKeyUp( int keyCode, KeyEvent event);
booleanonKeyMultiple( int keyCode, int count, KeyEvent event);
}public interface Callback {
// 几乎所有基于回调的事件处理函数都会返回一个boolean类型值,该返回值用于
// 标识该处理函数是否能完全处理该事件
// 返回true,表明该函数已完全处理该事件,该事件不会传播出去
// 返回false,表明该函数未完全处理该事件,该事件会传播出去boolean onKeyDown(int keyCode, KeyEvent event);
boolean onKeyLongPress( int keyCode, KeyEvent event);boolean onKeyUp( int keyCode, KeyEvent event);   boolean onKeyMultiple( int keyCode,  int count, KeyEvent event);
}public interface Callback { // 几乎所有基于回调的事件处理函数都会返回一个boolean类型值,该返回值用于 // 标识该处理函数是否能完全处理该事件 // 返回true,表明该函数已完全处理该事件,该事件不会传播出去 // 返回false,表明该函数未完全处理该事件,该事件会传播出去 boolean onKeyDown(int keyCode, KeyEvent event); boolean onKeyLongPress(int keyCode, KeyEvent event); boolean onKeyUp(int keyCode, KeyEvent event); boolean onKeyMultiple(int keyCode, int count, KeyEvent event); } public interface Callback {// 几乎所有基于回调的事件处理函数都会返回一个boolean类型值,该返回值用于// 标识该处理函数是否能完全处理该事件
// 返回true,表明该函数已完全处理该事件,该事件不会传播出去
// 返回false,表明该函数未完全处理该事件,该事件会传播出去boolean onKeyDown(int keyCode, KeyEvent event);boolean onKeyLongPress(int keyCode, KeyEvent event);boolean onKeyUp(int keyCode, KeyEvent event);boolean onKeyMultiple(int keyCode, int count, KeyEvent event);
}

比对

  • 基于监听器的事件模型符合单一职责原则,事件源和事件监听器分开实现。

  • Android的事件处理机制保证基于监听器的事件处理会优先于基于回调的事件处理被触发。

  • 某些特定情况下,基于回调的事件处理机制会更好的提高程序的内聚性。

1.4.Handler消息处理

什么是Handler

Handler是一个消息分发对象。

Handler是Android系统提供的一套用来更新UI的机制,也是一套消息处理机制,可以通过Handler发消息,也可以通过Handler处理消息。

Handler的工作原理

在下面介绍Handler机制前,首先得了解以下几个概念:

1.Message 消息,理解为线程间通讯的数据单元。例如后台线程在处理数据完毕后需要更新UI,则可发送一条包含更新信息的Message给UI线程。 Message Queue 消息队列,用来存放通过Handler发布的消息,按照先进先出执行。
2.Handler Handler是Message的主要处理者,负责将Message添加到消息队列以及对消息队列中的Message进行处理。
3.Looper 循环器,扮演Message Queue和Handler之间桥梁的角色,循环取出Message Queue里面的Message,并交付给相应的Handler进行处理。 线程 UI thread 通常就是main thread,而Android启动程序时会替它建立一个Message Queue。每一个线程里可含有一个Looper对象以及一个MessageQueue数据结构。在你的应用程序里,可以定义Handler的子类别来接收Looper所送出的消息。

Handler的运行流程

在子线程执行完耗时操作,当Handler发送消息时,将会调用 MessageQueue.enqueueMessage,向消息队列中添加消息。 当通过 Looper.loop开启循环后,会不断地从消息池中读取消息,即调用 MessageQueue.next, 然后调用目标Handler(即发送该消息的Handler)的 dispatchMessage方法传递消息, 然后返回到Handler所在线程,目标Handler收到消息,调用 handleMessage方法,接收消息,处理消息。

3.1.5.源码分析

在子线程创建Handler

class LooperThread extends Thread {public Handler mHandler;public void run() {Looper.prepare();mHandler = new Handler() {public void handleMessage(Message msg) {// process incoming messages here
}};Looper.loop();}
}

从上面可以看出,在子线程中创建Handler之前,要调用 Looper.prepare()方法,Handler创建后,还要调用 Looper.loop()方法。而前面我们在主线程创建Handler却不要这两个步骤,因为系统帮我们做了。

主线程的Looper

在ActivityThread的main方法,会调用 Looper.prepareMainLooper()来初始化Looper,并调用 Looper.loop()方法来开启循环。

public final class ActivityThread extends ClientTransactionHandler {// ... public static void main(String[] args) {// ... Looper.prepareMainLooper();// ... Looper.loop();}
}

1.5.Looper

从上可知,要使用Handler,必须先创建一个Looper。

初始化looper:

public final class Looper {public static void prepare() {prepare(true);}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));}public static void prepareMainLooper() {prepare(false);synchronized (Looper.class) {if (sMainLooper != null) {throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();}}private Looper(Boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread();}// ...
}

从上可以看出,不能重复创建Looper,每个线程只能创建一个。创建Looper,并保存在 ThreadLocal。其中ThreadLocal是线程本地存储区(Thread Local Storage,简称TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的TLS区域。

开启Looper

public final class Looper {// ... public static void loop() {// 获取TLS存储的Looper对象 final Looper me = myLooper();if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}final MessageQueue queue = me.mQueue;// 进入loop主循环方法 for (;;) {Message msg = queue.next();
// 可能会阻塞,因为next()方法可能会无线循环
if (msg == null) {// No message indicates that the message queue is quitting. 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);
}
// ...
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
try {// 获取msg的目标Handler,然后分发Message msg.target.dispatchMessage(msg);dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
}
finally {if (traceTag != 0) {Trace.traceEnd(traceTag);}
}
// ...
msg.recycleUnchecked();}}
}

1.6.Handler

创建Handler

public class Handler {// ... public Handler() {this(null, false);}public Handler(Callback callback, Boolean async) {// ... // 必须先执行Looper.prepare(),才能获取Looper对象,否则为null 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;// 消息队列,来自Looper对象 mCallback = callback;// 回调方法 mAsynchronous = async;// 设置消息是否为异步处理方式}
}

发送消息:

子线程通过Handler的post()方法或send()方法发送消息,最终都是调用 sendMessageAtTime()方法。

post方法:

public final Boolean post(Runnable r){return sendMessageDelayed(getPostMessage(r), 0);
}
public final Boolean postAtTime(Runnable r, long uptimeMillis){return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}
public final Boolean postAtTime(Runnable r, Object token, long uptimeMillis){return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
}
public final Boolean postDelayed(Runnable r, long delayMillis){return sendMessageDelayed(getPostMessage(r), delayMillis);
}
private static Message getPostMessage(Runnable r) {Message m = Message.obtain();m.callback = r;return m;
}

send方法

public final Boolean sendMessage(Message msg){return sendMessageDelayed(msg, 0);
}
public final Boolean sendEmptyMessage(int what){return sendEmptyMessageDelayed(what, 0);
}
public final Boolean sendEmptyMessageDelayed(int what, long delayMillis) {Message msg = Message.obtain();msg.what = what;return sendMessageDelayed(msg, delayMillis);
}
public final Boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {Message msg = Message.obtain();msg.what = what;return sendMessageAtTime(msg, uptimeMillis);
}
public final Boolean sendMessageDelayed(Message msg, long delayMillis){if (delayMillis < 0) {delayMillis = 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
sendMessageAtTime()
public Boolean sendMessageAtTime(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);
}
private Boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {msg.target = this;if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);
}

1.7.分发消息

loop()方法中,获取到下一条消息后,执行 msg.target.dispatchMessage(msg),来分发消息到目标Handler。

public class Handler {// ... public void dispatchMessage(Message msg) {if (msg.callback != null) {// 当Message存在回调方法,调用该回调方法
handleCallback(msg);} else {if (mCallback != null) {// 当Handler存在Callback成员变量时,回调其handleMessage()方法 if (mCallback.handleMessage(msg)) {return;}
}
// Handler自身的回调方法
handleMessage(msg);}}private static void handleCallback(Message message) {message.callback.run();}
}

1.8.Handler的简单使用

在子线程中,进行耗时操作,执行完操作后,发送消息,通知主线程更新UI。

public class Activity extends android.app.Activity {private Handler mHandler = new Handler(){@Override  public void handleMessage(Message msg) {super.handleMessage(msg);
// 更新UI}};@Override public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {super.onCreate(savedInstanceState, persistentState);setContentView(R.layout.activity_main);new Thread(new Runnable() {@Overridepublic void run() {// 执行耗时任务 ... // 任务执行完后,通知Handler更新UI Message message = Message.obtain();message.what = 1;mHandler.sendMessage(message);
}}).start();}
}

Android 核心笔记笔录

Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 性能优化篇:https://qr18.cn/FVlo89
Android 车载篇:https://qr18.cn/F05ZCM
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
Kotlin 篇:https://qr18.cn/CdjtAF
Flutter 篇:https://qr18.cn/DIvKma
OkHttp 篇:https://qr18.cn/DzrmMB
Gradle 篇:https://qr18.cn/Cw0pBD
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 面试题锦:https://qr18.cn/CKV8OZ

Framework事件机制—Android事件处理的三种方法相关推荐

  1. 【Android开发】三种方法实现Button点击事件响应

    目录 1.在xml中对onclick()进行指定方法 2.NEW一个OnClickListenner()接口实现 3.实现OnClickListener接口(Switch方法) Hello,你好呀,我 ...

  2. Chrome模拟手机浏览器(iOS/Android)的三种方法,亲测无误!

    各大网站都有推出自己的手机访问版本页面,不管是新闻类还是视频网站,我们在电脑是无法直接访问到手机网站的,比如我经常访问一个3g.qq.com这个手机站点,如果在电脑上直接打开它,则会跳转到其它页面,一 ...

  3. Android 手机app三种方法获取定位地址(自带API,外接SDK,获取外网IP)

    一.手机App定位在我看来有三种方法: 1.通过Android自带的API:LocationManager获取到经纬度,再通过Geocoder反地理位置查询到所在的地址. 2.外接SDK,如高德SDK ...

  4. android 计时器 三种,Android计时器的三种实现方式(Chronometer、Timer、handler)

    本文实例为大家分享了Android计时器的三种方法,具体内容如下 目录: 1.借助Timer实现 2.调用handler.sendMessagedely(Message msg, long delay ...

  5. canvas 判断哪个元素被点击_监听 Canvas 内部元素点击事件的三种方法

    canvas内部元素不能像DOM元素一样方便的添加交互事件监听,因为canvas内不存在"元素"这个概念,他们仅仅是canvas绘制出来的图形.这对于交互开发来说是一个必经障碍,想 ...

  6. 事件争夺战 Android事件处理机制与原理分析

    事件争夺战 Android事件处理机制与原理分析 文章目录 事件争夺战 Android事件处理机制与原理分析 View的继承关系 View的事件处理源码 总结: ViewGroup的事件分发源码 总结 ...

  7. Struts2中action接收参数的三种方法及ModelDriven跟Preparable接口结合JAVA反射机制的灵活用法...

    Struts2中action接收参数的三种方法及ModelDriven跟Preparable接口结合JAVA反射机制的灵活用法 www.MyException.Cn   发布于:2012-09-15 ...

  8. android全局计时_Android中使用定时器的三种方法

    本文实例为大家分享了Android中使用定时器的三种方法,供大家参考,具体内容如下 图示: 因为都比较简单,所以就直接贴代码(虑去再次点击停止的操作),有个全局的Handler负责接收消息更新UI 第 ...

  9. 21天学习之二(Android 10.0 SystemUI默认去掉底部导航栏的三种方法)

    活动地址:CSDN21天学习挑战赛 1.概述 在定制化开发中,在SystemUI的一些定制功能中,针对默认去掉底部导航栏的方法有好几种,StatusBar和DisplayPolicy.java中api ...

最新文章

  1. 使用条件卷积进行实例和全景分割
  2. python虚拟环境
  3. 单片机的就业方向是什么,搞单片机是青春饭吗?
  4. install g++ 出现“g++ : Depends: g++-4.8 (= 4.8.2-5~) but it is not going to be installed...解决方法
  5. Linux中输入输出重定向和管道
  6. oracle 迁库 教程,Oracle整库文件迁移步骤详解教程
  7. 苹果appID的获取方法
  8. SQL替换字段中的部分内容
  9. 【vscode】程序员居然用vscode听网易云
  10. 苹果手机计算机切换用户名,苹果ID如何切换账号?苹果手机切换ID登录使用教程...
  11. jQuery- 跟着李南江学编程
  12. 超好用的UWP应用推荐
  13. 移动硬盘计算机无图标,移动硬盘不显示盘符但右下角有显示USB图标解决方法
  14. 计算机的语言栏怎么更改,win7电脑语言栏不见了如何修复
  15. vue实现动态二维码完成签到功能
  16. Make Product Equal One(思维)
  17. android obb在哪,.obb是什么文件?obb文件怎么用/放在哪里
  18. 快问快答,MySQL面试夺命20问
  19. 强烈推荐 10 款 Mac 软件!
  20. Oracle修改内存大小

热门文章

  1. wordpress Disable Google Map 插件优化;禁用谷歌地图
  2. php随机数生成代码,PHP随机数生成代码与使用实例分析
  3. python求解整数线性规划
  4. MYSQL批量修改表前缀与表名sql语句
  5. 【转贴】游戏引擎大全
  6. 当把Java比喻成一个美少女,她有哪些性格?
  7. u盘安装linux卡logo,求助:U盘启动卡在LOGO
  8. Idea 中隐藏文件夹、文件的显示 idea 忽略文件
  9. 课程设计项目——基于3D建模技术的车位在线销售平台(移动端)
  10. 路由交换之静态路由配置