最新消息:20210816 当前crifan.com域名已被污染,为防止失联,请关注(页面右下角的)公众号

Java AWT: Delegation Event Model-2

工作和技术 crifan 1555浏览 0评论

Java AWT: Delegation Event Model-2



from jdk150 English Version

Last updated: February 3, 1997

Since many of the EventListener interfaces are designed to listen to multiple event subtypes (i.e. the MouseListener listens to mouse-down, mouse-up, mouse-enter, etc.), the AWT will provide a a set of abstract “adapter” classes, one which implements each listener interface. This will allow programs to easily subclass the Adapters and override ONLY the methods representing event types they are interested in.

The Adapter classes provided by AWT are as follows:

<code> <a href="mk:@MSITStore:C: Documents%20and%20Settings crifan_li 桌面 jdk150%20English%20Version.chm::/jdk150/api/java/awt/event/ComponentAdapter.html">java.awt.event.ComponentAdapter</a>
</code>
<code> <a href="mk:@MSITStore:C: Documents%20and%20Settings crifan_li 桌面 jdk150%20English%20Version.chm::/jdk150/api/java/awt/event/ContainerAdapter.html">java.awt.event.ContainerAdapter</a>
</code>
<code> <a href="mk:@MSITStore:C: Documents%20and%20Settings crifan_li 桌面 jdk150%20English%20Version.chm::/jdk150/api/java/awt/event/FocusAdapter.html">java.awt.event.FocusAdapter</a>
</code>
<code> <a href="mk:@MSITStore:C: Documents%20and%20Settings crifan_li 桌面 jdk150%20English%20Version.chm::/jdk150/api/java/awt/event/KeyAdapter.html">java.awt.event.KeyAdapter</a>
</code>
<code> <a href="mk:@MSITStore:C: Documents%20and%20Settings crifan_li 桌面 jdk150%20English%20Version.chm::/jdk150/api/java/awt/event/MouseAdapter.html">java.awt.event.MouseAdapter</a>
</code>
<code> <a href="mk:@MSITStore:C: Documents%20and%20Settings crifan_li 桌面 jdk150%20English%20Version.chm::/jdk150/api/java/awt/event/MouseMotionAdapter.html">java.awt.event.MouseMotionAdapter</a>
</code>
<code> <a href="mk:@MSITStore:C: Documents%20and%20Settings crifan_li 桌面 jdk150%20English%20Version.chm::/jdk150/api/java/awt/event/WindowAdapter.html">java.awt.event.WindowAdapter</a>
</code>

Note: There are no default Adapters provided for the semantic listeners, since each of those only contain a single method and an adapter would provide no real value.

Filtering for Performance

One of the great benefits of the new model is that since listeners are registered to handle specific event types, it’s relatively easy for the toolkit to filter the events and deliver ONLY the events a component is interested in. This was not true with the old model!. Filtering should improve performance, especially with high frequency type events, such as mouse-moves.

All platforms should see some performance improvement from reduced event traffic, but the Solaris implementation should gain exceptional improvement since it’s a network-based window system. Following is some sample code that uses the new model:

<code> 
import java.awt.*;
import java.awt.event.*;

public class App {
&nbsp;&nbsp;&nbsp;&nbsp; public void search() { 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* do search operation ...*/ 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("Searching...");
&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp;&nbsp; public void sort() { 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* do sort operation ...*/ 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println("Sorting....");
&nbsp;&nbsp;&nbsp;&nbsp; }

&nbsp;&nbsp;&nbsp;&nbsp; static public void main(String args[]) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; App app = new App();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GUI gui = new GUI(app);
&nbsp;&nbsp;&nbsp;&nbsp; }
}

<strong>class Command implements ActionListener&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp; static final int SEARCH = 0;
&nbsp;&nbsp;&nbsp;&nbsp; static final int SORT = 1;
&nbsp;&nbsp;&nbsp;&nbsp; int id;
&nbsp;&nbsp;&nbsp;&nbsp; App app;

&nbsp;&nbsp;&nbsp;&nbsp; public Command(int id, App app) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.id = id;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.app = app;
&nbsp;&nbsp;&nbsp;&nbsp; }

&nbsp;&nbsp;&nbsp;&nbsp; public void actionPerformed(ActionEvent e) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; switch(id) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case SEARCH: 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app.search();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case SORT:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; app.sort();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp;&nbsp; }
}</strong>

