当前位置: 移动技术网 > 移动技术>移动开发>Android > Android 基于Socket的聊天室实例

Android 基于Socket的聊天室实例

2019年07月24日  | 移动技术网移动技术  | 我要评论

socket是tcp/ip协议上的一种通信,在通信的两端各建立一个socket,从而在通信的两端之间形成网络虚拟链路。一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信。

client a  发信息给 client b ,  a的信息首先发送信息到服务器server ,server接受到信息后再把a的信息广播发送给所有的clients

首先我们要在服务器建立一个serversocket ,serversocket对象用于监听来自客户端的socket连接,如果没有连接,它将一直处于等待状态。

socket accept():如果接收到一个客户端socket的连接请求,该方法将返回一个与客户端socket对应的socket

server示例:

//创建一个serversocket,用于监听客户端socket的连接请求
serversocket ss = new serversocket(30000);
//采用循环不断接受来自客户端的请求
while (true){
//每当接受到客户端socket的请求,服务器端也对应产生一个socket
socket s = ss.accept();
//下面就可以使用socket进行通信了
...
}

客户端通常可使用socket的构造器来连接到指定服务器

client示例:

//创建连接到服务器、30000端口的socket
socket s = new socket("192.168.2.214" , 30000);
//下面就可以使用socket进行通信了
...

这样server和client就可以进行一个简单的通信了

当然,我们要做的是多客户,所以每当客户端socket连接到该serversocket之后,程序将对应socket加入clients集合中保存,并为该socket启动一条线程,该线程负责处理该socket所有的通信任务

//定义保存所有socket的arraylist
public static arraylist<socket> clients = new arraylist<socket>();

当服务器线程读到客户端数据之后,程序遍历clients集合,并将该数据向clients集合中的每个socket发送一次。这样就可以实现一个聊天室的功能了

下面来看看整个功能的demo

先建立一个java工程,把server.java运行起来,然后再运行手机模拟器

服务器打印信息:

程序文件结构:

1.先看看主activity : socketmsgactivity.java

public class socketmsgactivity extends activity {
  /** called when the activity is first created. */
  private sqlitedatabase db;
  
  thread thread = null;
  socket s = null;
  private inetsocketaddress isa = null; 

  datainputstream dis = null;
  dataoutputstream dos = null;
  private string remsg=null;
  private boolean iscontect = false;
  private edittext chattxt;
  private edittext chatbox;
  private button chatok;
  
  private string chatkey="sleeknetgeock4stsjes";
  private string name=null,ip=null,port=null;
  @override
  public void oncreate(bundle savedinstancestate) {
    super.oncreate(savedinstancestate);
    setcontentview(r.layout.main);
    chattxt = (edittext)findviewbyid(r.id.chattxt);
    chatbox = (edittext)findviewbyid(r.id.chatbox);
    chatok = (button)findviewbyid(r.id.chatok);
    chatbox.setcursorvisible(false);
    chatbox.setfocusable(false);
    chatbox.setfocusableintouchmode(false);
    chatbox.setgravity(2);
    
    //初始化,创建数据库来储存用户信息
    initdatabase();
    db = sqlitedatabase.openorcreatedatabase(config.f, null);
    try {
      cursor cursor = db.query("config", new string[]{"ip","name","port"},null,null, null, null, null);
      while(cursor.movetonext()){
        name = cursor.getstring(cursor.getcolumnindex("name"));
        ip = cursor.getstring(cursor.getcolumnindex("ip"));
        port = cursor.getstring(cursor.getcolumnindex("port"));
      }
      cursor.close();
    } catch (exception e) {
      // todo: handle exception
      system.out.println(e.tostring());
    }
    db.close();
    
    //设置连接
    if(ip==null || port==null){
      intent intent = new intent(socketmsgactivity.this,iniactivity.class);
      startactivity(intent);
      socketmsgactivity.this.finish();
    }
    //设置名称
    else if(name==null){
      intent intent = new intent(socketmsgactivity.this,iniuseractivity.class);
      startactivity(intent);
      socketmsgactivity.this.finish();
    }else{
      
      connect();
      chatok.setonclicklistener(new view.onclicklistener() {
  
        @override
        public void onclick(view v) {
  
          string str = chattxt.gettext().tostring().trim();
          system.out.println(s);
          try {
            dos.writeutf(chatkey+"name:"+name+"end;"+str);
            chattxt.settext("");
  
          }catch (sockettimeoutexception e) {
             system.out.println("連接超時,服務器未開啟或ip錯誤");
             toast.maketext(socketmsgactivity.this, "連接超時,服務器未開啟或ip錯誤", toast.length_short).show();
             intent intent = new intent(socketmsgactivity.this,iniactivity.class);
            startactivity(intent);
            socketmsgactivity.this.finish();
             e.printstacktrace();
           } catch (ioexception e) {
            // todo auto-generated catch block
             system.out.println("連接超時,服務器未開啟或ip錯誤");
             toast.maketext(socketmsgactivity.this, "連接超時,服務器未開啟或ip錯誤", toast.length_short).show();
             intent intent = new intent(socketmsgactivity.this,iniactivity.class);
            startactivity(intent);
            socketmsgactivity.this.finish();
             e.printstacktrace();
          }
        }
      });
    }
  }
  
