android基于socket的局域網(wǎng)內(nèi)服務(wù)器與客戶端加密通信
實(shí)現(xiàn)了基本的socket通信(即兩臺設(shè)備,一臺用作服務(wù)器,一臺用作客戶端),服務(wù)器進(jìn)行監(jiān)聽,客戶端發(fā)送加密數(shù)據(jù)到服務(wù)器,服務(wù)器進(jìn)行解密得到明文。
注意:本項(xiàng)目中使用了ButterKnife及EventBus作為輔助工具,通信建立時(shí)默認(rèn)網(wǎng)絡(luò)正常(未做局域網(wǎng)網(wǎng)絡(luò)環(huán)境檢測),加密方式為AES加密
1.效果圖:
(1)客戶端

(2)服務(wù)器端

2.界面布局部分
(1)服務(wù)器端布局 function_socket_server.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout style="@style/ToolBar">
<TextView
style="@style/ToolBar_tv_Title"
android:text="網(wǎng)絡(luò)加密-服務(wù)器端" />
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btn_startListener"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="啟動監(jiān)聽" />
<Button
android:id="@+id/btn_stopListener"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="停止監(jiān)聽" />
<Button
android:id="@+id/btn_getUser"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="刷新用戶" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="本機(jī)地址:" />
<TextView
android:id="@+id/tv_localAddress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true" />
</LinearLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="接收到的明文:"
android:textColor="@color/black" />
<TextView
android:id="@+id/tv_receivedContent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="解密后的明文:"
android:textColor="@color/black" />
<TextView
android:id="@+id/tv_decryptContent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp" />
</LinearLayout>
</ScrollView>
</LinearLayout>
(2)客戶端布局 function_socket_client.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout style="@style/ToolBar">
<TextView
style="@style/ToolBar_tv_Title"
android:text="網(wǎng)絡(luò)加密-客戶端" />
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="服務(wù)器地址:" />
<EditText
android:id="@+id/edtTxt_serverAddress"
android:layout_width="match_parent"
android:text="192.168.43.1"
android:layout_height="wrap_content"
android:singleLine="true" />
</LinearLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="文本內(nèi)容:"
android:textColor="@color/black" />
<EditText
android:id="@+id/edtTxt_Content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/main_background"
android:padding="10dp"
android:text="123木頭人" />
</LinearLayout>
</ScrollView>
<Button
android:id="@+id/btn_encryptAndSend"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:text="加密并發(fā)送" />
</LinearLayout>
(3)用到的style
<!--通用Title的右側(cè)按鈕--> <style name="ToolBar_iv_Right"> <item name="android:layout_width">@dimen/toolbar_icon_dimen</item> <item name="android:layout_height">@dimen/toolbar_icon_dimen</item> <item name="android:layout_alignParentRight">true</item> <item name="android:layout_gravity">end</item> <item name="android:clickable">true</item> <item name="android:background">?android:actionBarItemBackground</item> <item name="android:padding">15dp</item> </style> <!--通用Title的TextView--> <style name="ToolBar_tv_Title"> <item name="android:layout_width">wrap_content</item> <item name="android:layout_height">wrap_content</item> <item name="android:layout_centerVertical">true</item> <item name="android:layout_marginLeft">@dimen/toolbar_title_haveBack_marginStart</item> <item name="android:layout_marginRight">@dimen/toolbar_title_haveBack_marginEnd</item> <item name="android:gravity">center</item> <item name="android:singleLine">true</item> <item name="android:textColor">@color/white</item> <item name="android:textSize">20sp</item> </style>
3.功能代碼
(1)基類 BaseEventActivity.Java
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import org.greenrobot.eventbus.EventBus;
import butterknife.ButterKnife;
public abstract class BaseEventActivity extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getIntentData();
setContentView(getLayoutResId());
ButterKnife.bind(this);
EventBus.getDefault().register(this);
init();
}
protected void getIntentData() {
}
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
protected abstract void init();
protected abstract int getLayoutResId();
}
(2)服務(wù)器主界面 Function_Socket.java
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.view.View;
import android.widget.TextView;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;
import butterknife.BindView;
import butterknife.OnClick;
/**
* 服務(wù)器界面
*/
public class Function_Socket_Server extends BaseEventActivity {
@BindView(R.id.tv_localAddress)
TextView tv_localAddress;
@BindView(R.id.tv_receivedContent)
TextView tv_receivedContent;
@BindView(R.id.tv_decryptContent)
TextView tv_decryptContent;
private LocalService localService;//用于啟動監(jiān)聽的服務(wù)
private ServiceConnection sc;//服務(wù)連接
@Override
protected void init() {
tv_localAddress.setText(ToolUtil.getHostIP());
sc = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
LocalService.LocalBinder localBinder = (LocalService.LocalBinder) service;
localService = localBinder.getService();
localService.startWaitDataThread();
ToastUtil.showToast(Function_Socket_Server.this, "監(jiān)聽已啟動");
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
connection();
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void getData(String data) {
tv_receivedContent.setText(data);
tv_decryptContent.setText(AESUtil.decrypt(ConstantUtil.password, data));
}
/**
* 綁定service
*/
private void connection() {
Intent intent = new Intent(this, LocalService.class);
bindService(intent, sc, BIND_AUTO_CREATE);
}
@Override
protected int getLayoutResId() {
return R.layout.function_socket_server;
}
/**
* 獲取連接到本機(jī)熱點(diǎn)上的手機(jī)ip
*/
private ArrayList<String> getConnectedIP() {
ArrayList<String> connectedIP = new ArrayList<>();
try {
//通過讀取配置文件實(shí)現(xiàn)
BufferedReader br = new BufferedReader(new FileReader(
"/proc/net/arp"));
String line;
while ((line = br.readLine()) != null) {
String[] splitted = line.split(" +");
if (splitted.length >= 4) {
String ip = splitted[0];
connectedIP.add(ip);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return connectedIP;
}
@OnClick({R.id.btn_startListener, R.id.btn_stopListener, R.id.btn_getUser})
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_startListener://啟動監(jiān)聽
connection();
break;
case R.id.btn_stopListener://停止監(jiān)聽
if (sc != null)
unbindService(sc);
break;
case R.id.btn_getUser://刷新連接到此設(shè)備的IP并清空之前接收到的數(shù)據(jù)
ArrayList<String> connectedIP = getConnectedIP();
StringBuilder resultList = new StringBuilder();
for (String ip : connectedIP) {
resultList.append(ip);
resultList.append("\n");
}
ToastUtil.showToast(this, "連接到手機(jī)上的Ip是:" + resultList.toString());
tv_decryptContent.setText("");
tv_receivedContent.setText("");
break;
}
}
public void onDestroy() {
super.onDestroy();
if (sc != null)
unbindService(sc);
}
}
(3)客戶端主界面 Function_Socket_Client.java
import android.app.ProgressDialog;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import butterknife.BindView;
import butterknife.OnClick;
/**
* 客戶端界面
*/
public class Function_Socket_Client extends BaseEventActivity {
@BindView(R.id.edtTxt_Content)
EditText edtTxt_Content;
@BindView(R.id.edtTxt_serverAddress)
EditText edtTxt_serverAddress;
private ProgressDialog mProgressDialog;//加載的小菊花
/**
* 初始化
*/
@Override
protected void init() {
}
@Override
protected int getLayoutResId() {
return R.layout.function_socket_client;
}
@OnClick(R.id.btn_encryptAndSend)
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_encryptAndSend:
String s = edtTxt_Content.getText().toString().trim();
String ip = edtTxt_serverAddress.getText().toString().trim();
if (ToolUtil.IsIpv4(ip)) {
new SendDataThread(ip, AESUtil.encrypt(ConstantUtil.password, s), ConstantUtil.port).start();//消息發(fā)送方啟動線程發(fā)送消息
showProgressDialog("嘗試發(fā)送數(shù)據(jù)到\n\t\t" + ip, true);
} else {
ToastUtil.showToast(this, "IP不合法!");
}
break;
}
}
/**
* 連接結(jié)果
*
* @param resultCode 0:連接超時(shí);1:發(fā)送成功 2:失敗
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void sendResult(Integer resultCode) {
Log.i("succ", "=" + resultCode);
dismissProgressDialog();
switch (resultCode) {
case ConstantUtil.CODE_SUCCESS:
ToastUtil.showToast(this, "發(fā)送成功");
break;
case ConstantUtil.CODE_TIMEOUT:
ToastUtil.showToast(this, "連接超時(shí)");
break;
case ConstantUtil.CODE_UNKNOWN_HOST:
ToastUtil.showToast(this, "錯(cuò)誤-未知的host");
break;
}
}
/**
* 數(shù)據(jù)加載小菊花
*
* @param msg 內(nèi)容
* @param isCancel 是否允許關(guān)閉 true - 允許 false - 不允許
*/
public void showProgressDialog(final String msg, final boolean isCancel) {
runOnUiThread(new Runnable() {
@Override
public void run() {
try {
if (mProgressDialog == null) {
mProgressDialog = new ProgressDialog(Function_Socket_Client.this);
}
if (mProgressDialog.isShowing()) {
return;
}
mProgressDialog.setMessage(msg);
mProgressDialog.setCancelable(isCancel);
mProgressDialog.setCanceledOnTouchOutside(false);
mProgressDialog.setOnCancelListener(null);
mProgressDialog.show();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* 隱藏?cái)?shù)據(jù)加載的進(jìn)度小菊花
**/
public void dismissProgressDialog() {
try {
if (mProgressDialog != null && mProgressDialog.isShowing()) {
runOnUiThread(
new Runnable() {
@Override
public void run() {
mProgressDialog.dismiss();
}
}
);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
(4)LocalService.java
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
/**
* 此服務(wù)用于啟動監(jiān)聽線程
*/
public class LocalService extends Service {
private IBinder iBinder = new LocalService.LocalBinder();
@Override
public IBinder onBind(Intent intent) {
return iBinder;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
public void startWaitDataThread() {
new ListenThread(ConstantUtil.port).start();
}
//定義內(nèi)容類繼承Binder
public class LocalBinder extends Binder {
//返回本地服務(wù)
public LocalService getService() {
return LocalService.this;
}
}
}
(5)ListenThread.java
import org.greenrobot.eventbus.EventBus;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 監(jiān)聽線程
*/
public class ListenThread extends Thread {
private ServerSocket serverSocket;
public ListenThread(int port) {
try {
serverSocket = new ServerSocket(port);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (true) {
try {
if (serverSocket != null) {
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
if (inputStream != null) {
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
String str;
str = in.readLine();
EventBus.getDefault().post(str);
socket.close();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
(6)SendDataThread.java
import android.util.Log;
import org.greenrobot.eventbus.EventBus;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
/**
* 數(shù)據(jù)發(fā)送線程
*/
public class SendDataThread extends Thread {
private Socket socket;
private String ip;//接收方的IP
private int port;//接收方的端口號
private String data;//準(zhǔn)備發(fā)送的數(shù)據(jù)
public SendDataThread(String ip, String data, int port) {
this.ip = ip;
this.data = data;
this.port = port;
}
@Override
public void run() {
try {
socket = new Socket();
socket.connect(new InetSocketAddress(ip,port),ConstantUtil.TIME_MILLIS);//設(shè)置超時(shí)時(shí)間
} catch (UnknownHostException e) {
EventBus.getDefault().post(ConstantUtil.CODE_UNKNOWN_HOST);
Log.d("error", "SendDataThread.init() has UnknownHostException" + e.getMessage());
} catch (SocketTimeoutException e) {
EventBus.getDefault().post(ConstantUtil.CODE_TIMEOUT);
Log.d("error", "SendDataThread.init() has TimeoutException:" + e.getMessage());
}catch (IOException e){
Log.d("error", "SendDataThread.init() has IOException:" + e.getMessage());
}
if (socket != null&&socket.isConnected()) {
try {
OutputStream ops = socket.getOutputStream();
OutputStreamWriter opsw = new OutputStreamWriter(ops);
BufferedWriter writer = new BufferedWriter(opsw);
writer.write(data + "\r\n\r\n");//由于socket使用緩沖區(qū)進(jìn)行讀寫數(shù)據(jù),因此使用\r\n\r\n用于表明數(shù)據(jù)已寫完.不加這個(gè)會導(dǎo)致數(shù)據(jù)無法發(fā)送
EventBus.getDefault().post(ConstantUtil.CODE_SUCCESS);
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
(7)AESUtil.java
import android.util.Log;
import java.io.UnsupportedEncodingException;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
* AES加密工具類
*/
public class AESUtil {
// private static final String CipherMode = "AES/ECB/PKCS5Padding";使用ECB加密,不需要設(shè)置IV,但是不安全
private static final String CipherMode = "AES/CFB/NoPadding";//使用CFB加密,需要設(shè)置IV
/**
* 生成加密后的密鑰
*
* @param password 密鑰種子
* @return isSucceed
*/
private static SecretKeySpec createKey(String password) {
byte[] data = null;
if (password == null) {
password = "";
}
StringBuilder sb = new StringBuilder(32);
sb.append(password);
while (sb.length() < 32) {
sb.append("0");
}
if (sb.length() > 32) {
sb.setLength(32);
}
try {
data = sb.toString().getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return new SecretKeySpec(data, "AES");
}
// /** 加密字節(jié)數(shù)據(jù) **/
private static byte[] encrypt(byte[] content, String password) {
try {
SecretKeySpec key = createKey(password);
System.out.println(key);
Cipher cipher = Cipher.getInstance(CipherMode);
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(
new byte[cipher.getBlockSize()]));
return cipher.doFinal(content);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
// /** 加密(結(jié)果為16進(jìn)制字符串) **/
public static String encrypt(String password, String content) {
Log.d("加密前", "seed=" + password + "\ncontent=" + content);
byte[] data = null;
try {
data = content.getBytes("UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
data = encrypt(data, password);
String result = byte2hex(data);
Log.d("加密后", "result=" + result);
return result;
}
// /** 解密字節(jié)數(shù)組 **/
private static byte[] decrypt(byte[] content, String password) {
try {
SecretKeySpec key = createKey(password);
Cipher cipher = Cipher.getInstance(CipherMode);
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(
new byte[cipher.getBlockSize()]));
return cipher.doFinal(content);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
// /** 解密16進(jìn)制的字符串為字符串 **/
public static String decrypt(String password, String content) {
Log.d("解密前", "seed=" + password + "\ncontent=" + content);
byte[] data = null;
try {
data = hex2byte(content);
} catch (Exception e) {
e.printStackTrace();
}
data = decrypt(data, password);
if (data == null)
return null;
String result = null;
try {
result = new String(data, "UTF-8");
Log.d("解密后", "result=" + result);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return result;
}
// /** 字節(jié)數(shù)組轉(zhuǎn)成16進(jìn)制字符串 **/
private static String byte2hex(byte[] b) { // 一個(gè)字節(jié)的數(shù),
StringBuilder sb = new StringBuilder(b.length * 2);
String tmp ;
for (byte aB : b) {
// 整數(shù)轉(zhuǎn)成十六進(jìn)制表示
tmp = (Integer.toHexString(aB & 0XFF));
if (tmp.length() == 1) {
sb.append("0");
}
sb.append(tmp);
}
return sb.toString().toUpperCase(); // 轉(zhuǎn)成大寫
}
// /** 將hex字符串轉(zhuǎn)換成字節(jié)數(shù)組 **/
private static byte[] hex2byte(String inputString) {
if (inputString == null || inputString.length() < 2) {
return new byte[0];
}
inputString = inputString.toLowerCase();
int l = inputString.length() / 2;
byte[] result = new byte[l];
for (int i = 0; i < l; ++i) {
String tmp = inputString.substring(2 * i, 2 * i + 2);
result[i] = (byte) (Integer.parseInt(tmp, 16) & 0xFF);
}
return result;
}
}
(8)ConstantUti.java
/**
* 常量類
*/
public class ConstantUtil {
public static final int TIME_MILLIS = 5 * 1000;//連接超時(shí)時(shí)間
public static final int port = 25256;//端口號
public static final String password = "123456885";//加密所使用的密鑰
public static final int CODE_TIMEOUT = 0;//連接超時(shí)
public static final int CODE_SUCCESS = 1;//連接成功
public static final int CODE_UNKNOWN_HOST = 2;//錯(cuò)誤-未知的host
}
(9)ToolUtil.java
import android.util.Log;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
/**
* 工具類
*/
public class ToolUtil {
/**
* 獲取ip地址
* 如果是移動網(wǎng)絡(luò),會顯示自己的公網(wǎng)IP,如果是局域網(wǎng),會顯示局域網(wǎng)IP
* 因此本例中服務(wù)器端需要斷開移動網(wǎng)絡(luò)以得到本機(jī)局域網(wǎng)IP
*/
public static String getHostIP() {
String hostIp = null;
try {
Enumeration nis = NetworkInterface.getNetworkInterfaces();
InetAddress ia;
while (nis.hasMoreElements()) {
NetworkInterface ni = (NetworkInterface) nis.nextElement();
Enumeration<InetAddress> ias = ni.getInetAddresses();
while (ias.hasMoreElements()) {
ia = ias.nextElement();
if (ia instanceof Inet6Address) {
continue;// skip ipv6
}
String ip = ia.getHostAddress();
if (!"127.0.0.1".equals(ip)) {
hostIp = ia.getHostAddress();
break;
}
}
}
} catch (SocketException e) {
Log.i("error", "SocketException");
e.printStackTrace();
}
return hostIp;
}
/**
* 判斷地址是否為IPV4地址
*/
public static boolean IsIpv4(String ipv4) {
if (ipv4 == null || ipv4.length() == 0) {
return false;//字符串為空或者空串
}
String[] parts = ipv4.split("\\.");//因?yàn)閖ava doc里已經(jīng)說明, split的參數(shù)是reg, 即正則表達(dá)式, 如果用"|"分割, 則需使用"\\|"
if (parts.length != 4) {
return false;//分割開的數(shù)組根本就不是4個(gè)數(shù)字
}
for (String part : parts) {
try {
int n = Integer.parseInt(part);
if (n < 0 || n > 255) {
return false;//數(shù)字不在正確范圍內(nèi)
}
} catch (NumberFormatException e) {
return false;//轉(zhuǎn)換數(shù)字不正確
}
}
return true;
}
}
(10)ToastUtil.java
import android.content.Context;
import android.widget.Toast;
public class ToastUtil {
private static Toast mToast = null;
/**
* Toast方法
*
* @param text 需要展示的文本
* @param context 所需上下文
*/
public static void showToast(Context context, String text) {
if (text != null) {
if (mToast == null) {
mToast = Toast.makeText(context, text, Toast.LENGTH_SHORT);
} else {
mToast.setText(text);
mToast.setDuration(Toast.LENGTH_SHORT);
}
mToast.show();
}
}
}
3.權(quán)限及聲明
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/> <!--service部分--> <service android:name="com.test.test.LocalService"/>
代碼到此為止了,功能比較簡單。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- 服務(wù)器共享文件夾設(shè)置軟件、局域網(wǎng)共享文件夾加密工具、文件共享服務(wù)器軟件的選擇
- 使用Java代碼獲取服務(wù)器性能信息及局域網(wǎng)內(nèi)主機(jī)名
- 局域網(wǎng)“內(nèi)鬼” 非授權(quán)DHCP服務(wù)器防范策略
- 新手架設(shè)魔獸單機(jī)和局域網(wǎng)服務(wù)器教程
- 局域網(wǎng)代理服務(wù)器組建方案 教程
- 局域網(wǎng)內(nèi)架設(shè)DNS服務(wù)器要謹(jǐn)慎
- 局域網(wǎng)中架設(shè)Win 2003終端服務(wù)器
- 基于http.server搭建局域網(wǎng)服務(wù)器過程解析
相關(guān)文章
Android實(shí)現(xiàn)RecyclerView添加分割線的簡便方法
這篇文章主要介紹了Android實(shí)現(xiàn)RecyclerView添加分割線的簡便方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07
Android實(shí)現(xiàn)ListView分頁加載數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)ListView分頁加載數(shù)據(jù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11
務(wù)必掌握的Android十六進(jìn)制狀態(tài)管理最佳實(shí)踐
這篇文章主要為大家介紹了務(wù)必掌握的Android十六進(jìn)制狀態(tài)管理最佳實(shí)踐,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
Android開發(fā)中一個(gè)簡單實(shí)用的調(diào)試應(yīng)用技巧分享
這篇文章主要跟大家分享了一個(gè)簡單實(shí)用的Android調(diào)試應(yīng)用技巧,文中介紹的非常詳細(xì),相信對大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友下面來一起看看吧。2017-05-05
Android APT 實(shí)現(xiàn)控件注入框架SqInject的示例
這篇文章主要介紹了Android APT 實(shí)現(xiàn)控件注入框架SqInject的示例,幫助大家更好的理解和學(xué)習(xí)使用Android,感興趣的朋友可以了解下2021-03-03
Android 中對JSON數(shù)據(jù)解析實(shí)例代碼
這篇文章主要介紹了Android 中對JSON數(shù)據(jù)解析實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-03-03
Android使用RecyclerView實(shí)現(xiàn)水平滾動控件
這篇文章給大家介紹了利用Android使用RecyclerView實(shí)現(xiàn)水平滾動的效果,本文做了一個(gè)年齡滾動控件的例子,對大家開發(fā)Android具有一定參考借鑒價(jià)值,有需要的可以參考借鑒。2016-09-09

