您的位置:首页 > 移动开发 > Android开发

Android开发之消息处理机制

2013-07-19 14:40 423 查看

Android开发之消息处理机制(一)——Handler

/*

 *  Android开发之消息处理机制(一)——Handler

 *  北京Android俱乐部群:167839253  

 *  Created on: 2011-8-29

 *  Author: blueeagle

 *  Email: liujiaxiang@gmail.com

 */

       对于Android里的消息处理,涉及到Handler,Looper,Message,Message Queue等概念,先捋顺这些概念。

Message:消息,其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,终由Handler处理。

Handler:处理者,负责Message的发送及处理。使用Handler时,需要实现handleMessage(Message msg)方法来对特定的Message进行处理,例如更新UI等。

MessageQueue:消息队列,用来存放Handler发送过来的消息,并按照FIFO规则执行。当然,存放Message并非实际意义的保存,而是将Message以链表的方式串联起来的,等待Looper的抽取。

Looper:一个线程可以产生一个Looper对象,用来管理MessageQueue,它就像一个消息泵,不断地从MessageQueue中抽取Message执行。因此,一个MessageQueue需要一个Looper。

下面来先来着重说明Handler类。

       Android 里面对于异步消息的处理,提供了一套Handler的实现方案。Handler有很多适宜的应用和微妙之处,使它在和Thread以及Service等一起使用的时候达到很好的效果。

       下面先看看手册里对Handler是如何定义的:

A Handler allows you to send and process
Message
and Runnable objects associated with a thread's
MessageQueue
.
Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler, it is bound to the thread / message queue of the thread that is creating it -- from that point on, it will deliver messages and runnables
to that message queue and execute them as they come out of the message queue.

There are two main uses for a Handler: (1) to schedule messages and runnables to be executed as some point in the future; and (2) to enqueue an action to be performed on a different thread than your own.

Handler 允许 你去发送和处理  消息或者是发送处理绑定在线程消息队列中的rannable对象,每一个Handler实例都联系到一个单一的线程和那个线程的消息队列中。当创建一个新的Handler,他将绑定到这个线程或者是创建Hanlder的线程的消息队列。从这一点上来看,它会传递信息和消息队列的rannables并在消息弹出消息队列时执行它们。Handler有两个主要的用途:1是调度消息和runnables在未来执行的时间;2是为不同线程中所进行的活动排队。

云里雾里的,但是好像隐约知道他是什么意思了。继续理解下去:这里就是多线程的问题了,当我们启动一个Android应用程序的时候,Android会首先开启一个主线程,这个主线程的工作主要就是管理界面中的UI控件,进行事件分发,处理消息响应函数等。但是如果处理一个比较耗时的操作时,比如读取本地大文档,读取网络数据等等时,如果依然用主线程的话,就会出现问题,Android系统规定默认5S无反应的话,就会弹出强制关闭对话框。

在这个时候我们就需要另外开一个线程来处理耗时的工作,这与在学习SurfaceView的时候,我们开启了一个线程去处理频繁更新的操作有些类似。但是因为子线程涉及到UI更新,而更新UI只能在主线程中更新(Android主线程不是线程安全的),子线程中操作是危险的。Handler就是用来解决这个复杂问题而出现的。Handler运行在主线程中(UI线程中),它与子线程可以通过Message对象来传递数据,这个时候,Handler就承担着接受子线程传过来的(子线程用sendMessage()方法传递)Message对象(里面包含数据),把这些消息放入主线程队列中,配合主线程进行更新UI。

如下面的例子:

在主Activity中,定义一个TextView,我们来实时的更新这个TextView的内容。

[java]
view plaincopy

package com.blueeagle;  
  
import android.app.Activity;  
import android.os.Bundle;  
import android.os.Handler;  
import android.os.Looper;  
import android.os.Message;  
import android.view.View;  
import android.widget.Button;  
import android.widget.TextView;  
  