  private runnable dothread = new runnable() {
    public void run() {
      system.out.println("running!");
      receivemsg();
    }
  };  
  
  public void connect() {
    try {
      s = new socket();
      isa = new inetsocketaddress(ip,integer.parseint(port)); 
      s.connect(isa,5000); 

      if(s.isconnected()){
        dos = new dataoutputstream (s.getoutputstream());
        dis = new datainputstream (s.getinputstream());
        dos.writeutf(chatkey+"online:"+name);
        /**
         * 这里是关键,我在此耗时8h+
         * 原因是 子线程不能直接更新ui
         * 为此,我们需要通过handler物件,通知主线程ui thread来更新界面。
         * 
*/
        thread = new thread(null, dothread, "message");
         thread.start();
         system.out.println("connect");
         iscontect=true;
      }
     }catch (unknownhostexception e) {
       system.out.println("連接失敗");
      toast.maketext(socketmsgactivity.this, "連接失敗", toast.length_short).show();
      intent intent = new intent(socketmsgactivity.this,iniactivity.class);
      startactivity(intent);
      socketmsgactivity.this.finish();
       e.printstacktrace();
     }catch (sockettimeoutexception e) {
       system.out.println("連接超時,服務器未開啟或ip錯誤");
       toast.maketext(socketmsgactivity.this, "連接超時,服務器未開啟或ip錯誤", toast.length_short).show();
      intent intent = new intent(socketmsgactivity.this,iniactivity.class);
      startactivity(intent);
      socketmsgactivity.this.finish();
       e.printstacktrace();
     }catch (ioexception e) {
       system.out.println("連接失敗");
       e.printstacktrace();
     }
  }
  
  public void disconnect() {
    if(dos!=null){
    try {
      
        dos.writeutf(chatkey+"offline:"+name);
      
    } catch (ioexception e1) {
      // todo auto-generated catch block
      e1.printstacktrace();
    }
    try {
      s.close();
    } catch (ioexception e) {
       e.printstacktrace();
    }
    }
  }
 
  
  /**
   * 线程监视server信息
*/
  private void receivemsg() {
    if (iscontect) {
      try {
        while ((remsg = dis.readutf()) != null) {
          system.out.println(remsg);
          if (remsg != null) {

            try {
              message msgmessage = new message();
              msgmessage.what = 0x1981;
              handler.sendmessage(msgmessage);
              thread.sleep(100);
            } catch (interruptedexception e) {
              // todo auto-generated catch block
              e.printstacktrace();
            }

          }
        }
      } catch (socketexception e) {
        // todo: handle exception
        system.out.println("exit!");
      } catch (ioexception e) {
        // todo auto-generated catch block
        e.printstacktrace();
      }

    }
  }
 
  /**
   * 通过handler更新ui
*/
  handler handler = new handler() {
    public void handlemessage(message msg) {
      switch (msg.what) {
      case 0x1981:
        chatbox.settext(chatbox.gettext() + remsg + '\n');
        chatbox.setselection(chatbox.length());
        break;
      }
    }
  };
  
