自适应学习系统客户端与服务器端是怎样传输数据的

您的位置: &
基于自适应的数据库系统学习平台的设计和实现
优质期刊推荐Miko Android自学之路 WifiDirect中文最强详解,如何传输数据,如何设置GroupOwener,如何设置客户端以及服务器端
大家好我是Miko,最近有参加一个比赛,用到了WifiDirect技术,于是翻看官方文档之后,想写一个Demo,Google API Sample已经很老了,还是用的Eclipse,宝宝心好累=_=+///,在CSDN上找了几篇文章,竟然都是些API的国语翻译,程序猿节操何在?这里我将会用自己根据官方Demo重写的Demo来详解WifiDirect的使用。
Android4.0之后开始支持WifiDirect技术,即Wifi直连,做为一种通讯方式,它的优势在于传输速度快传输距离远。
首先上我的DemoGithub地址/MikoGodZd/WifiDerect
在上官方文档/intl/zh-tw/guide/topics/connectivity/wifip2p.html
官方Demo http://download.csdn.net/detail/yichigo/5516627
通过Wi-Fi Direct查找附近的设备,并与之连接一般包括如下几个骤:
一 设置应用程序权限
二 创建一个广播接收器和对等网络管理器
三 初始化对等点的搜索
四 获取对等点列表
五 连接一个对等点
一、设置权限
我们mainifest中添加如下权限
&uses-sdk android:minSdkVersion="14" /&
&uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /&
&uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /&
&uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /&
&uses-permission android:name="android.permission.INTERNET" /&
&uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /&
&uses-permission android:name="android.permission.READ_PHONE_STATE" /&
&uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /&
&uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /&
android.permission.ACCESS_WIFI_STATE
允许程序访问Wi-Fi网络状态信息(Allows applications to access information about Wi-Fi networks)
android.permission.CHANGE_WIFI_STATE
允许程序改变Wi-Fi连接状态(Allows applications to change Wi-Fi connectivity state)
android.permission.INTERNET
当需要访问网络的时候,需要在AndroidManifest.xml里面添加访问网络的权限
二、创建广播接收器
首先在MainActivity中初始化IntentFilter并让它监听以下动作:
WIFI_P2P_STATE_CHANGED_ACTION
* 表明Wi-Fi对等网络(P2P)是否已经启用
WIFI_P2P_PEERS_CHANGED_ACTION
* 表明可用的对等点的列表发生了改变
WIFI_P2P_CONNECTION_CHANGED_ACTION
* 表示Wi-Fi对等网络的连接状态发生了改变
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION
* 表示该设备的配置信息发生了改变
private void initIntentFilter() {
mFilter = new IntentFilter();
mFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
mFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
mFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
mFilter.addAction(WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION);
mFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
然后我们创建一个新的广播类
public class WifiDirectBroadcastReceiver extends BroadcastReceiver {
private WifiP2pManager mM
private WifiP2pManager.Channel mC
private Activity mA
private WifiP2pManager.PeerListListener mPeerListL
private WifiP2pManager.ConnectionInfoListener mInfoL
public WifiDirectBroadcastReceiver(WifiP2pManager manager, WifiP2pManager.Channel channel, Activity activity,
WifiP2pManager.PeerListListener peerListListener,
WifiP2pManager.ConnectionInfoListener infoListener
this.mManager =
this.mChannel =
this.mPeerListListener = peerListL
this.mActivity =
this.mInfoListener = infoL
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
/*check if the wifi is enable*/
if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1);
if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) {
activity.setIsWifiP2pEnabled(true);
activity.setIsWifiP2pEnabled(false);
/*get the list*/
else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
mManager.requestPeers(mChannel, mPeerListListener);
/*查看当前是否处于查找状态
* get the state of discover*/
else if (WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION.equals(action)) {
int State = intent.getIntExtra(WifiP2pManager.EXTRA_DISCOVERY_STATE, -1);
if (State == WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED)
Toast.makeText(mActivity, "搜索开启", Toast.LENGTH_SHORT).show();
else if (State == WifiP2pManager.WIFI_P2P_DISCOVERY_STOPPED)
Toast.makeText(mActivity, "搜索已关闭", Toast.LENGTH_SHORT).show();
/*Respond to new connection or disconnections
*查看是否创建连接*/
else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
if (mManager == null) {
NetworkInfo networkInfo = (NetworkInfo) intent
.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);
if (networkInfo.isConnected()) {
Log.i("xyz", "已连接");
mManager.requestConnectionInfo(mChannel, mInfoListener);
Log.i("xyz", "断开连接");
/*Respond to this device's wifi state changing*/
else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
构造函数中的manager以及channel比较好理解,剩下的主要是在mainActivity中的方法的实现
第一个action用于检测当前设备的wifi是否打开很好理解
第二个action当你开始搜索之后,当设备列表发生变化的时候即触发通过mManager.requestPeers(mChannel, mPeerListListener);方法可以的到列表,这些我们下文详讲
第三个action用于检测当前是否处于搜索状态,
第四个action用于检测两个设备连接状态是否改变
第五个action适用于设备名称发生改变这里我们不讲
三、创建搜索,获得列表
ok 广播搭建好之后,我们就要初始化一个p2p了
首先在MainActivity中创建一个Manager以及一个Channel
private WifiP2pManager mM
private WifiP2pManager.Channel mC
mManager = (WifiP2pManager) getSystemService(WIFI_P2P_SERVICE);
mChannel = mManager.initialize(this, Looper.myLooper(), null);
这样我们就开启了direct服务
下面我们定义搜索函数
private void DiscoverPeers() {
mManager.discoverPeers(mChannel, new WifiP2pManager.ActionListener() {
public void onSuccess() {
public void onFailure(int reason) {
这里面的success以及failure没有任何实质的信息,只是提醒你调用这个方法是否成功,而不代表开启搜索是否成功,要监听搜索的状态,如上文所述我们要在广播中实现
else if (WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION.equals(action)) {
int State = intent.getIntExtra(WifiP2pManager.EXTRA_DISCOVERY_STATE, -1);
if (State == WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED)
Toast.makeText(mActivity, "搜索开启", Toast.LENGTH_SHORT).show();
else if (State == WifiP2pManager.WIFI_P2P_DISCOVERY_STOPPED)
Toast.makeText(mActivity, "搜索已关闭", Toast.LENGTH_SHORT).show();
ok开启搜索服务之后,当找到一个设备后,设备列表就会发生改变,这个时候就会触发广播中的第二个action
else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
mManager.requestPeers(mChannel, mPeerListListener);
通过requestPeers(mChannel, mPeerListListener)再通过监听器中的onPeersAvailable方法就可以得到设备列表,当让我们想在主界面中显示列表,因此监听器我们在activity中实现然后通过传参的方式传入到广播之中,
WifiP2pManager.PeerListListener mPeerListListerner = new WifiP2pManager.PeerListListener() {
public void onPeersAvailable(WifiP2pDeviceList peersList) {
peers.clear();
peersshow.clear();
Collection&WifiP2pDevice& aList = peersList.getDeviceList();
peers.addAll(aList);
for (int i = 0; i & aList.size(); i++) {
WifiP2pDevice a = (WifiP2pDevice) peers.get(i);
HashMap&String, String& map = new HashMap&String, String&();
map.put("name", a.deviceName);
map.put("address", a.deviceAddress);
peersshow.add(map);
mAdapter = new MyAdapter(peersshow);
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setLayoutManager(new LinearLayoutManager
(MainActivity.this));
mAdapter.SetOnItemClickListener(new MyAdapter.OnItemClickListener() {
public void OnItemClick(View view, int position) {
CreateConnect(peersshow.get(position).get("address"),
peersshow.get(position).get("name"));
public void OnItemLongClick(View view, int position) {
这是在Activity中的实现,得到List的方法有很多,我使用了RecyclerView感觉游侠笨重,一定有其他好的方法=_=///
单击每一个设备我所写的Adapter就会返回出来它的MAC地址,通过地址我们就可以来连接设备了
三、创建连接
创建连接我们调用manager中的connect方法,如果连接之前没有创建一个组,系统会自动创建一个组,并且随机分配谁是GroupOwner即谁是组长,这也关系到谁是客户端谁是服务器,
connect方法官方文档解释
If the current device is part of an existing p2p group or has created
* a p2p group with
#createGroup}, an invitation to join the group is sent to
* the peer device.
我们写一个方法来创建Group,在两个设备中谁调用这个方法,谁就是组长这样就实现了设定谁是服务器与客户端
private void BeGroupOwener() {
mManager.createGroup(mChannel, new WifiP2pManager.ActionListener() {
public void onSuccess() {
public void onFailure(int reason) {
private void CreateConnect(String address, final String name) {
WifiP2pConfig config = new WifiP2pConfig();
Log.i("xyz", address);
config.deviceAddress =
/*mac地址*/
config.wps.setup = WpsInfo.PBC;
Log.i("address", "MAC IS " + address);
if (address.equals("9a:ff:d0:23:85:97")) {
config.groupOwnerIntent = 0;
Log.i("address", "lingyige shisun");
if (address.equals("36:80:b3:e8:69:a6")) {
config.groupOwnerIntent = 15;
Log.i("address", "lingyigeshiwo");
Log.i("address", "lingyige youxianji" + String.valueOf(config.groupOwnerIntent));
mManager.connect(mChannel, config, new WifiP2pManager.ActionListener() {
public void onSuccess() {
public void onFailure(int reason) {
Wifip2pconfi这个类官方解释是
A class representing a Wi-Fi P2p configuration for setting up a connection
有道翻译 一个类代表一个wi - fi P2p配置为建立一个连接=_=+///
其实是一个类用来储存p2p设备的信息,我们把其中的Address值改为想要链接设备的Mac地址就可以得到设备的实例,
mManager.connect(mChannel, config, new WifiP2pManager.ActionListener() {
public void onSuccess() {
public void onFailure(int reason) {
调用connect方法,实现连接,如上文所述,这里的sucess,以及failure都是表示函数的成功与否,要看是否连接还要到广播之中
else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
if (mManager == null) {
NetworkInfo networkInfo = (NetworkInfo) intent
.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);
if (networkInfo.isConnected()) {
Log.i("xyz", "已连接");
mManager.requestConnectionInfo(mChannel, mInfoListener);
Log.i("xyz", "断开连接");
NetWorkInfo方法Describes the status of a network interface. 用来描述网络接口的状态
当网络连接时我们调用此方法mManager.requestConnectionInfo(mChannel, mInfoListener);就可让服务器端开始接受数据了,这里在下文中也会详讲。
四、传输数据前的准备
上文讲到mManager.requestConnectionInfo(mChannel, mInfoListener);方法
第二个参数是ConnectionInfoListener类
Interface for callback invocation when connection info is available用来当connect成功后回掉,跟上一个得List的监听器一样,我们也在MainActivity中实现,然后通过传参到广播之中
WifiP2pManager.ConnectionInfoListener mInfoListener = new WifiP2pManager.ConnectionInfoListener() {
public void onConnectionInfoAvailable(final WifiP2pInfo minfo) {
Log.i("xyz", "InfoAvailable is on");
info = minfo;
TextView view = (TextView) findViewById(R.id.tv_main);
if (info.groupFormed && info.isGroupOwner) {
Log.i("xyz", "owmer start");
mServerTask = new FileServerAsyncTask(MainActivity.this, view);
mServerTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
mDataTask = new DataServerAsyncTask(MainActivity.this, view);
mDataTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else if (info.groupFormed) {
SetButtonVisible();
我们看一下下面这核心代码
if (info.groupFormed && info.isGroupOwner) {
Log.i("xyz", "owmer start");
mServerTask = new FileServerAsyncTask(MainActivity.this, view);
mServerTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
mDataTask = new DataServerAsyncTask(MainActivity.this, view);
mDataTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else if (info.groupFormed) {
SetButtonVisible();
我们使用的传输方式是UDP模型在这个Demo中只能从客户端向服务器端发送消息
ok知道这个前提之后我们看
第一个判断:如果组已经建立,并且是组长,也就是说当前设备是服务器,我们开启两个AsyncTask分别用来接收图片以及字符串,当然看完这篇博客之后你可以传输各种类型的数据,这里仅以这两个举例
AsyncTask怎么写我们后面再说,
看第二个判断:如果组建立了,但不是组长,也就是说当前设备是客户端,这个时候我们让两个发送Button可见,也就是说一开始所有的设备界面都是一样的没有发送数据的按钮,但判断出谁是客户端之后,我们就将发送按钮展现出来。
ok搜索连接都讲完了,下面就是大头传送数据了
五、传输数据
1.客户端 发送服务的编写
Activity中设置发送图片按钮的监听器
sendpicture.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
startActivityForResult(intent, 20);
Intent intent = new Intent(Intent.ACTION_GET_CONTENT)
官方文档Allow the user to select a particular kind of data and return it.
这个Intent会打开文件管理器
intent.setType(“image/*”);这个语句决定着以什么方式打开,我们设以图片方式打开,启动Activity后我们选择好图片,之后Activity关闭自动调用onActivityResult
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 20) {
super.onActivityResult(requestCode, resultCode, data);
Uri uri = data.getData();
Intent serviceIntent = new Intent(MainActivity.this,
FileTransferService.class);
serviceIntent.setAction(FileTransferService.ACTION_SEND_FILE);
serviceIntent.putExtra(FileTransferService.EXTRAS_FILE_PATH,
uri.toString());
serviceIntent.putExtra(FileTransferService.EXTRAS_GROUP_OWNER_ADDRESS,
info.groupOwnerAddress.getHostAddress());
serviceIntent.putExtra(FileTransferService.EXTRAS_GROUP_OWNER_PORT,
MainActivity.this.startService(serviceIntent);
Uri uri = data.getData();获得图片所在位置
serviceIntent.putExtra(FileTransferService.EXTRAS_FILE_PATH,uri.toString());将位置传入Service
serviceIntent.putExtra(FileTransferService.EXTRAS_GROUP_OWNER_ADDRESS,info.groupOwnerAddress.getHostAddress());传入组长的IP地址,用来创建Socket端口
serviceIntent.putExtra(FileTransferService.EXTRAS_GROUP_OWNER_PORT,8988);传入端口port
ok下面我们看服务究竟怎么写
public class FileTransferService extends IntentService {
private static final int SOCKET_TIMEOUT = 5000;
public static final String ACTION_SEND_FILE = "com.example.android.wifidirect.SEND_FILE";
public static final String EXTRAS_FILE_PATH = "sf_file_url";
public static final String EXTRAS_GROUP_OWNER_ADDRESS = "sf_go_host";
public static final String EXTRAS_GROUP_OWNER_PORT = "sf_go_port";
public FileTransferService(String name) {
super(name);
public FileTransferService() {
super("FileTransferService");
* (non-Javadoc)
* @see android.app.IntentService#onHandleIntent(android.content.Intent)
protected void onHandleIntent(Intent intent) {
Context context = getApplicationContext();
if (intent.getAction().equals(ACTION_SEND_FILE)) {
String fileUri = intent.getExtras().getString(EXTRAS_FILE_PATH);
String host = intent.getExtras().getString(
EXTRAS_GROUP_OWNER_ADDRESS);
Socket socket = new Socket();
int port = intent.getExtras().getInt(EXTRAS_GROUP_OWNER_PORT);
Log.d("xyz", "Opening client socket - ");
socket.bind(null);
socket.connect((new InetSocketAddress(host, port)),
SOCKET_TIMEOUT);
Log.d("xyz",
"Client socket - " + socket.isConnected());
/*returns an output stream to write data into this socket*/
OutputStream stream = socket.getOutputStream();
ContentResolver cr = context.getContentResolver();
InputStream is = null;
is = cr.openInputStream(Uri.parse(fileUri));
} catch (FileNotFoundException e) {
Log.d("xyz", e.toString());
FileServerAsyncTask.copyFile(is, stream);
Log.d("xyz", "Client: Data written");
} catch (IOException e) {
Log.e("xyz", e.getMessage());
} finally {
if (socket != null) {
if (socket.isConnected()) {
socket.close();
} catch (IOException e) {
// Give up
e.printStackTrace();
socket.connect((new InetSocketAddress(host, port)),
SOCKET_TIMEOUT);创建Socket连接
/*returns an output stream to write data into this socket*/
OutputStream stream = socket.getOutputStream();
ContentResolver cr = context.getContentResolver();
InputStream is = null;
is = cr.openInputStream(Uri.parse(fileUri));
} catch (FileNotFoundException e) {
Log.d("xyz", e.toString());
FileServerAsyncTask.copyFile(is, stream);
将is copy到stream中ok这样我们的服务就启动成功了,至于怎么接收先不细讲,先看一下AsyncTask
2、服务器端 AsyncTask的编写
public class FileServerAsyncTask extends
AsyncTask&Void, Void, String& {
private TextView statusT
* @param context
* @param statusText
public FileServerAsyncTask(Context context, View statusText) {
this.context =
this.statusText = (TextView) statusT
protected String doInBackground(Void... params) {
Log.i("xyz", "file doinback");
ServerSocket serverSocket = new ServerSocket(8988);
Socket client = serverSocket.accept();
final File f = new File(
Environment.getExternalStorageDirectory() + "/"
+ "com.miko.zd" + "/wifip2pshared-"
+ System.currentTimeMillis() + ".jpg");
File dirs = new File(f.getParent());
if (!dirs.exists())
dirs.mkdirs();
f.createNewFile();
/*Returns an input stream to read data from this socket*/
InputStream inputstream = client.getInputStream();
copyFile(inputstream, new FileOutputStream(f));
serverSocket.close();
return f.getAbsolutePath();
} catch (IOException e) {
Log.e("xyz", e.toString());
return null;
* (non-Javadoc)
* @see android.os.AsyncTask#onPostExecute(java.lang.Object)
protected void onPostExecute(String result) {
Log.i("xyz", "file onpost");
Toast.makeText(context, "result"+result, Toast.LENGTH_SHORT).show();
if (result != null) {
statusText.setText("File copied - " + result);
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse("file://" + result), "image/*");
context.startActivity(intent);
* (non-Javadoc)
* @see android.os.AsyncTask#onPreExecute()
protected void onPreExecute() {
public static boolean copyFile(InputStream inputStream, OutputStream out) {
byte buf[] = new byte[1024];
while ((len = inputStream.read(buf)) != -1) {
out.write(buf, 0, len);
out.close();
inputStream.close();
} catch (IOException e) {
return false;
return true;
AsyncTask原理大家应该很清楚这里不细说,不懂Google=_=+///
ok先看doInbackGround
ServerSocket serverSocket = new ServerSocket(8988);创建Socket
Socket client = serverSocket.accept();这句很关键,
accept方法
Waits for an incoming request and blocks until the connection is opened.
This method returns a socket object representing the just opened connection.
也就说等待连接之前这里是阻塞的,线程停止,当然不会调用下面的onPostExecute,
这一点很关键我们梳理一下,找到设备,点击设备进行连接后ConnectionInfoListener监听器触发,调用方法
onConnectionInfoAvailable{
if (info.groupFormed && info.isGroupOwner) {
Log.i("xyz", "owmer start");
mServerTask = new FileServerAsyncTask(MainActivity.this, view);
mServerTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
这个时候Task就启动了,当然它会在 Socket client = serverSocket.accept()这里阻塞
这个时候,我们点击发送数据按钮启动服务,socket.connect((new InetSocketAddress(host, port)),
SOCKET_TIMEOUT);
这时候创建连接,阻塞取消,然后就会尽心之后的copy保存了
ok博主是大二党,计算机科学与技术专业,这搞笑的专业,完全自学,每天学一点,好吧这一点有时候是10个钟头,但自己就能感觉到进步,起码打字快了吧=——=,虽然blog很耗费时间,但我坚信这是一种学习的好方法。
> 本站内容系网友提交或本网编辑转载,其目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如涉及作品内容、版权和其它问题,请及时与本网联系,我们将在第一时间删除内容!
本文章来给各位同学详细的介绍一下关于Android Ant命令行编译和APK签名详解一些实现方法,这是一个朋友在自己做安卓开发时写的,希望对大家会有所帮助呀.最近在做Android开发时,需要引用第三方的代码进项目,一般情况下,直接在Eclipse下设置需要导入的代码的编译输出为library即可,但是很多代码在Eclipse下编译会出现很多莫名其妙的错误. ...
本文章来给大家介绍Android的程序解码与XML文件解码详解,有需要了解的同学可参考参考.首先,怀着无比激动的心情写下这篇日志.....Googles真的很给力....让我有幸看到ANDROID应用程序的内部源码....下面开始言归正传:ANDROID的应用程序是由两部分组成的.1.*.class文件2.*.xml文件 解码时候需要使用到不同的工具...下 ...
Google在旧金山Moscone会展中心举办Google I/O 2010大会第二天(美国时间5月20日上午)的会议之上如约正式发布了代号为&Froyo(冻酸奶)&的最新版Android 2.2操作系统.而紧接着网络中也开始提供SDK(开发者套件)安装包的下载,虽然仅凭SDK包尚无法让我们体验到诸如Flash10.1等应用,但据官 方介 ...
新闻&Android N第一版不支持压力感应下一版才有&:前不久国外媒体爆料,谷歌Android 7.0也就是Android N将会在今年I/O大会上公布,随着日期临近越来越多的消息被曝光,其中就包括原生支持压力感应屏幕功能.现在已经有很多Android厂商模仿苹果推出了压力感应屏幕的设计,但这些都是有实力的手机厂商单独开发的,并不是在Andro ...
李华明Himi 原创,转载务必在明显处注明:转载自 原文链接: /android-game/329.html 很多童鞋说我的代码运行后,点击home或者back后会程序异常,如果你也这样遇到过,那么你肯定没有仔细读完Himi的博文,第十九篇Himi专门写了关于这些错误的原因和解决方法,这里我在博客都补充说明下, ...}

我要回帖

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信