【一步一個腳印】Tomcat+MySQL為自己的APP打造伺服器(3-1)Android 和 Service 的互動之GET方式
好久沒更新了,罪過罪過。最對不起的人莫過於那些支援和等待在下拙文的諸位,在此道一聲抱歉。管窺之見,僥倖博得各位認同,給了我莫大的鼓勵。
話休絮煩,文接前章。
到【一步一個腳印】Tomcat+MySQL為自己的APP打造伺服器(2-3)Servlet連線MySQL資料庫為止,我們已經將服務端的部分走通了:通過 Servlet 連線 MySQL ,分析業務需求進行響應的增刪改查操作返回對應的處理結果。(上一篇結尾是說接下來該說POST請求了,但是在準備這篇文章時發現POST再推後一篇,等我們把 Android 通過 GET 方式和 Servlet 伺服器互動全部走完了,回過頭來對比著說 POST 會更加明瞭,所以決定修正一下之前的思路,本章我們繼續完成 GET 的剩下內容)
很明顯,想要 Android 和伺服器進行互動,必然要使用到網路,為了解決後顧之憂,我們先下手為強,在 Manifest 檔案中宣告網路訪問許可權:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
這個許可權可不是平白無故就去申請的,因為我們要通過網路和伺服器互動,要完成這一互動過程,就要用到 Android 網路技術。Android 網路技術包含目前所有主流網路技術,比如你聽過的TCP/IP(Socket、ServiceSocket)、UDP... ...(媽的,不寫了。講真,網路這塊其實我已經不懂了,曾經真的懂過,反正大學時候網路基礎學的挺嗨,幾年不接觸已經恍如隔世了。以免誤人子弟,或者是遇到真正的大神被拆穿就尷尬了
在 Android 上傳送 HTTP 請求的方式一般有兩種:HttpURLConnection和 HttpClient,我們都來試用一下:
(一)HttpURLConnection 進行 HTTP 請求
先是 HttpURLConnection,直接上程式碼吧,用法有註釋:
Run,Fuck!報錯——public class HttpURLConActivity extends AppCompatActivity { private TextView tvContent; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_http_urlcon); tvContent = (TextView) findViewById(R.id.tv_content); // 這裡頁面上就一個簡單的TextView,用於展示獲取到報文內容 requestUsingHttpURLConnection(); } private void requestUsingHttpURLConnection() { // 網路通訊屬於典型的耗時操作,開啟新執行緒進行網路請求 new Thread(new Runnable() { @Override public void run() { HttpURLConnection connection = null; try { URL url = new URL("https://www.baidu.com"); // 宣告一個URL,注意——如果用百度首頁實驗,請使用https connection = (HttpURLConnection) url.openConnection(); // 開啟該URL連線 connection.setRequestMethod("GET"); // 設定請求方法,“POST或GET”,我們這裡用GET,在說到POST的時候再用POST connection.setConnectTimeout(8000); // 設定連線建立的超時時間 connection.setReadTimeout(8000); // 設定網路報文收發超時時間 InputStream in = connection.getInputStream(); // 通過連線的輸入流獲取下發報文,然後就是Java的流處理 BufferedReader reader = new BufferedReader(new InputStreamReader(in)); StringBuilder response = new StringBuilder(); String line; while ((line = reader.readLine()) != null){ response.append(line); } tvContent.setText(response.toString()); // 地雷 } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }).start(); } }
典型的子執行緒試圖操作 UI 元素報錯,為啥,因為網路請求是在新開的子執行緒中執行,當然不能直接拿到結果就給 TextView 賦值了!怎麼做?Android 的Handler訊息機制這不就用上了嘛!
/**
* 訊息處理
*/
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
if(msg.what == 1){
tvContent.setText(msg.obj.toString());
}
}
};
private void requestUsingHttpURLConnection() {
......
/* 獲取返回報文部分省略,將原來
* tvContent.setText(response.toString())替換為
* 給handler傳送訊息
*/
Message msg = new Message();
msg.what = 1;
msg.obj = response.toString();
Log.e("WangJ", response.toString());
handler.sendMessage(msg);
......
}
重新 Run,結果
什麼?看不懂,什麼鬼!其實伺服器返回的百度首頁就是這樣的 HTML 程式碼,只是平時我們使用瀏覽器開啟的時候,瀏覽器引擎幫我們把這些程式碼解析和展示成了花花綠綠的頁面,僅此而已。
(二)HttpClient 進行 HTTP 請求
HttpClient 是Apache 提供的 HTTP 網路訪問介面,但是原生 Android 系統內建了這套藉口,所以不用引入第三方 jar 就可以直接用。他可以和 HttpURLConnection 完成幾乎一模一樣的效果,但是兩者的使用方法還是有一些區別的。下面我們用程式碼來說明:
public class HttpClientActivity extends AppCompatActivity {
private TextView tvContent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_http_client);
tvContent = (TextView) findViewById(R.id.tv_content);
requestUsingHttpClient();
}
// 同樣的訊息機制
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 1) {
tvContent.setText(msg.obj.toString());
}
}
};
private void requestUsingHttpClient() {
new Thread(new Runnable() {
@Override
public void run() {
HttpClient client = new DefaultHttpClient(); // HttpClient 是一個介面,無法例項化,所以我們通常會建立一個DefaultHttpClient例項
HttpGet get = new HttpGet("https://www.baidu.com"); // 發起GET請求就使用HttpGet,發起POST請求則使用HttpPost,這裡我們先使用HttpGet
try {
HttpResponse httpResponse = client.execute(get); // 呼叫HttpClient物件的execute()方法
// 狀態碼200說明響應成功
if (httpResponse.getStatusLine().getStatusCode() == 200) {
HttpEntity entity = httpResponse.getEntity(); // 取出報文的具體內容
String response = EntityUtils.toString(entity, "utf-8"); // 報文編碼
// 傳送訊息
Message msg = new Message();
msg.what = 1;
msg.obj = response;
handler.sendMessage(msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
怎麼樣,還是挺簡潔的吧!Run,和前一個圖一樣,節省篇幅圖就不貼了。
(三)HttpClient 的窘境
年輕人,是不是要到問題了(沒遇到問題的請自覺忽略,如果你用的 Android Studio比較新,compileSdkVersion >= 23,相信你會遇到的)?是不是在寫程式碼中找不到 HttpClient 類?那就對了!因為從 Android 6.0(API 23) 往後 Google 又把 HttpClient 給幹掉了,為什麼?說是因為它介面、方法太多,API太過複雜,升級維護難以在當前版本API上進行,這就會導致 Android 版本相容上出現難以解決的問題,所以就把它幹掉了,因為使用 HttpURLConnection 也能達到同樣的效果,並且易於維護。囉嗦個屁呀,問題咋解決呢?改 SDK 版本唄,讓他SDK <= 22 就可以了。什麼!業務不允許?要求最新版 SDK?別怕,到 Apache 下載最新 jar 包匯入工程,還像以前一樣用,不過方法名可能不一樣了。
就這樣,Android 傳送 HTTP 請求就完成了。什麼?訊息處理機制太麻煩了?是的!我也舉得麻煩,其實 Android 官方也覺得麻煩,所以 Android 為了降低這個開發難度,提供了AsyncTask。AsyncTask就是一個封裝過的後臺任務類,顧名思義就是非同步任務,其實現原理也是基於非同步訊息處理機制,只是 Android給我們做了很好的封裝而已,相對於 Handler 更輕量,適用於簡單的非同步處理,但是在面對多個非同步任務更新同一個或同一組 UI 時的同步就比較困難,不瞭解不要緊,以後用用就知道問題在哪了,一口也吃不成個大胖子。下面我們在 http 請求時就用AsyncTask來處理吧——
(四)Android 和 Servlet 伺服器通過 HTTP GET 模式進行互動
來吧,來到了今天的主題。首先,請確保你的 Tomcat 上部署的 Servlet 已經啟動,確保資料庫服務正常啟動並且資料庫連線正常,有問題請參考之前的 【一步一個腳印】Tomcat+MySQL為自己的APP打造伺服器。
(雙12換了電腦,環境都是新裝的,如與之前的資料不符,請以自己的為準)
資料庫表就是這麼一個簡單的表
下邊是伺服器Servlet的程式碼,其實和之前文章中的程式碼原理上一模一樣,但是為了寫一個完整的互動,我這裡重寫了一個:
處理“註冊”邏輯的Servlet:
@WebServlet(description = "註冊使用的Servlet", urlPatterns = { "/RegisterServlet" })
public class RegisterServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* Default constructor.
*/
public RegisterServlet() {
LogUtil.log("RegisterServlet construct...");
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String method = request.getMethod();
if ("GET".equals(method)) {
LogUtil.log("請求方法:GET");
doGet(request, response);
} else if ("POST".equals(method)) {
LogUtil.log("請求方法:POST");
doPost(request, response);
} else {
LogUtil.log("請求方法分辨失敗!");
}
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String code = "";
String message = "";
String account = request.getParameter("account");
String password = request.getParameter("password");
LogUtil.log(account + ";" + password);
Connection connect = DatabaseUtil.getConnection();
try {
Statement statement = connect.createStatement();
String sql = "select account from " + DatabaseUtil.Table_Account + " where account='" + account + "'";
LogUtil.log(sql);
ResultSet result = statement.executeQuery(sql);
if (result.next()) { // 能查到該賬號,說明已經註冊過了
code = "100";
message = "該賬號已存在";
} else {
String sqlInsert = "insert into " + DatabaseUtil.Table_Account + "(account, password) values('"
+ account + "', '" + password + "')";
LogUtil.log(sqlInsert);
if (statement.executeUpdate(sqlInsert) > 0) { // 否則進行註冊邏輯,插入新賬號密碼到資料庫
code = "200";
message = "註冊成功";
} else {
code = "300";
message = "註冊失敗";
}
}
} catch (SQLException e) {
e.printStackTrace();
}
response.getWriter().append("code:").append(code).append(";message:").append(message);
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
@Override
public void destroy() {
LogUtil.log("RegisterServlet destory.");
super.destroy();
}
}
處理“登入”邏輯的Servlet:@WebServlet(description = "登入", urlPatterns = { "/LoginServlet" })
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public LoginServlet() {
super();
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String code = "";
String message = "";
String account = request.getParameter("account");
String password = request.getParameter("password");
LogUtil.log(account + ";" + password);
Connection connect = DatabaseUtil.getConnection();
try {
Statement statement = connect.createStatement();
String sql = "select account from " + DatabaseUtil.Table_Account + " where account='" + account
+ "' and password='" + password + "'";
LogUtil.log(sql);
ResultSet result = statement.executeQuery(sql);
if (result.next()) { // 能查到該賬號,說明已經註冊過了
code = "200";
message = "登陸成功";
} else {
code = "100";
message = "登入失敗,密碼不匹配或賬號未註冊";
}
} catch (SQLException e) {
e.printStackTrace();
}
response.getWriter().append("code:").append(code).append(";message:").append(message);
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
* response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
LogUtil.log("不支援POST方法");
}
}
/*請求和響應編碼格式設定、資料庫連線程式碼和之前的一樣,這裡就不重複貼了 */
接下來是Android客戶端的程式碼:
常量類
public class Constant {
public static String URL = "http://192.168.1.109:8080/FirstServletService/"; // IP地址請改為你自己的IP
public static String URL_Register = URL + "RegisterServlet";
public static String URL_Login = URL + "LoginServlet";
}
Activity的介面<?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:gravity="center"
android:orientation="vertical"
android:padding="@dimen/activity_vertical_margin">
<EditText
android:id="@+id/et_account"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="請輸入賬號" />
<EditText
android:id="@+id/et_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="請輸入登入密碼"
android:inputType="textPassword" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="horizontal">
<Button
android:id="@+id/btn_register"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Register" />
<Button
android:id="@+id/btn_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Login" />
</LinearLayout>
<!-- 用來顯示報文返回結果 -->
<TextView
android:id="@+id/tv_result"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
Activity的程式碼public class MainActivity extends Activity {
private EditText etAccount;
private EditText etPassword;
private TextView tvResult;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
etAccount = (EditText) findViewById(R.id.et_account);
etPassword = (EditText) findViewById(R.id.et_password);
tvResult = (TextView) findViewById(R.id.tv_result);
Button btnRegister = (Button) findViewById(R.id.btn_register);
btnRegister.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!StringUtil.isEmpty(etAccount.getText().toString())
&& !StringUtil.isEmpty(etPassword.getText().toString())) {
Log.e("WangJ", "都不空");
register(etAccount.getText().toString(), etPassword.getText().toString());
} else {
Toast.makeText(MainActivity.this, "賬號、密碼都不能為空!", Toast.LENGTH_SHORT).show();
}
}
});
Button btnLogin = (Button) findViewById(R.id.btn_login);
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!StringUtil.isEmpty(etAccount.getText().toString())
&& !StringUtil.isEmpty(etPassword.getText().toString())) {
Log.e("WangJ", "都不空");
login(etAccount.getText().toString(), etPassword.getText().toString());
} else {
Toast.makeText(MainActivity.this, "賬號、密碼都不能為空!", Toast.LENGTH_SHORT).show();
}
}
});
}
private void register(String account, String password) {
String registerUrlStr = Constant.URL_Register + "?account=" + account + "&password=" + password;
new MyAsyncTask(tvResult).execute(registerUrlStr);
}
private void login(String account, String password) {
String registerUrlStr = Constant.URL_Login + "?account=" + account + "&password=" + password;
new MyAsyncTask(tvResult).execute(registerUrlStr);
}
/**
* AsyncTask類的三個泛型引數:
* (1)Param 在執行AsyncTask是需要傳入的引數,可用於後臺任務中使用
* (2)後臺任務執行過程中,如果需要在UI上先是當前任務進度,則使用這裡指定的泛型作為進度單位
* (3)任務執行完畢後,如果需要對結果進行返回,則這裡指定返回的資料型別
*/
public static class MyAsyncTask extends AsyncTask<String, Integer, String> {
private TextView tv; // 舉例一個UI元素,後邊會用到
public MyAsyncTask(TextView v) {
tv = v;
}
@Override
protected void onPreExecute() {
Log.w("WangJ", "task onPreExecute()");
}
/**
* @param params 這裡的params是一個數組,即AsyncTask在啟用執行是呼叫execute()方法傳入的引數
*/
@Override
protected String doInBackground(String... params) {
Log.w("WangJ", "task doInBackground()");
HttpURLConnection connection = null;
StringBuilder response = new StringBuilder();
try {
URL url = new URL(params[0]); // 宣告一個URL,注意如果用百度首頁實驗,請使用https開頭,否則獲取不到返回報文
connection = (HttpURLConnection) url.openConnection(); // 開啟該URL連線
connection.setRequestMethod("GET"); // 設定請求方法,“POST或GET”,我們這裡用GET,在說到POST的時候再用POST
connection.setConnectTimeout(80000); // 設定連線建立的超時時間
connection.setReadTimeout(80000); // 設定網路報文收發超時時間
InputStream in = connection.getInputStream(); // 通過連線的輸入流獲取下發報文,然後就是Java的流處理
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return response.toString(); // 這裡返回的結果就作為onPostExecute方法的入參
}
@Override
protected void onProgressUpdate(Integer... values) {
// 如果在doInBackground方法,那麼就會立刻執行本方法
// 本方法在UI執行緒中執行,可以更新UI元素,典型的就是更新進度條進度,一般是在下載時候使用
}
/**
* 執行在UI執行緒中,所以可以直接操作UI元素
* @param s
*/
@Override
protected void onPostExecute(String s) {
Log.w("WangJ", "task onPostExecute()");
tv.setText(s);
}
}
}
程式碼很簡單,沒什麼解釋的。本文的主要內容是將資料庫、伺服器、Android串聯起來這一過程。
*注意* 這裡我們不可能通過行動網路來連線我們的伺服器,因為我們的伺服器部署在本機本地,沒有公網IP,所以這裡我們用自己的電腦開啟一個共享熱點,然後用手機連上這個熱點來進行訪問本機伺服器,IP地址通過ipconfig命令檢視。開啟網路熱點自己百度吧,命令列不行直接找360免費WIFI等等,這篇已經夠囉嗦了,就不再加入其他干擾內容了,請關注本文的重點。
執行前再檢查一遍:(1)資料庫連線正常;(2)伺服器執行正常;(3)Android端訪問的本機伺服器地址可連通。然後Run,看結果
就這樣就完事了,其實篇幅不短,內容卻不多。到此GET方式的互動就完成了,同理,POST互動也是依葫蘆畫瓢,下一篇我們就來說說POST方式的互動。
佶屈聱牙,作拋磚引玉之用,水平有限,如有不足歡迎留言指正!
相關推薦
【一步一個腳印】Tomcat+MySQL為自己的APP打造伺服器(1)伺服器環境搭建
做 Android 開發一年多了,雖然不敢說有多精通,但也相對熟悉。做久了就會發現 Android 在行外人眼中是多麼高深(包括 IOS 也一樣),但是我們自己知道其實 Android 和 Web 前
【一步一個腳印】Tomcat+MySQL為自己的APP打造伺服器(3-1)Android 和 Service 的互動之GET方式
好久沒更新了,罪過罪過。最對不起的人莫過於那些支援和等待在下拙文的諸位,在此道一聲抱歉。管窺之見,僥倖博得各位認同,給了我莫大的鼓勵。 話休絮煩,文接前章。 到【一步一個腳印】Tomcat+MySQL為自己的APP打造伺服器(2-3)Se
【一步一個腳印】Tomcat+MySQL為自己的APP打造伺服器(3-2)Android 和 Service 的互動之POST方式
今天是聖誕節,雖說我本人對這個西方節日沒什麼感覺,但畢竟還是有很多小年輕人(自認為已然脫離年輕人的航道)挺在意這個節日的,在這裡祝大家聖誕快樂吧(要是湊巧你也沒什麼感覺,那就預祝元旦快
【一步一個腳印】Tomcat+MySQL為自己的APP打造伺服器(4)完結篇
在這個系列的前幾篇文章中,從最初簡單的伺服器環境搭建、MySQL資料庫的安裝、Servlet 的原理及使用、資料庫的連線及CURD操作、Android和伺服器GET/POST資料互動,到最後JSon格式報文的使用,我們已經將這個過程完整的走完一遍,但是其中
【安卓-自定義佈局】安卓App開發思路 一步一個腳印(十)實現內嵌在app中的webview 騰訊開源X5 高效安全
實現內嵌在app中的webview 採用騰訊開源X5 高效安全 webview在app的使用中,十分頻繁,原生的webview載入速度相對來說很慢,而且很費流量。騰訊開源了x5的webview
【安卓-自定義佈局】安卓App開發思路 一步一個腳印(九)實現自定義滾動的新聞條目上下滾動-仿蘑菇街
實現自定義滾動的新聞條目上下滾動-仿蘑菇街 這種上下滾動的自定義佈局,就像是公告那種上下的翻滾,一般為文字的滾動,很明顯,就是自定義佈局,一般是線性佈局。這裡提到的安卓原生的控制元件自然是
紮紮實實把基礎打牢,一步一個腳印
敢於把自己當做一個較笨的人,不強求自己做到完美,不要認為自己很牛逼。 紮紮實實把基礎打牢,一步一個腳印,不要急躁,比什麼都靠譜。 Stay hungry. Stay foolish. 敢於把自己當做一個較笨的人,不強求自己做到完美,不要
哪有什麼天生王者,不過是一步一個腳印
轉載自公眾號java團長 前言 Tomcat,這隻3腳貓,大學的時候就認識了,直到現在工作中,也常會和它打交道。這是一隻神奇的貓,今天讓我來抽象你,實現你! Tomcat Write MyTomcat Tomcat是非常流行的Web Server,它還是一個
一步一個腳印,QAD助力CAPP走出資訊化進階之路
【本文轉載於e-works數字化企業網】 客戶引言: “隨著汽車市場進入拐點,整個行業尤其是零部件廠商無可避免的將從管理入手進行轉型升級。QAD ERP解決方案在汽車行業擁有“專業+領先”的能力與實踐積累,“快速+便捷”的實施模式,非常適合基礎薄弱並且快速發展的中國民企。” ---長春市
一步一個腳印筆試面試(二)—google2013年校園招聘筆試題答案
(注:答案全部自己個人,希望指正討論) 1.單項選擇題 1.1 使用C語言將一個1G位元組的字元陣列從頭到尾全部設定為字元'A',在一臺典型的當代PC上,需要花費的CPU時間的數量級最接近: &
一步一個腳印的往前走!
想想自己曾經學習C語言時,初次見到指標、地址,只能死記硬背,經過一段時間的軟磨硬泡,終於有所收穫,在此記錄自己對C語言中地址、指標的理解。 推薦對C語言有一定理解的程式設計師閱讀。 學習過C語言的程式設計師們,一定都知道“指標就是地址”的經典描述
【Leetcode】【DP-二維陣列】 63. Unique Paths II / 不同路徑2(帶障礙)
給定一個二維陣列,每格為0/1值,1代表無法通過。求從左上到右下的不同路徑數。只能往右/下走。 Input: [ [0,0,0], [0,1,0], [0,0,0] ] Output: 2 Explanation: There is one obstacl
【杭電100題】RPG專場練習賽 2063 過山車(匈牙利演算法)
Problem Description 過山車的每一排只有兩個座位,每個女生必須找個男生做partner和她同坐,每個女生都願意跟若干男生做partner。只讓找到partner的人去坐過山車,最多有多少對組合可以坐上過山車? Input 輸入資料的第一行是三個整數K
一步一步教你寫股票走勢圖——K線圖二(圖表聯動)
K線圖越做發現坑越多,跟之前做的分時圖完全不是一個重量級的啊,分時圖不需要滾動,少走了很多彎路,K線圖因為滾動的問題,會導致很多其他問題,比如: 多個圖表之間滾動時怎麼聯動 高亮滾動衝突 放縮聯動 … 表對齊 下面我們就開始介紹本
【Linux Device Driver】—(3.1)—ioctl——程式碼
《Linux Device Driver》這本書的卻做的很好,對於一個初學者來說雖然有點難度,但是隻要認真看,絕對是大有裨益的! 好了,昨天把ioctl的原理以及涉及到的程式碼貼了一下,今天就做了做實驗,感覺還湊合,所以就貼出來!對自己也算是做個筆記吧! 今天這個實驗主
【Linux Device Driver】—(3.1)—ioctl——原理
大部分驅動除了需要具備讀寫裝置能力外,還需要具備對硬體控制的能力,例如:要求裝置報告錯誤資訊改變模特率,這些操作常常通過ioctl方法來實現! 這裡的東西看上去的卻挺多,但是還是依照此次的原則,只是對自己知識的一個複習,所以也就懶得貼那麼多的文字。 1、ioctl方法
【編程訓練-PAT】A1135 Is It A Red-Black Tree (30 分)
watermark per inpu sea rate ifdef mark pre avl 1135 Is It A Red-Black Tree (30 分) There is a kind of balanced binary search tree named re
(3.1)一個按鍵所能涉及的:按鍵中斷
/* AUTHOR: Pinus * Creat on : 2018-10-11 * KERNEL : linux-4.4.145 * BOARD : JZ2440(arm9 s3c2440) * REFS : 韋東山視訊教程第二期 */ 概述
linux下 mysql 5.7 配置 my.cnf(mysqld.cnf)檔案位置 以及具體的配置方式
一、問題 mysql 5.7 版本,/etc/my.cnf 和 /etc/mysql/my.cnf 空空如也,需要自己新增需要的配置,而不能像之前一樣 只要去掉 #號註釋即可。 可以參考djCode的blogMySQL的my.cnf檔案(解決5.7.18下沒有my-d
【一步一步學習VBA】Excel VBA 在當前目錄建立一個excel表
首先我們新建一個EXcel表,然後調用出Visual Basic,同時寫入程式碼: Sub AddNew() Dim path As String, filepath As String filepa