  @override
  protected void ondestroy() {
    // todo auto-generated method stub
    super.ondestroy();
    disconnect();
    //system.exit(0);
  }
  
  @override
  public boolean oncreateoptionsmenu(menu menu) {
    // todo auto-generated method stub
    menu.add(0, 1, 1, "初始化設置");
    menu.add(0, 2, 2, "退出");
    return super.oncreateoptionsmenu(menu);
  }

  @override
  public boolean onoptionsitemselected(menuitem item) {
    // todo auto-generated method stub
    if(item.getitemid()==1){
      intent intent = new intent(socketmsgactivity.this,iniactivity.class);
      startactivity(intent);
      socketmsgactivity.this.finish();
    }else if(item.getitemid()==2){
      disconnect();
      socketmsgactivity.this.finish(); 
      android.os.process.killprocess(android.os.process.mypid());
      system.exit(0);
    }
    return super.onoptionsitemselected(item);
  }

  public void initdatabase(){
     
    if(!config.path.exists()){ 
      config.path.mkdirs();  
      log.i("logdemo", "mkdir"); 
    }  
    if(!config.f.exists()){   
      try{  
        config.f.createnewfile(); 
        log.i("logdemo", "create a new database file");
      }catch(ioexception e){  
        log.i("logdemo",e.tostring());
      }  
    } 
    try {
      if(tabisexist("config")==false){
        db = sqlitedatabase.openorcreatedatabase(config.f, null); 
        db.execsql("create table config(_id integer primary key autoincrement," +
            "ip varchar(128),port varchar(10),name varchar(32))");
        log.i("logdemo", "create a database");
        db.close();
      }
    } catch (exception e) {
      // todo: handle exception
      log.i("logdemo",e.tostring());
    }
  }
  
  /**
   * check the database is already exist
   * @param tabname
   * @return
*/
  public boolean tabisexist(string tabname){
    boolean result = false;
    if(tabname == null){
        return false;
    }
    cursor cursor = null;
    db = sqlitedatabase.openorcreatedatabase(config.f, null); 
    try {
      string sql = "select count(*) as c from sqlite_master where type ='table' " +
            "and name ='"+tabname.trim()+"' ";
      cursor = db.rawquery(sql, null);
      if(cursor.movetonext()){
        int count = cursor.getint(0);
        if(count>0){
          result = true;
        }
      }
        
    } catch (exception e) {
        // todo: handle exception
    } 
    cursor.close();
    db.close();
    return result;
  }
}

2.初始化ip和端口activity, iniactivity.java

public class iniactivity extends activity{

  private edittext ip,port;
  private button nextbutton;
  private string getip,getport;
  private progressdialog progressdialog;
  private inetsocketaddress isa = null; 
  private sqlitedatabase db;
  private string ipstring=null,portstring=null;
  private int row=0;
  @override
  protected void oncreate(bundle savedinstancestate) {
    // todo auto-generated method stub
    super.oncreate(savedinstancestate);
    setcontentview(r.layout.config);
    
    ip = (edittext)findviewbyid(r.id.ip);
    port = (edittext)findviewbyid(r.id.port);
    nextbutton = (button)findviewbyid(r.id.next);
    
    
    db = sqlitedatabase.openorcreatedatabase(config.f, null);
    try {
      cursor cursor = db.query("config", new string[]{"ip","port"},null,null, null, null, null);
      while(cursor.movetonext()){
        ipstring = cursor.getstring(cursor.getcolumnindex("ip"));
        portstring = cursor.getstring(cursor.getcolumnindex("port"));
        row++;
      }
      ip.settext(ipstring);
      port.settext(portstring);
      cursor.close();
    } catch (exception e) {
      // todo: handle exception
      system.out.println(e.tostring());
    }
    db.close();
    
    nextbutton.setonclicklistener(new nextbuttonlistenner());
  }
  
  class nextbuttonlistenner implements onclicklistener{