public class SendMessage extends Activity {  
    private TextView textView;  
    private MyHandler myHandler;//定义一个自己的Handle类  
    private Button button;  
    private MyThread m=new MyThread(); //定义一个自己的线程类  
    @Override  
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.main);   
        textView=(TextView)findViewById(R.id.text);  
        button=(Button)findViewById(R.id.startButton);   
        button.setOnClickListener(new View.OnClickListener() {   
        @Override  
        public void onClick(View arg0) {   
        myHandler=new MyHandler();   
        new Thread(m).start();   
        System.out.println("主线程运行ID:"+Thread.currentThread().getId());  
        }   
        });  
        }//在对UI进行更新时,执行时所在的线程为主UI线程   
    class MyHandler extends Handler{//继承Handler类时,必须重写handleMessage方法   
        public MyHandler(){  
              
        }  
        public MyHandler(Looper l){  
            super(l);  
            }  
        @Override  
        public void handleMessage(Message msg) {//执行接收到的通知,此时执行的顺序是按照队列进行,即先进先出   
            super.handleMessage(msg);  
            Bundle b=msg.getData();   
            String textStr1=b.getString("textStr");  
            SendMessage.this.textView.setText(textStr1);//更改TextView中的值  
            }   
        }//该线程将会在单独的线程中运行   
          
        class MyThread implements Runnable{  
            int i=1;   
            @Override  
            public void run() {  
                while(i<11){   
                    System.out.println("当前运行线程ID:"+Thread.currentThread().getId());  
                    try {  
                        Thread.sleep(1000);  
                        }  
                    catch(InterruptedException e){  
                        e.printStackTrace();   
                        }  
                    Message msg=new Message();   
                    Bundle b=new Bundle();   
                    b.putString("textStr", "线程运行"+i+"次");   
                    i++;   
                    msg.setData(b);  
                    SendMessage.this.myHandler.sendMessage(msg);//通过sendMessage向Handler发送更新UI的消息   
                    }  
                i=1;//下次启动线程时重新计数。  
                }  
            }  
        }   

 

XML文件:

[html]
view plaincopy

<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:orientation="vertical"  
    android:layout_width="fill_parent"  
    android:layout_height="fill_parent"  
    >  
<TextView    
    android:id="@+id/text"  
    android:layout_width="fill_parent"   
    android:layout_height="wrap_content"   
    android:text="@string/hello"  
    />  
<Button android:id="@+id/startButton" android:layout_height="wrap_content" android:text="Button"  android:layout_width="wrap_content"></Button>  
</LinearLayout>  

 

小结:

可以看到,屏幕上的TxtView在实时变化,考察线程ID可以得知是由哪一个线程来完成这项工作的。再次点击按钮后,则会触发新的线程去执行实时更新的操作。

Android开发之消息处理机制(二)——消息循环

/*

 *  Android开发之消息处理机制(二)——消息循环

 *  北京Android俱乐部群:167839253  

 *  Created on: 2011-9-1

 *  Author: blueeagle

 *  Email: liujiaxiang@gmail.com

 */

 

       先介绍一下如何在Android应用程序中使用后台线程和UI线程。

创建Android友好的后台线程时,最灵活的方式就是创建Handler子类的一个实例。每个Activity仅需要一个Handler对象,而且不需要手动注册他。

       后台线程可以与Handler通讯,Handler将在Activity的UI线程上执行自己所有的工作。这一点非常重要。因为UI的更改,只能发生在Activity的UI线程上。

       有两种与Handler通信的方式:消息和Runnable对象。

       关于消息:

       要向Handler发送一个Message,首先调用obtainMessage()从池中获取Message对象。obtainMessage()对象有许多特点,允许创建空的Message对象或填充了消息标示符和参数的对象。需要的Handler处理过程越复杂,就越需要将数据放在Message中,以帮助Handler区分不同的事件。

       然后可以通过消息队列将Message发送给Handler,此时需要使用sendMessage…()系列方法中的一个方法。

       sendMessage();立即将消息放在队列中。

       sendMessageAtFrontOfQueue();立即将消息放在队列中,并将其放在消息队列的最前面,这样该消息就会具有比所有其他消息更高的优先级。

       sendMessageAtTime();在规定的时间将消息放在队列中,这个时间用ms表示,基于系统正常工作时间。(SystemClock.uptimeMillis())

       sendMessageDelayed();在一段延迟之后将消息放在队列中,延迟用ms表示。

       要处理这些消息,Handler需要实现handleMessage(),将使用出现在消息队列中的每个消息来调用handleMessage()。此处Handler可以根据需要更新UI。但是,它仍然应该迅速完成此工作,因为在它完成之前,其他UI工作会暂停。

       关于Runnable :

       如果不愿意使用Message对象,也可以将Runnable对象传递给Handler,Handler将在ActivityUI线程上运行这些Runnable对象。Handler提供了一组post…()方法来传入Runnable对象供最终处理。

       对Android的消息循环机制的理解,可以仿照Windows消息循环的机制进行。