class GUI {

&nbsp;&nbsp;&nbsp;&nbsp; public GUI(App app) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Frame f = new Frame();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; f.setLayout(new FlowLayout());&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong>Command searchCmd = new Command(Command.SEARCH, app);</strong>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong>Command sortCmd = new Command(Command.SORT, app);</strong>

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Button b;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; f.add(b = new Button("Search"));
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong>b.addActionListener(searchCmd);</strong>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; f.add(b = new Button("Sort"));
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong>b.addActionListener(sortCmd);</strong>

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; List l;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; f.add(l = new List());
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; l.add("Alphabetical");
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; l.add("Chronological");
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong>l.addActionListener(sortCmd);</strong>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; f.pack();

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; f.show();
&nbsp;&nbsp;&nbsp;&nbsp; }
}
</code>

Note in particular the following differences between this example and how this would have been implemented in the old model:

  • The application code/logic is completely separate from the GUI. The GUI can be modified (or alternatives provided) without touching the application-specific code. This benefit may not be as apparent from this over-simplified case. However, as applications get larger and more complex, this becomes extremely important.
  • Absolutely no subclassing of AWT components is required.
  • ONLY the action events are delivered to this program; in the old model all other events would have also been delivered even though they performed no required function.
  • This code is not necessarily leaner than the code that would have been required for the old event model. This is mostly due to the requirement for writing the adapter class which listens to the events and dispatches them into application commands appropriately. The adapter classes are required because the Java language supports neither method references nor closures. In this context, it was believed that using interfaces/adapters would be the most type-safe option for making connections between objects. This code could be condensed by using the new JDK1.1 “Inner classes” java language feature.

Handling Events in Extended Components

For Java programs which are extending component classes via subclassing, it would be burdensome to require the registration of separate listener objects to respond to events. Therefore, for this case, the AWT defines that each component provide specific protected methods (that can be overridden by subclasses) which actually dispatch the events to listeners if they exist. This way a subclass can simply override one of these methods in order to process an event.

In order to make this as flexible as possible, this event processing capability is provided in two levels. The first is a single method on all components:

<code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="mk:@MSITStore:C: Documents%20and%20Settings crifan_li 桌面 jdk150%20English%20Version.chm::/jdk150/api/java/awt/Component.html#processEvent(java.awt.AWTEvent)">protected void processEvent(AWTEvent)</a>
</code>

All events for a component are first funneled through this method so that subclasses can choose to handle all events in a single place (similar to the 1.0 model’s “handleEvent” with the main difference being that events are NOT propagated up the containment hierarchy in the new model).

The second option for processing events is provided at the event class level; there is a separate method for each class of event handled by that component:

<code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; protected void process<gt></gt>EventClass<lt></lt>(<gt></gt>EventClass<lt></lt>)
</code>

For example, the java.awt.List component has the following event-class processing methods:

<code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="mk:@MSITStore:C: Documents%20and%20Settings crifan_li 桌面 jdk150%20English%20Version.chm::/jdk150/api/java/awt/List.html#processActionEvent(java.awt.event.ActionEvent)">protected void processActionEvent(ActionEvent e)</a>

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="mk:@MSITStore:C: Documents%20and%20Settings crifan_li 桌面 jdk150%20English%20Version.chm::/jdk150/api/java/awt/List.html#processItemEvent(java.awt.event.ItemEvent)">protected void processItemEvent(ItemEvent e)</a>
</code>

By default, the single processEvent method will invoke the proper event-class processing method. The event-class processing method by default will invoke any listeners which are registered. It’s important to remember that these methods perform a critical function in the event processing for an AWT component and so if you override them you should remember to call the superclass’s method somewhere within your own!

Selecting for Event Types

One of the goals of the listener model is to improve performance by NOT delivering events which components are not interested in. By default, if a listener type is not registered on a component, then those events will NOT be delivered and these processing methods will therefore NOT be called. So if you are using this extension mechanism for event-handling, you’ll need to select for the specific types of events your component wishes to receive (in case no listener is registered). This can be done by using the following method on java.awt.Component:

<code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="mk:@MSITStore:C: Documents%20and%20Settings crifan_li 桌面 jdk150%20English%20Version.chm::/jdk150/api/java/awt/Component.html#enableEvents(long)">protected final void enableEvents(long eventsToEnable)</a>
</code>

The parameter to this method is a bitwise mask of the event types you wish to enable. The event masks are defined in java.awt.AWTEvent. Note that changing this mask will not affect the delivery of events to listeners — it only controls the delivery to the component’s processing methods. The bottom line is that the set of events which are delivered to processEvent() is defined by the union of event types which have listeners registered and event types explicitly turned on via enableEvents().

Example using Extension Mechanism

Following is an example of how this extension mechanism may be used. For Example, if a subclass of java.awt.Canvas wishes to render some visual feedback when it receives/loses keyboard focus, it could do the following.

<code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp;&nbsp; public class TextCanvas extends Canvas {
 &nbsp;&nbsp; boolean haveFocus = false;

 &nbsp;&nbsp; public TextCanvas() {
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong>enableEvents(AWTEvent.FOCUS_EVENT_MASK); // ensure we get focus events</strong>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ...
 &nbsp;&nbsp; }
 &nbsp;&nbsp;<strong>protected void processFocusEvent(FocusEvent e) {</strong>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; switch(e.getID()) {
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case FocusEvent.FOCUS_GAINED:
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; haveFocus = true;
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; case FocusEvent.FOCUS_LOST:
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; haveFocus = false;
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; repaint(); // need to repaint with focus feedback on or off...

 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong>super.processFocusEvent(e); // let superclass dispatch to listeners</strong>
 &nbsp;&nbsp;<strong>}</strong>
 &nbsp;&nbsp; public void paint(Graphics g) {
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (haveFocus) {
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // render focus feedback...
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }
 &nbsp;&nbsp; }
 &nbsp;&nbsp; ...rest of TextCanvas class...
&nbsp;&nbsp;&nbsp;&nbsp; }

</code>

A Word of Caution

In general we recommend you use the delegation-based listener model for most of your basic event handling needs and reserve the use of the above when you are truly extending the look or behavior of a component. This is because the above mechanism suffers from some of the same ills as the 1.0 event model (complex, error-prone logic in processing methods, forgetting to call “super.processEvent”, etc.), and unless you clearly understand what you are doing, your program may not behave as you expect!

An alternative to using the above approach would be to simply have your component subclass implement the particular listener interface for the events it wishes to receive and then register itself as a listener. For example, the above code example would be rewritten to:

<code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
&nbsp;&nbsp;&nbsp;&nbsp; public class TextCanvas extends Canvas <strong>implements FocusListener</strong> {
 &nbsp;&nbsp; boolean haveFocus = false;

 &nbsp;&nbsp; public TextCanvas() {
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong>addFocusListener(this); // ensure we get focus events</strong>
 &nbsp;&nbsp; }
 &nbsp;&nbsp;<strong>public void focusGained(FocusEvent e) {</strong>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; haveFocus = true;
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; repaint();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }        
 &nbsp;&nbsp;<strong>public void focusLost(FocusEvent e) {</strong>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; haveFocus = false; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; repaint(); 
 &nbsp;&nbsp; } &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
 &nbsp;&nbsp; public void paint(Graphics g) {
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (haveFocus) {
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // render focus feedback...
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }
 &nbsp;&nbsp; }
 &nbsp;&nbsp; ...rest of TextCanvas class...
&nbsp;&nbsp;&nbsp;&nbsp; }
</code>

Consuming Events

There are cases where programs need to prevent certain types of events from being processed normally by a component (i.e. a builder wants to use mouse events to enable a user to graphically move a button around and it wants to prevent the mouse press from ‘pushing’ the button).

We have explicitly enabled this capability for input events only by providing two methods on java.awt.event.InputEvent:

<code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="mk:@MSITStore:C: Documents%20and%20Settings crifan_li 桌面 jdk150%20English%20Version.chm::/jdk150/api/java/awt/event/InputEvent.html#consume()">public void consume()</a>

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="mk:@MSITStore:C: Documents%20and%20Settings crifan_li 桌面 jdk150%20English%20Version.chm::/jdk150/api/java/awt/event/InputEvent.html#isConsumed()">public boolean isConsumed()</a>
</code>

Note that if some object ‘consumes’ an input event, it is strictly an indication to the source component that the event should not be processed in its default manner (i.e. consuming a mousePressed event on a Button will prevent it from being activated). The event will still be dispatched to all listeners registered, regardless of whether a listener in the chain consumes the event.

The Event Queue

Another feature of the 1.1 event model is the addition of an event queue class:

<code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</code>

This class provides a number of public instance methods to manipulate the queue:

<code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="mk:@MSITStore:C: Documents%20and%20Settings crifan_li 桌面 jdk150%20English%20Version.chm::/jdk150/api/java/awt/EventQueue.html#postEvent(java.awt.AWTEvent)">public synchronized void postEvent(AWTEvent e)</a>

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="mk:@MSITStore:C: Documents%20and%20Settings crifan_li 桌面 jdk150%20English%20Version.chm::/jdk150/api/java/awt/EventQueue.html#getNextEvent()">public synchronized AWTEvent getNextEvent()</a>

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="mk:@MSITStore:C: Documents%20and%20Settings crifan_li 桌面 jdk150%20English%20Version.chm::/jdk150/api/java/awt/EventQueue.html#peekEvent()">public synchronized AWTEvent peekEvent()</a>

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="mk:@MSITStore:C: Documents%20and%20Settings crifan_li 桌面 jdk150%20English%20Version.chm::/jdk150/api/java/awt/EventQueue.html#peekEvent()">public synchronized AWTEvent peekEvent(int eventID)</a>
</code>

Programs can actually use this class to instantiate their own event queue instances for the purpose of asynchronously posting events. The EventQueue class automatically instantiates an internal thread for dispatching the events appropriately.

In the default JDK implementation, all events generated on components are first posted to a special “system” EventQueue instance before being dispatched to their target component.

The Toolkit class provides a method to access the handle of the system EventQueue instance:

<code>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="mk:@MSITStore:C: Documents%20and%20Settings crifan_li 桌面 jdk150%20English%20Version.chm::/jdk150/api/java/awt/Toolkit.html#getSystemEventQueue()">public final EventQueue getSystemEventQueue()</a>
</code>

It would obviously be a security problem to allow untrusted applets to freely manipulate the system event queue, therefore the getSystemEventQueue() method is protected by a SecurityManager check which disallows applets direct access to the system queue. We realize that applets would also like access to an event queue which is scoped to their own containment hierarchies and we are working on an architecture to allow that for a follow-on release.


Compatibility with the old Model

Our intention is to maintain binary compatibility for programs written to the old model for the 1.1 release. However, we strongly recommend new java programs migrate to the new model. We do not encourage the explicit mixing of the two models within a single Applet. However, we realize that programs which code to the new model may need/wish to use existing GUI classes which still use the old model and so we have done our best to ensure this works (for example, the hotjava browser is a java application which will need to be able to load both 1.0 and 1.1 style applets).

The way this works is that the AWT will recognize a component as being either a 1.0-event-model “target” OR a 1.1-event-model “source”, but not both. A component is recognized to be a 1.1-event-model “source” by meeting one of the following conditions:

  1. A listener (of any kind) is registered
  2. An event type (of any kind) was explicitly enabled by calling enableEvents()

ELSE the component will be treated as a 1.0-event-model “target” and all events will be delivered to the 1.0 handleEvent method as before.

Note that this is an “all or nothing” distinction and that once the AWT determines a component is a particular event model type, ALL events on that component will be processed in that context. For example, if a TextField object has only a FocusListener registered, then only focus events will be dispatched to the textfield in the 1.1 mechanism and the old 1.0 handleEvent method will NEVER be called (not even for other event types!). So while it is possible to combine components which use the different models, it is not possible to get a single component to mix both models.

One key difference between the two models is that the old model would automatically propagate events up the containment hierarchy, while the new model does NOT propagate events in this way. The way this works for compatibility is that if an event originates on a component which is a 1.0-event-model “target”, then it WILL be propagated up the hierarchy in the 1.0 fashion, regardless of the event model type of its ancestor containers. If an event originates on a 1.1-event-model “source”, then that event will NOT propagate up the hierarchy, regardless of the event model type of its ancestor containers.

java.awt.EventQueue

Code Example

Adapters

转载请注明:在路上 » Java AWT: Delegation Event Model-2

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
79 queries in 0.165 seconds, using 22.17MB memory