    @override
    public void onclick(view v) {
      // todo auto-generated method stub
      getip = ip.gettext().tostring().trim();
      getport = port.gettext().tostring().trim();
      if(getip=="" || getip==null || getip.equals("")){
        toast.maketext(iniactivity.this, "請輸入ip", toast.length_short).show();
        ip.setfocusable(true);
      }else if(getport=="" || getport==null || getport.equals("")){
        toast.maketext(iniactivity.this, "請輸入端口", toast.length_short).show();
        port.setfocusable(true);
      }else{
      //progressdialog = progressdialog.show(iniactivity.this, "", "請稍後...", true, false);
//new thread() {
//@override
//public void run() {
          try {
            socket s = new socket();
            isa = new inetsocketaddress(getip,integer.parseint(getport)); 
            s.connect(isa,5000); 
            //showdialog("連接成功",iniactivity.this);
            try {
              //生成contentvalues对象
              contentvalues values = new contentvalues();
              //想该对象当中插入键值对,其中键是列名,值是希望插入到这一列的值,值必须和数据库当中的数据类型一致
              values.put("ip", getip);
              values.put("port",getport);
              db = sqlitedatabase.openorcreatedatabase(config.f, null); 
              if(row==0){
                db.insert("config", null, values);
              }else{
                db.update("config", values ,null,null);
              }
              toast.maketext(iniactivity.this, "連接成功", toast.length_short);
              s.close();
              intent intent = new intent(iniactivity.this,iniuseractivity.class);
              startactivity(intent);
              iniactivity.this.finish();
              db.close();
            } catch (exception e) {
              // todo: handle exception
              showdialog("設置失敗,數據庫不可用",iniactivity.this);
            }
            
            
          } catch (unknownhostexception e) {
            // todo auto-generated catch block
            e.printstacktrace();
            showdialog("連接失敗,ip或者端口不可用",iniactivity.this);
          }catch (sockettimeoutexception e) {
             system.out.println("連接超時,服務器未開啟或ip錯誤");
             showdialog("連接超時,服務器未開啟或ip錯誤",iniactivity.this);
             e.printstacktrace();
          }
          catch (ioexception e) {
            // todo auto-generated catch block
            e.printstacktrace();
            showdialog("連接失敗,ip或者端口不可用",iniactivity.this);
          }
          //progressdialog.dismiss();
//finish();
//}
//}.start();
      }
      
    }
    
  }
  
  /**
   * define a dialog for show the message
   * @param mess
   * @param activity
*/
  public void showdialog(string mess,activity activity){
   new alertdialog.builder(activity).settitle("信息")
    .setmessage(mess)
    .setnegativebutton("確定",new dialoginterface.onclicklistener()
    {
     public void onclick(dialoginterface dialog, int which)
     {     
     }
    })
    .show();
  }
}

3.初始化用户名称activity, iniuseractivity.java

public class iniuseractivity extends activity{
  private edittext name;
  private button ok;
  private sqlitedatabase db;
  
  private string namestring;
  @override
  protected void oncreate(bundle savedinstancestate) {
    // todo auto-generated method stub
    super.oncreate(savedinstancestate);
    setcontentview(r.layout.configuser);
    
    name = (edittext)findviewbyid(r.id.name);
    ok = (button)findviewbyid(r.id.ok);
    ok.setonclicklistener(new okbuttonlistenner());
    
    
    db = sqlitedatabase.openorcreatedatabase(config.f, null);
    try {
      cursor cursor = db.query("config", new string[]{"name"},null,null, null, null, null);
      while(cursor.movetonext()){
        namestring = cursor.getstring(cursor.getcolumnindex("name"));
      }
      name.settext(namestring);
      cursor.close();
    } catch (exception e) {
      // todo: handle exception
      system.out.println(e.tostring());
    }
    db.close();
  }
  
  class okbuttonlistenner implements onclicklistener{