每个Handler都会和一个线程和线程的message queue关联起来,此时你可以传递messages 和 runnable对象到message queue中。后面可以从message queue中拿出该对象并且执行。Handler, Message queue这两个对象之间的交互,就涉及到了Looper这个东西。

关于Handler,Looper,Message Queue三者之间的关系,如下图所示:



Handler有很多构造函数

Handler在无参数的构造方法中调用Looper.myLooper()方法,里面就是从当前线程里面获取一个Looper的同时一并初始化MessageQueue,并且从中得到looper的MessageQueue。可以看出Handler就是Looper和MessageQueue的管理者和调度者。

       其中最重要的是sendMessageAtTime方法,当你往Handler中发送Message消息的时候,从代码看出他自己并不去处理Message,而是交给了MessageQueue,由queue.enqueueMessage来处理。

       Looper代码中有一个prepare()方法,通过ThreadLocal实现一个线程只有一个Looper。

Handler的消息发送

Handler是一个核心,负责message的创建,获得,处理。Handler的sendMessage()方法最终其实就是调用了queue.enqueueMessage(msg, uptimeMillis);把消息放入message queue中。Looper通过Looper.loop()检测到有消息并将消息广播后,Handler又负责接收到此消息并调用handleMessage进行处理接下来的事情。Message的创建一般都是通过如下代码建立的,把Handler传进去。

     public final Message obtainMessage()

               {

                 return Message.obtain(this);

       }

这种工作方式如下图所示:



Message在里面是一个链表的结构。在这个方法中,会首先去MessagePool(消息回收站)去找Message,如果有被回收的Message,则会将这个Message取出来进行再利用。如果没有被回收的,这时系统才会真正new一个Message对象出来当然MessagePool不是无限大的,它最多只能保存十条回收的消息,多出来的就直接删除了。

至此,我们看到,一个Message经由Handler创建、发送,MessageQueue的入队,Looper的抽取,又再一次地回到Handler进行处理。而绕的这一圈,也正好帮助我们将同步操作变成了异步操作。

在主线程(UI线程)里,如果创建Handler时不传入Looper对象,那么将直接使用主线程(UI线程)的Looper对象(系统已经帮我们创建了);在其它线程里,如果创建Handler时不传入Looper对象,那么,这个Handler将不能接收处理消息。在创建Handler之前,为该线程准备好一个Looper(Looper.prepare),然后让这个Looper跑起来(Looper.loop),抽取Message,这样,Handler才能正常工作。

因此,Handler处理消息总是在创建Handler的线程里运行。

下面根据上述理论知识,来举几个例子:

例1:

       在UI线程中调用Handler的Post方法。代码如下:

[java]
view plaincopy

/* 
 *  Android开发之消息处理机制(二)——消息循环 
 *  MessageCircle.java   
 *  Created on: 2011-9-2 
 *  Author: blueeagle 
 *  Email: liujiaxiang@gmail.com 
 */  
package blueeagle.com;  
  
import android.app.Activity;  
import android.os.Bundle;  
import android.os.Handler;  
  
public class MessageCircle extends Activity {  
    /** Called when the activity is first created. */  
    private Handler myHandler = new Handler();  
    private Runnable myRunnable = new Runnable(){  
        @Override  
        public void run(){  
            try{  
                Thread.sleep(1000);  
            }  
            catch(InterruptedException e){  
                e.printStackTrace();  
            }  
            System.out.println("Current Runnable Thread ID:"+Thread.currentThread().getId());  
        }  
    };  
    @Override  
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.main);          
        System.out.println("Current Activity Thread ID:"+Thread.currentThread().getId());  
        myHandler.post(myRunnable);  
    }      
}  

 

