今天來說說wifi的調用方法:
參考這個檔案。其中針對wifi的相關調用做了一個包裝。
這個程式主要實現了幾個功能:
因為用不到,所以也沒有測試...以後有機會會試著實驗看看的,
而有關database的調用,可以參考這裡(裡面有很囉唆的說明)。
回到這個程式,其中裡面有三個class:
這是關於wifi使用的WifiHelper:
最後便是調用的MainClass了:
參考這個檔案。其中針對wifi的相關調用做了一個包裝。
這個程式主要實現了幾個功能:
- 獲取目前wifi所連接到的裝置,其各項資訊。
- 獲取wifi搜尋過得裝置,所獲得結果的各項資訊。
- 對wifi進行30秒的搜尋(每秒搜尋一次,連續30次),然後將結果全部都存到database中。
因為用不到,所以也沒有測試...以後有機會會試著實驗看看的,
而有關database的調用,可以參考這裡
回到這個程式,其中裡面有三個class:
- Main。Activity作為互動控制的內容。
- WifiHelper。將wifi所有的項目都包裝起來使用,參考至這裡。
- DatabaseForWifiTest。是實做database調用的一個類別。
至於方法,裡面有詳細的註解,先試著看一次code吧!
首先要注意有幾個權限要加上去:
首先要注意有幾個權限要加上去:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="adnroid.permission.ACCESS_CHECKIN_PROPERTTES" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
然後,這是關於SQLite調用的class,DatabaseForWifiTest:
package wifiTest.susan.idea;
import java.sql.Timestamp;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
public class DatabaseForWifiTest {
private static final String TAG = DatabaseForWifiTest.class.getSimpleName();
static final int VERSION = 1;
static final String DATABASE = android.os.Environment
.getExternalStorageDirectory().getAbsolutePath()
+ "/wifiTest/database.db3";
static final String TABLE = "tableOfthisDataBase";
/**
* "date", "bssid", "ssid","level"
*/
public static final String[] KEY_COLUMNS = { "date", "bssid", "ssid",
"level" };
private static class DbHelper extends SQLiteOpenHelper {
public DbHelper(Context context) {
super(context, DATABASE, null, VERSION);
}
@Override
// 只會被呼叫一次,在第一次資料庫建立的時候
public void onCreate(SQLiteDatabase db) {
Log.i(TAG, "Creating database: " + DATABASE);
db.execSQL(getCreateSQL());
}
public String getCreateSQL() {
StringBuffer mStringBuffer = new StringBuffer();
mStringBuffer.append("CREATE TABLE IF NOT EXISTS " + TABLE + " ("
+ "_id" + " INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
+ KEY_COLUMNS[0]
+ " TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,");
for (int n = 1; n < KEY_COLUMNS.length; n++) {
mStringBuffer.append(KEY_COLUMNS[n] + " TEXT, ");
}
mStringBuffer.delete(mStringBuffer.length() - 2,
mStringBuffer.length());// 刪除", "
mStringBuffer.append(");");
return mStringBuffer.toString();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub
db.execSQL("DROP TABLE IF EXISTS " + TABLE);
this.onCreate(db);
}
}
private final DbHelper dbHelper;
private SQLiteDatabase db;
public DatabaseForWifiTest(Context context) {
dbHelper = new DbHelper(context);
Log.i(TAG, "Initialized database in Constructor of DB");
}
public DatabaseForWifiTest openToWrite() throws SQLException {
db = dbHelper.getWritableDatabase(); // 若資料庫存在則開啟;若不存在則建立一個新的
return this;
}
public DatabaseForWifiTest openToRead() throws SQLException {
db = dbHelper.getReadableDatabase(); // 若資料庫存在則開啟;若不存在則建立一個新的
return this;
}
public void close() {
dbHelper.close();
}
/** 添加資料 */
public void insert(ContentValues values) throws SQLException {
db.insertOrThrow(TABLE, null, values);
Log.i(TAG, "insert data into db");
}
/**
* 更新資料(測試)
*
* @param column
* 輸入欄位名稱
* @param oldValue
* 輸入欄位內欲查詢的變數
* @param newValue
* 輸入要更新的值
*/
public void update(String column, String oldValue, ContentValues newValue)
throws SQLException {
db.update(TABLE, newValue, column + "=" + oldValue, null);
}
/**
* 刪除資料(測試)
*
* @param column
* 輸入欄位名稱
* @param value
* 輸入欄位內欲查詢的變數
*/
public void deleteData(String column, String value) throws SQLException {
db.delete(TABLE, column + "=" + value, null);
}
/**
* 獲得資料(測試)
*
* @param column
* 輸入欄位名稱
* @param value
* 輸入欄位內欲查詢的變數
*/
public Cursor getData(String column, String value) {
String cmd = "SELECT * FROM " + TABLE + " WHERE " + column + "="
+ value;
return db.rawQuery(cmd, null);
}
/** 獲得全部資料 */
public Cursor getAll() {
// return db.rawQuery("SELECT * FROM " + TABLE, null);
return db.query(TABLE, null, null, null, null, null, null);
}
/** 依照時間間隔獲取資料 */
public Cursor getIntervalData(Timestamp from, Timestamp to) {
String cmd = "SELECT * FROM " + TABLE + " WHERE (" + KEY_COLUMNS[0]
+ " >= '" + from.toString() + "') AND (" + KEY_COLUMNS[0]
+ " <= '" + to.toString() + "')";
return db.rawQuery(cmd, null);
}
/** 清除全部的資料 */
public boolean clearAll() {
return db.delete(TABLE, null, null) > 0;
}
}
這是關於wifi使用的WifiHelper:
package wifiTest.susan.idea;
/*參考:http://www.cnblogs.com/fly_binbin/archive/2010/12/21/1913230.html*/
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.List;
import android.content.Context;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.WifiLock;
import android.util.Log;
public class WifiHelper {
private final String TAG = this.getClass().getSimpleName();
private WifiManager wifiManager;// 聲明管理對象
private WifiInfo wifiInfo;// Wifi信息
private List<ScanResult> scanResultList; // 掃描出來的網絡連接列表
private List<WifiConfiguration> wifiConfigList;// 網絡配置列表
private WifiLock wifiLock;// Wifi鎖
public WifiHelper(Context context) {
this.wifiManager = (WifiManager) context
.getSystemService(Context.WIFI_SERVICE);// 獲取Wifi服務
Log.i(TAG, "產生類別!");
// 得到Wifi信息
this.wifiInfo = wifiManager.getConnectionInfo();// 得到連接信息
}
/**
* 獲得wifi狀態
*
* @return true if Wi-Fi is enabled
*/
public boolean getWifiStatus() {
return wifiManager.isWifiEnabled();
}
/** 打開 wifi */
public boolean openWifi() {
if (!wifiManager.isWifiEnabled()) {
try {
return wifiManager.setWifiEnabled(true);
} catch (SecurityException e) {
return false;
}
} else {
return false;
}
}
/** 關閉 wifi */
public boolean closeWifi() {
if (!wifiManager.isWifiEnabled()) {
return true;
} else {
try {
return wifiManager.setWifiEnabled(false);
} catch (SecurityException e) {
return false;
}
}
}
/**
* 鎖定wifi。 Locks the Wi-Fi radio on until release is called. If this
* WifiLock is reference-counted, each call to acquire will increment the
* reference count, and the radio will remain locked as long as the
* reference count is above zero. If this WifiLock is not reference-counted,
* the first call to acquire will lock the radio, but subsequent calls will
* be ignored. Only one call to release will be required, regardless of the
* number of times that acquire is called.
*/
public void lockWifi() {
// 其實鎖定WiFI就是判斷wifi是否建立成功,在這裡使用的是held,握手的意思acquire 得到!
// 參考:http://www.asiteof.me/2011/02/wakelock-wifilock/
if (wifiLock == null) {
wifiLock = wifiManager.createWifiLock("flyfly"); // 創建一個鎖的標誌
}
wifiLock.acquire();
}
/** 解鎖wifi */
public void unLockWifi() {
if (!wifiLock.isHeld()) {
wifiLock.release(); // 釋放資源
}
}
/** 掃描網絡 */
public void startScan() {
if (wifiManager.startScan()) {
scanResultList = wifiManager.getScanResults(); // 掃描返回結果列表
wifiConfigList = wifiManager.getConfiguredNetworks(); // 掃描配置列表
} else {
Log.e(TAG, "wifiManager.startScan().. The scan was initiated.");
}
}
/** 返回結果列表 */
public List<ScanResult> getWifiList() {
return scanResultList;
}
/** 返回配置列表 */
public List<WifiConfiguration> getWifiConfigList() {
return wifiConfigList;
}
/**
* 獲得頻段標識符號,範圍為1-14。
* 信道标识符、频率(单位:MHz):(1、2412),(2、2417),(3、2422),(4、2427),(5、2432),(6、2437),(7
* 、2442),(8、2447) (9、2452),(10、2457)
* ,(11、2462),(12、2467),(13、2472),(14、2484)
*/
public int getWifiFrequency(ScanResult mScanResult){
int frequency=mScanResult.frequency;
return (frequency-2412)/5+1;
}
/**
* 獲取掃描列表
*
* @return 返回一個StringBuilder值,格式為StringBuilder
*/
public StringBuilder lookUpscan() {
StringBuilder scanBuilder = new StringBuilder();
for (int i = 0; i < scanResultList.size(); i++) {
scanBuilder.append("編號:" + (i + 1));
scanBuilder.append(scanResultList.get(i).toString()); // 所有信息
scanBuilder.append("\n");
}
return scanBuilder;
}
/** 獲取指定信號的強度 */
public Integer getScanResultListLevel(int NetId) {
return scanResultList.get(NetId).level;
}
/** 獲取本機Mac地址 */
public String getMac() {
return (wifiInfo == null) ? null : wifiInfo.getMacAddress();
}
/** 獲取當前連接的BSSID */
public String getBSSID() {
return (wifiInfo == null) ? null : wifiInfo.getBSSID();
}
/** 獲取當前連接的SSID */
public String getSSID() {
return (wifiInfo == null) ? null : wifiInfo.getSSID();
}
/** 返回當前連接的網絡的ID */
public Integer getCurrentNetId() {
return (wifiInfo == null) ? null : wifiInfo.getNetworkId();
}
/** 返回所有信息 */
public WifiInfo getWifiInfo() {
return this.wifiInfo;
}
/** 獲取IP地址 */
public Integer getIP() {
return (wifiInfo == null) ? null : wifiInfo.getIpAddress();
}
/** 獲得當前連接的Ip address */
public String getLocalIpAddress() {
// 參考:http://lovezhou.iteye.com/blog/945880
try {
for (Enumeration<NetworkInterface> en = NetworkInterface
.getNetworkInterfaces(); en.hasMoreElements();) {
NetworkInterface intf = en.nextElement();
for (Enumeration<InetAddress> enumIpAddr = intf
.getInetAddresses(); enumIpAddr.hasMoreElements();) {
InetAddress inetAddress = enumIpAddr.nextElement();
if (!inetAddress.isLoopbackAddress()) {
return inetAddress.getHostAddress().toString();
}
}
}
} catch (SocketException ex) {
Log.e(TAG, ex.toString());
}
return null;
}
// -----不知道怎麼用......
/** 添加一個連接 */
public Boolean addNetWordLink(WifiConfiguration config) {
int NetId = wifiManager.addNetwork(config);
return wifiManager.enableNetwork(NetId, true);
}
/** 禁用一個鏈接 */
public Boolean disableNetWordLick(int NetId) {
wifiManager.disableNetwork(NetId);
return wifiManager.disconnect();
}
/** 移除一個鏈接 */
public Boolean removeNetworkLink(int NetId) {
return wifiManager.removeNetwork(NetId);
}
/** 不顯示SSID */
public void hiddenSSID(int NetId) {
wifiConfigList.get(NetId).hiddenSSID = true;
}
/** 顯示SSID */
public void displaySSID(int NetId) {
wifiConfigList.get(NetId).hiddenSSID = false;
}
// -----不知道怎麼用......
}
最後便是調用的MainClass了:
package wifiTest.susan.idea;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.TimeZone;
import android.app.Activity;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.view.View.OnLongClickListener;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.ToggleButton;
public class Main extends Activity {
private final String TAG = this.getClass().getSimpleName();
private ToggleButton[] toggleButtons;
final int[] toggleButtonsId = new int[] { R.id.toggleButton_WifiInfo,
R.id.toggleButton_sacnResult, R.id.toggleButton_scanAndSave };
private TextView textView;
private WifiHelper wifiHelper;
private Handler handler;
private RelativeLayout relativeLayoutWait;
private DatabaseForWifiTest database;
private boolean theadOfscanOff;
ProgressBar progressBar_onWork;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
init();
}
/** 初始化 */
private void init() {
database = new DatabaseForWifiTest(this);
progressBar_onWork = (ProgressBar) this
.findViewById(R.id.progressBar_onWork);
progressBar_onWork.setVisibility(ProgressBar.GONE);
handler = new Handler();
textView = (TextView) this.findViewById(R.id.textView_show);
relativeLayoutWait = (RelativeLayout) this
.findViewById(R.id.RelativeLayout_wait);
relativeLayoutWait.setVisibility(RelativeLayout.GONE);
toggleButtons = new ToggleButton[toggleButtonsId.length];
for (int n = 0; n < toggleButtons.length; n++) {
toggleButtons[n] = (ToggleButton) this
.findViewById(toggleButtonsId[n]);
toggleButtons[n]
.setOnCheckedChangeListener(mOnCheckedChangeListener);
}
if (wifiHelper == null) {
wifiHelper = new WifiHelper(getApplicationContext());
}
Log.d(TAG, "開啟wifi..");
wifiHelper.openWifi();
wifiHelper.lockWifi();
textView.setOnLongClickListener(mOnClickListener);
}
OnLongClickListener mOnClickListener = new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
database.openToRead();
Cursor mCursor = database.getAll();
if (mCursor.getCount() > 0) {
mCursor.moveToFirst();
StringBuffer mStringBuffer = new StringBuffer();
int count = 0;
while (mCursor.moveToNext()) {
count++;
mStringBuffer.append("第" + count + "筆 資料:\n");
mStringBuffer
.append(mCursor.getString(mCursor
.getColumnIndexOrThrow(DatabaseForWifiTest.KEY_COLUMNS[0])));
mStringBuffer.append("\n");
mStringBuffer
.append(mCursor.getString(mCursor
.getColumnIndexOrThrow(DatabaseForWifiTest.KEY_COLUMNS[1])));
mStringBuffer.append("\n");
mStringBuffer
.append(mCursor.getString(mCursor
.getColumnIndexOrThrow(DatabaseForWifiTest.KEY_COLUMNS[2])));
mStringBuffer.append("\n");
mStringBuffer
.append(mCursor.getString(mCursor
.getColumnIndexOrThrow(DatabaseForWifiTest.KEY_COLUMNS[3])));
mStringBuffer.append("\n");
}
setText(mStringBuffer);
database.close();
}
return true;
}
};
OnCheckedChangeListener mOnCheckedChangeListener = new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
if (isChecked) {// turn to on
for (ToggleButton toggleButton : toggleButtons) {
if (toggleButton.getId() != buttonView.getId()) {
if (toggleButton.isChecked())
toggleButton.setChecked(false);
toggleButton.setVisibility(ToggleButton.GONE);
}
}
if (buttonView.getId() == toggleButtonsId[2]) {
database.openToWrite();// 開啟database
theadOfscanOff = false;
new Thread(new Runnable() {
@Override
public void run() {
int n=0;
while (n<5) {
if (relativeLayoutWait.getVisibility() == RelativeLayout.GONE)
setVisibilityOfRelativeLayout(true);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
n++;
}
setVisibilityOfRelativeLayout(false);
int count = 0;// 初始化計數器
StringBuffer mStringBuffer = new StringBuffer();
ContentValues cv = new ContentValues();// 提供給予轉置SQLite的內容容器
Calendar c = Calendar.getInstance();
c.setTimeZone(TimeZone.getTimeZone("GMT+8:00"));// 設定時區為台灣時間。
Timestamp timestamp = new Timestamp(c
.getTimeInMillis());// 先行建立,於輸入database時置入
while ((!theadOfscanOff) && (count < 30)) {// 開始搜尋
setVisibilityOfProgressBar(true);
mStringBuffer.delete(0, mStringBuffer.length());
Log.d(TAG, "開始搜尋wifi訊號");
wifiHelper.startScan();// 開始搜尋
mStringBuffer.append("獲得搜尋wifi列表,第"
+ (count + 1) + "次。\n");
setText("");// 初始化文字框
for (ScanResult mScanResult : wifiHelper
.getWifiList()) {
// 放置訊息至文字框
mStringBuffer.append("獲得ScanResult內容\n");
putStringBufferFromScanResult(
mStringBuffer, mScanResult);
// 儲存訊息
putDatabaseValuesFromScanResult(database,
cv, timestamp, mScanResult);
setText(mStringBuffer.toString());
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
Log.d(TAG, "結束第" + (count + 1) + "次 搜尋的wifi訊號");
}// 搜尋結束
setVisibilityOfProgressBar(false);
database.close();// 關閉database
}
}).start();
} else if (buttonView.getId() == toggleButtonsId[1]) {
new Thread(new Runnable() {
@Override
public void run() {
int n=0;
while (n<5) {
if (relativeLayoutWait.getVisibility() == RelativeLayout.GONE)
setVisibilityOfRelativeLayout(true);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
n++;
}
setVisibilityOfRelativeLayout(false);
StringBuffer mStringBuffer = new StringBuffer();
wifiHelper.startScan();
mStringBuffer.append("獲得搜尋wifi ScanResult:\n");
for (ScanResult mScanResult : wifiHelper
.getWifiList()) {
putStringBufferFromScanResult(mStringBuffer,
mScanResult);
setText(mStringBuffer.toString());
}
for(WifiConfiguration mWifiConfiguration:wifiHelper.getWifiConfigList()){
mStringBuffer.append("獲得WifiConfiguration:\n");
setText(mWifiConfiguration.toString());
}
}
}).start();
} else if (buttonView.getId() == toggleButtonsId[0]) {
StringBuffer mStringBuffer = new StringBuffer();
mStringBuffer.append("獲取目前wifi狀態:\n");
mStringBuffer.append("獲得WifiInfo():" + "\n");
mStringBuffer.append(wifiHelper.getWifiInfo().toString());
mStringBuffer.append("\n");
mStringBuffer.append("\n獲得Mac位址:" + "\n ");
mStringBuffer.append(wifiHelper.getMac());
mStringBuffer.append("\n獲得IP位址(整數):" + "\n ");
mStringBuffer.append(wifiHelper.getIP());
mStringBuffer.append("\n獲得IP位址(字串):" + "\n ");
mStringBuffer.append(wifiHelper.getLocalIpAddress());
mStringBuffer.append("\n獲得BSSID:" + "\n ");
mStringBuffer.append(wifiHelper.getBSSID());
mStringBuffer.append("\n獲得SSID:" + "\n ");
mStringBuffer.append(wifiHelper.getSSID());
mStringBuffer.append("\n獲得LinkSpeed:" + "\n ");
mStringBuffer.append(wifiHelper.getWifiInfo()
.getLinkSpeed());// what is this?
setText(mStringBuffer.toString());
}
} else {// turn to off
setText("");
if (buttonView.getId() == toggleButtonsId[2]) {
theadOfscanOff = true;
}
for (ToggleButton toggleButton : toggleButtons) {
if (toggleButton.getId() != buttonView.getId()) {
toggleButton.setVisibility(ToggleButton.VISIBLE);
}
}
}
}
};
@Override
public void onDestroy() {
wifiHelper.closeWifi();
wifiHelper.unLockWifi();
super.onDestroy();
}
/** 放置足夠的資訊至StringBuffer之中。 */
private StringBuffer putStringBufferFromScanResult(StringBuffer output,
ScanResult mScanResult) {
output.append("\n");
output.append("\n獲得BSSID\n");
output.append(mScanResult.BSSID);
output.append("\n獲得SSID\n");
output.append(mScanResult.SSID);
output.append("\n獲得level\n");
output.append(mScanResult.level);
output.append("\n獲得frequency\n");
/*
* The frequency in MHz of the channel over which the client is
* communicating with the access point.
*/
output.append(mScanResult.frequency);
output.append("\n獲得capabilities(加密能力)\n");
/*
* Describes the authentication, key management, and encryption schemes
* supported by the access point.
*/
output.append(mScanResult.capabilities);
output.append("\ndescribe contents,他只會回傳0..\n");
/*
* a bitmask indicating the set of special object types marshalled by
* the Parcelable.
*/
output.append(mScanResult.describeContents());// WTF?
return output;
}
/**
* 將wifiResult提取部份內容置入database中。
*
* @param database
* 欲置入的database
* @param contentValues
* 外部產生一個ContentValues物件,提供容器置入database
* @param timestamp
* 輸入一個時間,為系統搜尋AP時的一個時間格式。
* @param mScanResult
* 提供ScanResult物件以便提取內容
*/
private void putDatabaseValuesFromScanResult(DatabaseForWifiTest database,
ContentValues contentValues, Timestamp timestamp,
ScanResult mScanResult) {
contentValues.clear();
contentValues.put(DatabaseForWifiTest.KEY_COLUMNS[0],
timestamp.toString());
contentValues
.put(DatabaseForWifiTest.KEY_COLUMNS[1], mScanResult.BSSID);
contentValues.put(DatabaseForWifiTest.KEY_COLUMNS[2], mScanResult.SSID);
contentValues
.put(DatabaseForWifiTest.KEY_COLUMNS[3], mScanResult.level);
database.insert(contentValues);
}
private void setVisibilityOfProgressBar(final boolean toOpen) {
handler.post(new Runnable() {
@Override
public void run() {
if (toOpen) {
progressBar_onWork.setVisibility(ProgressBar.VISIBLE);
} else {
progressBar_onWork.setVisibility(ProgressBar.GONE);
}
}
});
}
private void setVisibilityOfRelativeLayout(final boolean toOpen) {
handler.post(new Runnable() {
@Override
public void run() {
if (toOpen) {
relativeLayoutWait.setVisibility(RelativeLayout.VISIBLE);
} else {
relativeLayoutWait.setVisibility(RelativeLayout.GONE);
}
}
});
}
private void setText(final CharSequence content) {
// Log.d(TAG,"顯示文字,內容為:"+content);
handler.post(new Runnable() {
@Override
public void run() {
textView.setText(content);
}
});
}
}
沒有留言:
張貼留言
你好,我是小書,如果文章內容有錯誤,或是看到有建議以及任何感想時,歡迎提出分享,我們一起學習一起努力。