    @override
    public void onclick(view v) {
      // todo auto-generated method stub
      string getname = name.gettext().tostring().trim();
      if(getname==""){
        toast.maketext(iniuseractivity.this, "請輸入您的稱呢", toast.length_short).show();
        name.setfocusable(true);
      }else{      
        try {
          //生成contentvalues对象
          contentvalues values = new contentvalues();
          //想该对象当中插入键值对,其中键是列名,值是希望插入到这一列的值,值必须和数据库当中的数据类型一致
          values.put("name", getname);
          db = sqlitedatabase.openorcreatedatabase(config.f, null); 
          db.update("config",values,null,null);
          toast.maketext(iniuseractivity.this, "設置完成", toast.length_short).show();
          intent intent = new intent(iniuseractivity.this,socketmsgactivity.class);
          startactivity(intent);
          iniuseractivity.this.finish();
          db.close();
        } catch (exception e) {
          // todo: handle exception
          showdialog("設置失敗,數據庫不可用",iniuseractivity.this);
        }
      }
    }
    
  }
  
  /**
   * define a dialog for show the message
   * @param mess
   * @param activity
*/
  public void showdialog(string mess,activity activity){
   new alertdialog.builder(activity).settitle("信息")
    .setmessage(mess)
    .setnegativebutton("確定",new dialoginterface.onclicklistener()
    {
     public void onclick(dialoginterface dialog, int which)
     {     
     }
    })
    .show();
  }
}

4.config.java

public class config{
  public static string sdcard = android.os.environment.getexternalstoragedirectory().getabsolutepath();
  public static file path = new file(sdcard+"/runchatdatabase/"); //数据库文件目录  
  public static file f = new file(sdcard+"/runchatdatabase/config.db"); //数据库文件 
}

布局文件:

1.main.xml

<?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">
  <edittext android:id="@+id/chatbox" android:layout_width="fill_parent"
    android:layout_height="fill_parent" android:layout_weight="1">
  </edittext>
  <edittext android:id="@+id/chattxt" android:layout_width="fill_parent"
    android:layout_height="wrap_content" android:gravity="top"
    android:hint="你想和对方说点什么?">
  </edittext>
  <button android:id="@+id/chatok" android:layout_width="fill_parent"
    android:layout_height="wrap_content" android:text="send" 
    android:textsize="@dimen/btn1">
  </button>

</linearlayout>

2.config.xml

<?xml version="1.0" encoding="utf-8"?>
<linearlayout
xmlns:android="http://schemas.android.com/apk/res/android"
 android:orientation="vertical"
 android:layout_width="match_parent"
 android:layout_height="match_parent">
  <textview 
android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="初始化設置"
    android:textsize="@dimen/h2"/>
  <textview 
android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="服務器ip"
    android:textsize="@dimen/h3"/>
  <edittext 
android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:hint="192.168.2.214"
    android:id="@+id/ip"
    android:textsize="@dimen/et1"/>
    
  <textview 
android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="端口"
    android:textsize="@dimen/h3"/>
  <edittext 
android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:hint="8888"
    android:id="@+id/port"
    android:textsize="@dimen/et1"/>
    
  <button 
android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="下一步"
    android:id="@+id/next"
    android:textsize="@dimen/btn1"/>    
</linearlayout>

3.configuer.xml

<?xml version="1.0" encoding="utf-8"?>
<linearlayout
xmlns:android="http://schemas.android.com/apk/res/android"
 android:orientation="vertical"
 android:layout_width="match_parent"
 android:layout_height="match_parent">
  <textview 
android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="初始化設置"
    android:textsize="@dimen/h2"/>
  <textview 
android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="您的稱呢"
    android:textsize="@dimen/h3"/>
  <edittext 
android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:hint="潤仔"
    android:id="@+id/name"
    android:maxlength="20"
    android:textsize="@dimen/et1"/>
    
  <button 
android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="完成"
    android:id="@+id/ok"
    android:textsize="@dimen/btn1"/>  
</linearlayout>
style文件:dimens.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>   
  <dimen name="h3">30dip</dimen> 
  <dimen name="h2">40dip</dimen> 
  <dimen name="btn1">30dip</dimen>
  <dimen name="et1">25dip</dimen>
</resources>

最后是服务器文件:server.java

import java.io.*;
import java.net.*;
import java.text.dateformat;
import java.text.simpledateformat;
import java.util.*;

import javax.sound.sampled.port;
import javax.swing.joptionpane;