程序运行结果如下图:



在这个程序中,我们New了一个没有参数的Handler对象myHandler,这个myHandler自动与当前运行程序相关联,也就是说,这个myHandler将与当前运行的线程使用同一个消息队列,并且可以处理该队列中的消息。

在程序代码中,这个myHandler向消息队列中post了一个myRunnable对象。在myRunnable的run方法中,打印当前线程的ID。由结果得知,我们New出来的这个myHandler是把消息或者Runnable对象送到当前的线程中的。这里我们没有调用线程的start()函数,因此也就不会去创建一个新的线程。而是在原有线程的内部,直接调用run()方法。因此输出的ID是相同的。

那么如何创建新的线程呢?在Android开发之消息处理机制(一)中已经知道如何去创建一个新的线程,这里继续举一个例子来进行比较说明。

例2:

首先需要创建一个线程,这个线程可以是自己创建的线程,也可以是HandlerThread,这个线程需要继承于Thread类或者实现Runnable接口。其次需要有一个Handler,这个Handler可以是Handler也可以继承于Handler并且需要有一个有Looper参数的构造函数。然后就可以进行消息的分发执行了。

具体示例代码如下:

[java]
view plaincopy

/* 
 *  Android开发之消息处理机制(二)——消息循环 
 *  MessageCircle.java   
 *  Created on: 2011-9-2 
 *  Author: blueeagle 
 *  Email: liujiaxiang@gmail.com 
 */  
  
package blueeagle.com;  
  
import android.app.Activity;  
import android.os.Bundle;  
import android.os.Handler;  
import android.os.HandlerThread;  
import android.os.Looper;  
import android.os.Message;  
import android.widget.TextView;  
  
public class MessageCircle extends Activity {  
    public class MyOwnThread extends Thread{  
        int sleepSpan = 2000;//休眠时间  
        boolean flag;// 线程执行标志位  
        //----------------------------------//  
        //构造方法,初始化类的主要成员变量  
        public MyOwnThread(){  
            this.flag = true;  
        }  
        //----------------------------------//  
        //方法,线程执行方法  
        @Override  
        public void run(){  
            TextView myTextView = (TextView)findViewById(R.id.mytextview);  
            Looper.prepare();  
            int i = 1;  
            while(flag){  
                try{  
                    Thread.sleep(sleepSpan);//休眠一段时间  
                    System.out.println("Current MyOwnThread Thread ID: "+Thread.currentThread().getId());  
                    myTextView.setText(i);  
                    i++;  
                }  
                catch(Exception e){  
                    e.printStackTrace();//捕获异常并打印  
                }  
            }                 
            Looper.loop();  
        }  
    }  
    public class MyHandler extends Handler{  
        public MyHandler(){}  
        public MyHandler(Looper looper){  
            super(looper);  
        }  
        @Override  
        public void handleMessage(Message msg){  
            System.out.println("Current MyHandle Thread ID: "+Thread.currentThread().getId());  
            TextView myTextView = (TextView)findViewById(R.id.mytextview);  
            int i = 1;  
            while(true){  
                try{  
                    Thread.sleep(1000);//休眠一段时间  
                    System.out.println("Current MyHandle Thread ID: "+Thread.currentThread().getId());  
                    myTextView.setText("i");  
                    i++;  
                }  
                catch(Exception e){  
                    e.printStackTrace();//捕获异常并打印  
                }  
            }         
        }  
    }  
    /** Called when the activity is first created. */  
    private MyHandler myHandler = null;  
    @Override  
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.main);          
        System.out.println("Current Activity Thread ID:"+Thread.currentThread().getId());  
        //myHandler.post(myRunnable);  
        HandlerThread myThread = new HandlerThread("myThread");//生成一个HandlerThread对象,使用Looper来处理消息队列。  
        myThread.start();//启动这个线程  
        myHandler = new MyHandler(myThread.getLooper());//将一个线程绑定到myHandler这个对象上。  
        Message msg = myHandler.obtainMessage();//从myHandler这个对象中获取消息  
        msg.sendToTarget();//将msg对象发送给目标的Handler  
          
        //下面制造一个自己的线程  
        MyOwnThread myOwnThread = new MyOwnThread();  
        myOwnThread.start();  
    }      
}  

 

关于线程更新UI的方法,在(一)中已经说过。下面利用一个示例将消息循环过程进行展示,进而实现音乐播放,UI线程中更新歌词的例子来说明消息处理机制。例子较简单,没有实现歌词同步等内容。只是为了更深刻的理解线程运行情况。

按照上述理论:代码如下:

[java]
view plaincopy

/* 
 *  Android开发之消息处理机制(二)——消息循环 
 *  MusicPlayer.java   
 *  Created on: 2011-9-3 
 *  Author: blueeagle 
 *  Email: liujiaxiang@gmail.com 
 */  
  
package blueeagle.com;  
  
import android.app.Activity;  
import android.media.MediaPlayer;  
import android.os.Bundle;  
import android.os.Handler;  
import android.os.Looper;  
import android.os.Message;  
import android.view.View;  
import android.view.View.OnClickListener;  
import android.widget.Button;  
import android.widget.TextView;  
  
public class MusicPlayer extends Activity {  
    /** Called when the activity is first created. */  
    Button myButton;  
    TextView myTextView;  
    private UIUpdateThread myUpdateThread = new UIUpdateThread(); //定义一个自己的UI更新的线程类  
    MyHandler mHandler = new MyHandler();//定义自己的一个Handler类  
    @Override  
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.main);  
        myButton = (Button)findViewById(R.id.myButton);  
        myTextView = (TextView)findViewById(R.id.myTextView);  
        myButton.setOnClickListener(new OnClickListener(){  
            @Override  
            public void onClick(View v) {  
                // TODO Auto-generated method stub   
                System.out.println("主线程运行ID:"+Thread.currentThread().getId());  
                new Thread(myUpdateThread).start();//起一个自己定义的线程  
            }             
        });  
    }             
    class MyHandler extends Handler{//继承Handler类时,必须重写handleMessage方法   
        public MyHandler(){  
              
        }  
        public MyHandler(Looper l){  
            super(l);  
            }  
        @Override  
        public void handleMessage(Message msg) {//执行接收到的通知,此时执行的顺序是按照队列进行,即先进先出   
            myTextView.setText(msg.toString());  
            System.out.println("Current Handler Thread ID:"+Thread.currentThread().getId());  
            }   
        }//该线程将会在单独的线程中运行  
      
class UIUpdateThread implements Runnable {    
int i=1;  
        public void run() {    
            while (i<11) {    
                Message msg = mHandler.obtainMessage();  
                mHandler.sendMessage(msg);  
                System.out.println("新线程运行ID:"+Thread.currentThread().getId());  
                i++;  
                try {    
                    Thread.sleep(4000);    
                } catch (InterruptedException e) {    
                    // TODO Auto-generated catch block     
                    e.printStackTrace();    
                }    
            }    
        }    
    }  
}  

关于简单的音乐播放的源码如下:

[java]
view plaincopy

/* 
 *  Android开发之消息处理机制(二)——消息循环 
 *  MusicPlayer.java   
 *  Created on: 2011-9-3 
 *  Author: blueeagle 
 *  Email: liujiaxiang@gmail.com 
 */  
  
package blueeagle.com;  
  
import android.app.Activity;  
import android.media.MediaPlayer;  
import android.os.Bundle;  
import android.os.Handler;  
import android.os.Looper;  
import android.os.Message;  
import android.view.View;  
import android.view.View.OnClickListener;  
import android.widget.Button;  
import android.widget.TextView;  
  