public class server {
  
  serversocket ss = null;
  private string getnamestring=null;
  boolean started = false;
  list<client> clients = new arraylist<client>();
  list<info> infos = new arraylist<info>();
  public static void main(string[] args) {
    string inputport = joptionpane.showinputdialog("請輸入該服務器使用的端口:");
    int port = integer.parseint(inputport);
    new server().start(port);
  }
 
  public void start(int port) {
    try {
      ss = new serversocket(port);
      system.out.println("服務器啟動");
      started = true;
    } catch (bindexception e) {
       system.out.println(" 端口已经被占用");
       system.exit(0);
      }
     catch (ioexception e) {
       e.printstacktrace();
     }

   try {
     while (started) {
       socket s = ss.accept();
       client c = new client (s);
       system.out.println("a client is connected");
       new thread(c).start();
       clients.add(c);
       
       
     }
   } catch (ioexception e) {
      e.printstacktrace();
     }
     finally {
      try {
        ss.close();
      } catch (ioexception e) {
         e.printstacktrace();
        }
     }
  }
  public list<client> getclient(){
    return clients;
  }

 class client implements runnable {
   private string chatkey="sleeknetgeock4stsjes";
   private socket s = null;
   private datainputstream dis = null;
   private dataoutputstream dos = null;
   private boolean bconnected = false;
   private string sendmsg=null;
   client (socket s) {
    this.s = s;
    try {
     dis = new datainputstream (s.getinputstream());
     dos = new dataoutputstream (s.getoutputstream());
     bconnected = true;
    } catch(ioexception e) {
       e.printstacktrace();
      }
   }
   
   public void send (string str) {
     
     try {
       //system.out.println(s);
       dos.writeutf(str+"");
       dos.flush();
     } catch(ioexception e) {
       clients.remove(this);
       system.out.println("对方已经退出了");
     }
   }
   public void run() {
     try {
      while (bconnected) {
        string str = dis.readutf();
        dateformat df = new simpledateformat("yyyy-mm-dd hh:mm:ss");
        string date = " ["+df.format(new date())+"]";
        if(str.startswith(chatkey+"online:")){
          info info = new info();
          getnamestring = str.substring(27);
          
          info.setname(getnamestring);
          infos.add(info);
          for (int i=0; i<clients.size(); i++) {
           client c = clients.get(i);
           c.send(getnamestring+" on line."+date);
          }
          system.out.println(getnamestring+" on line."+date);
        }else if(str.startswith(chatkey+"offline:")){
          getnamestring = str.substring(28);
          clients.remove(this);
          for (int i=0; i<clients.size(); i++) {
             client c = clients.get(i);
             c.send(getnamestring+" off line."+date);
            }
          system.out.println(getnamestring+" off line."+date);
        }
        else{
          int charend = str.indexof("end;");
          string chatstring = str.substring(charend+4);
          string chatname = str.substring(25, charend);
          
          sendmsg=chatname+date+"\n"+chatstring; 
          for (int i=0; i<clients.size(); i++) {
            client c = clients.get(i);
            c.send(sendmsg);
           }
          system.out.println(sendmsg);
        }
       }
     } catch (socketexception e) {
       system.out.println("client is closed!");
       clients.remove(this);
     } catch (eofexception e) {
        system.out.println("client is closed!");
        clients.remove(this);
      }
      catch (ioexception e) {
        e.printstacktrace();
      }
      finally {
       try {
        if (dis != null) dis.close();
        if (dos != null) dos.close();
        if (s != null) s.close();
       } catch (ioexception e) {
          e.printstacktrace();
        }
      }
   }
 }
 
 class info{
   private string info_name = null;
   public info(){
     
   }
   public void setname(string name){
     info_name = name;
   }
   public string getname(){
     return info_name;
   }
 }
}


以上只是一个粗略的聊天室功能,如果要实现私聊,还需要保存该socket关联的客户信息。一个客户端可以将信息发送另一个指定客户端。实际上,我们知道所有客户端只与服务器连接,客户端之间并没有互相连接。这个功能等我以后有时间再写个demo.....

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网