public class MusicPlayer extends Activity {  
    /** Called when the activity is first created. */  
    private MediaPlayer mp;  
    Button myButton;  
    TextView myTextView;  
    private UIUpdateThread myUpdateThread = new UIUpdateThread(); //定义一个自己的UI更新的线程类  
    MyHandler mHandler = new MyHandler();//定义自己的一个Handler类  
    @Override  
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.main);  
        myButton = (Button)findViewById(R.id.myButton);  
        myTextView = (TextView)findViewById(R.id.myTextView);  
        myButton.setOnClickListener(new OnClickListener(){  
            @Override  
            public void onClick(View v) {  
                // TODO Auto-generated method stub  
                mp = MediaPlayer.create(MusicPlayer.this, R.raw.yinweiaiqing);  
                mp.start();  
                System.out.println("主线程运行ID:"+Thread.currentThread().getId());  
                new Thread(myUpdateThread).start();  
  
            }             
        });  
    }             
    class MyHandler extends Handler{//继承Handler类时,必须重写handleMessage方法   
        public MyHandler(){  
        }  
        public MyHandler(Looper l){  
            super(l);  
            }  
        @Override  
        public void handleMessage(Message msg) {//执行接收到的通知,此时执行的顺序是按照队列进行,即先进先出   
            myTextView.setText(msg.obj.toString());  
            System.out.println("Current Handler Thread ID:"+Thread.currentThread().getId());  
            }   
        }//该线程将会在单独的线程中运行  
    class UIUpdateThread implements Runnable {    
        int i = 0;  
        String myString[] = {  
                "这是一个实时播放的线程操作... ...",  
                "[00:08.79]《因为爱情》",  
                "[00:19.46]E 给你一张过去的CD",  
                "[00:28.68]听听那时我们的爱情",  
                "[00:34.12]有时会突然忘了",  
                "[00:37.48]我还在爱著你",  
                "[00:44.98]F 再唱不出那样的歌曲",  
                "[00:50.48]听到都会红著脸躲避",  
                "[00:55.83]虽然会经常忘了",  
                "[00:59.33]我依然爱著你",  
                "[01:05.49]F 因为爱情 不会轻易悲伤",  
                "[01:05.49]F 因为爱情 不会轻易悲伤",  
                "[01:12.09]E 所以一切都是幸福的模样",  
                "[01:17.24]F 因为爱情 简单的生长",  
                "[01:22.24]E 依然随时可以为你疯狂",  
                "[01:27.21]F 因为爱情 怎麼会有沧桑",  
                "[01:34.30]E 所以我们还是年轻的模样",  
                "[01:38.90]F 因为爱情 在那个地方",  
                "[01:44.32]E 依然还有人在那里游荡",  
                "[01:48.91]E&F 人来人往",  
                "[02:11.57]F 再唱不出那样的歌曲",  
                "[02:17.70]听到都会红著脸躲避",  
                "[02:23.14]虽然会经常忘了",  
                "[02:26.26]E&F 我依然爱著你",  
                "[02:32.60]F 因为爱情 不会轻易悲伤",  
                "[02:39.22]E 所以一切都是幸福的模样",  
                "[02:44.98]F 因为爱情 简单的生长",  
                "[02:49.36]E 依然随时可以为你疯狂",  
                "[02:54.38]F 因为爱情 怎麼会有沧桑",  
                "[03:00.94]E 所以我们还是年轻的模样",  
                "[03:06.04]F 因为爱情 在那个地方",  
                "[03:11.63]E 依然还有人在那里游荡",  
                "[03:17.04]E&F 人来人往",  
                "[03:21.98]E 给你一张过去的CD",  
                "[03:28.58]听听那时我们的爱情",  
                "[03:33.48]F 有时会突然忘了",  
                "[03:36.94]E&F 我还在爱著你"};  
        public void run() {    
            while (mp.isPlaying()) {    
                Message msg = mHandler.obtainMessage(1, 1, 1, myString[i]);  
                mHandler.sendMessage(msg);  
                System.out.println("新线程运行ID:"+Thread.currentThread().getId());  
                try {    
                    Thread.sleep(4000);    
                    i++;  
                    if(i == myString.length){  
                        i=myString.length-1;  
                    }   
                } catch (InterruptedException e) {    
                    // TODO Auto-generated catch block     
                    e.printStackTrace();    
                }    
            }    
        }    
    }  
}  

综上,基本了解了Android下的关于线程,消息循环的一些概念,还需要深入了解Looper如何运用在程序中等。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