摘要:面向字節(jié)流流,指的是流入到進(jìn)程或從進(jìn)程流出的字符序列。即收到連接信息后向返回確認(rèn)信息第三次握手客戶端收到服務(wù)器的報文段,并向服務(wù)器發(fā)送報文段。
前言
Socket的使用在 Android網(wǎng)絡(luò)編程中非常重要
今天我將帶大家全面了解 Socket 及 其使用方法
目錄 1.網(wǎng)絡(luò)基礎(chǔ) 1.1 計算機網(wǎng)絡(luò)分層計算機網(wǎng)絡(luò)分為五層:物理層、數(shù)據(jù)鏈路層、網(wǎng)絡(luò)層、運輸層、應(yīng)用層
其中:
網(wǎng)絡(luò)層:負(fù)責(zé)根據(jù)IP找到目的地址的主機
運輸層:通過端口把數(shù)據(jù)傳到目的主機的目的進(jìn)程,來實現(xiàn)進(jìn)程與進(jìn)程之間的通信
1.2 端口號(PORT)端口號規(guī)定為16位,即允許一個IP主機有2的16次方65535個不同的端口。其中:
0~1023:分配給系統(tǒng)的端口號
> 我們不可以亂用
1024~49151:登記端口號,主要是讓第三方應(yīng)用使用
> 但是必須在IANA(互聯(lián)網(wǎng)數(shù)字分配機構(gòu))按照規(guī)定手續(xù)登記,
49152~65535:短暫端口號,是留給客戶進(jìn)程選擇暫時使用,一個進(jìn)程使用完就可以供其他進(jìn)程使用。
1.3 C/S結(jié)構(gòu)在Socket使用時,可以用1024~65535的端口號
定義:即客戶端/服務(wù)器結(jié)構(gòu),是軟件系統(tǒng)體系結(jié)構(gòu)
作用:充分利用兩端硬件環(huán)境的優(yōu)勢,將任務(wù)合理分配到Client端和Server端來實現(xiàn),降低了系統(tǒng)的通訊開銷。
> Socket正是使用這種結(jié)構(gòu)建立連接的,一個套接字接客戶端,一個套接字接服務(wù)器。
如圖:
1.4 TCP協(xié)議可以看出,Socket的使用可以基于TCP或者UDP協(xié)議。
定義:Transmission Control Protocol,即傳輸控制協(xié)議,是一種傳輸層通信協(xié)議
基于TCP的應(yīng)用層協(xié)議有FTP、Telnet、SMTP、HTTP、POP3與DNS。
特點:面向連接、面向字節(jié)流、全雙工通信、可靠
面向連接:指的是要使用TCP傳輸數(shù)據(jù),必須先建立TCP連接,傳輸完成后釋放連接,就像打電話一樣必須先撥號建立一條連接,打完后掛機釋放連接。
全雙工通信:即一旦建立了TCP連接,通信雙方可以在任何時候都能發(fā)送數(shù)據(jù)。
可靠的:指的是通過TCP連接傳送的數(shù)據(jù),無差錯,不丟失,不重復(fù),并且按序到達(dá)。
面向字節(jié)流:流,指的是流入到進(jìn)程或從進(jìn)程流出的字符序列。簡單來說,雖然有時候要傳輸?shù)臄?shù)據(jù)流太大,TCP報文長度有限制,不能一次傳輸完,要把它分為好幾個數(shù)據(jù)塊,但是由于可靠性保證,接收方可以按順序接收數(shù)據(jù)塊然后重新組成分塊之前的數(shù)據(jù)流,所以TCP看起來就像直接互相傳輸字節(jié)流一樣,面向字節(jié)流。
TCP建立連接
必須進(jìn)行三次握手:若A要與B進(jìn)行連接,則必須
第一次握手:建立連接。客戶端發(fā)送連接請求報文段,將SYN位置為1,Sequence Number為x;然后,客戶端進(jìn)入SYN_SEND狀態(tài),等待服務(wù)器的確認(rèn)。即A發(fā)送信息給B
第二次握手:服務(wù)器收到客戶端的SYN報文段,需要對這個SYN報文段進(jìn)行確認(rèn)。即B收到連接信息后向A返回確認(rèn)信息
第三次握手:客戶端收到服務(wù)器的(SYN+ACK)報文段,并向服務(wù)器發(fā)送ACK報文段。即A收到確認(rèn)信息后再次向B返回確認(rèn)連接信息
> 此時,A告訴自己上層連接建立;B收到連接信息后告訴上層連接建立。
這樣就完成TCP三次握手 = 一條TCP連接建立完成 = 可以開始發(fā)送數(shù)據(jù)
為什么TCP建立連接需要三次握手?三次握手期間任何一次未收到對面回復(fù)都要重發(fā)。
最后一個確認(rèn)報文段發(fā)送完畢以后,客戶端和服務(wù)器端都進(jìn)入ESTABLISHED狀態(tài)。
答:防止服務(wù)器端因為接收了早已失效的連接請求報文從而一直等待客戶端請求,從而浪費資源
“已失效的連接請求報文段”的產(chǎn)生在這樣一種情況下:Client發(fā)出的第一個連接請求報文段并沒有丟失,而是在某個網(wǎng)絡(luò)結(jié)點長時間的滯留了,以致延誤到連接釋放以后的某個時間才到達(dá)server。
這是一個早已失效的報文段。但Server收到此失效的連接請求報文段后,就誤認(rèn)為是Client再次發(fā)出的一個新的連接請求。
于是就向Client發(fā)出確認(rèn)報文段,同意建立連接。
假設(shè)不采用“三次握手”:只要Server發(fā)出確認(rèn),新的連接就建立了。
由于現(xiàn)在Client并沒有發(fā)出建立連接的請求,因此不會向Server發(fā)送數(shù)據(jù)。
但Server卻以為新的運輸連接已經(jīng)建立,并一直等待Client發(fā)來數(shù)據(jù)。>- 這樣,Server的資源就白白浪費掉了。
采用“三次握手”的辦法可以防止上述現(xiàn)象發(fā)生:
Client不會向Server的確認(rèn)發(fā)出確認(rèn)
Server由于收不到確認(rèn),就知道Client并沒有要求建立連接
所以Server不會等待Client發(fā)送數(shù)據(jù),資源就沒有被浪費
TCP釋放連接
TCP釋放連接需要四次揮手過程,現(xiàn)在假設(shè)A主動釋放連接:(數(shù)據(jù)傳輸結(jié)束后,通信的雙方都可釋放連接)
第一次揮手:A發(fā)送釋放信息到B;(發(fā)出去之后,A->B發(fā)送數(shù)據(jù)這條路徑就斷了)
第二次揮手:B收到A的釋放信息之后,回復(fù)確認(rèn)釋放的信息:我同意你的釋放連接請求
第三次揮手:B發(fā)送“請求釋放連接“信息給A
第四次揮手:A收到B發(fā)送的信息后向B發(fā)送確認(rèn)釋放信息:我同意你的釋放連接請求
> B收到確認(rèn)信息后就會正式關(guān)閉連接; > A等待2MSL后依然沒有收到回復(fù),則證明B端已正常關(guān)閉,于是A關(guān)閉連接為什么TCP釋放連接需要四次揮手?
為了保證雙方都能通知對方“需要釋放連接”,即在釋放連接后都無法接收或發(fā)送消息給對方
需要明確的是:TCP是全雙工模式,這意味著是雙向都可以發(fā)送、接收的
釋放連接的定義是:雙方都無法接收或發(fā)送消息給對方,是雙向的
當(dāng)主機1發(fā)出“釋放連接請求”(FIN報文段)時,只是表示主機1已經(jīng)沒有數(shù)據(jù)要發(fā)送 / 數(shù)據(jù)已經(jīng)全部發(fā)送完畢;
> 但是,這個時候主機1還是可以接受來自主機2的數(shù)據(jù)。
當(dāng)主機2返回“確認(rèn)釋放連接”信息(ACK報文段)時,表示它已經(jīng)知道主機1沒有數(shù)據(jù)發(fā)送了
> 但此時主機2還是可以發(fā)送數(shù)據(jù)給主機1
當(dāng)主機2也發(fā)送了FIN報文段時,即告訴主機1我也沒有數(shù)據(jù)要發(fā)送了
> 此時,主機1和2已經(jīng)無法進(jìn)行通信:主機1無法發(fā)送數(shù)據(jù)給主機2,主機2也無法發(fā)送數(shù)據(jù)給主機1,此時,TCP的連接才算釋放
定義:User Datagram Protocol,即用戶數(shù)據(jù)報協(xié)議,是一種傳輸層通信協(xié)議。
基于UDP的應(yīng)用層協(xié)議有TFTP、SNMP與DNS。
特點:無連接的、不可靠的、面向報文、沒有擁塞控制
無連接的:和TCP要建立連接不同,UDP傳輸數(shù)據(jù)不需要建立連接,就像寫信,在信封寫上收信人名稱、地址就可以交給郵局發(fā)送了,至于能不能送到,就要看郵局的送信能力和送信過程的困難程度了。
不可靠的:因為UDP發(fā)出去的數(shù)據(jù)包發(fā)出去就不管了,不管它會不會到達(dá),所以很可能會出現(xiàn)丟包現(xiàn)象,使傳輸?shù)臄?shù)據(jù)出錯。
面向報文:數(shù)據(jù)報文,就相當(dāng)于一個數(shù)據(jù)包,應(yīng)用層交給UDP多大的數(shù)據(jù)包,UDP就照樣發(fā)送,不會像TCP那樣拆分。
沒有擁塞控制:擁塞,是指到達(dá)通信子網(wǎng)中某一部分的分組數(shù)量過多,使得該部分網(wǎng)絡(luò)來不及處理,以致引起這部分乃至整個網(wǎng)絡(luò)性能下降的現(xiàn)象,嚴(yán)重時甚至?xí)?dǎo)致網(wǎng)絡(luò)通信業(yè)務(wù)陷入停頓,即出現(xiàn)死鎖現(xiàn)象,就像交通堵塞一樣。TCP建立連接后如果發(fā)送的數(shù)據(jù)因為信道質(zhì)量的原因不能到達(dá)目的地,它會不斷重發(fā),有可能導(dǎo)致越來越塞,所以需要一個復(fù)雜的原理來控制擁塞。而UDP就沒有這個煩惱,發(fā)出去就不管了。
應(yīng)用場景
很多的實時應(yīng)用(如IP電話、實時視頻會議、某些多人同時在線游戲等)要求源主機以很定的速率發(fā)送數(shù)據(jù),并且允許在網(wǎng)絡(luò)發(fā)生擁塞時候丟失一些數(shù)據(jù),但是要求不能有太大的延時,UDP就剛好適合這種要求。所以說,只有不適合的技術(shù),沒有真正沒用的技術(shù)。
詳情請看我寫的另外一篇文章你需要了解的HTTP知識都在這里了!
*
2. Socket定義
即套接字,是一個對 TCP / IP協(xié)議進(jìn)行封裝 的編程調(diào)用接口(API)
> 1. 即通過`Socket`,我們才能在Andorid平臺上通過 `TCP/IP`協(xié)議進(jìn)行開發(fā) > 2. `Socket`不是一種協(xié)議,而是一個編程調(diào)用接口(`API`),屬于傳輸層(主要解決數(shù)據(jù)如何在網(wǎng)絡(luò)中傳輸)
成對出現(xiàn),一對套接字:
Socket ={(IP地址1:PORT端口號),(IP地址2:PORT端口號)}3. 原理
Socket的使用類型主要有兩種:
流套接字(streamsocket) :基于 TCP協(xié)議,采用 流的方式 提供可靠的字節(jié)流服務(wù)
數(shù)據(jù)報套接字(datagramsocket):基于 UDP協(xié)議,采用 數(shù)據(jù)報文 提供數(shù)據(jù)打包發(fā)送的服務(wù)
具體原理圖如下:
4. Socket 與 Http 對比Socket屬于傳輸層,因為 TCP / IP協(xié)議屬于傳輸層,解決的是數(shù)據(jù)如何在網(wǎng)絡(luò)中傳輸?shù)膯栴}
HTTP協(xié)議 屬于 應(yīng)用層,解決的是如何包裝數(shù)據(jù)
由于二者不屬于同一層面,所以本來是沒有可比性的。但隨著發(fā)展,默認(rèn)的Http里封裝了下面幾層的使用,所以才會出現(xiàn)Socket & HTTP協(xié)議的對比:(主要是工作方式的不同):
Http:采用 請求—響應(yīng) 方式。
> 1. 即建立網(wǎng)絡(luò)連接后,當(dāng) 客戶端 向 服務(wù)器 發(fā)送請求后,服務(wù)器端才能向客戶端返回數(shù)據(jù)。 > 2. 可理解為:**是客戶端有需要才進(jìn)行通信**
Socket:采用 服務(wù)器主動發(fā)送數(shù)據(jù) 的方式
> 1. 即建立網(wǎng)絡(luò)連接后,服務(wù)器可主動發(fā)送消息給客戶端,而不需要由客戶端向服務(wù)器發(fā)送請求 > 2. 可理解為:**是服務(wù)器端有需要才進(jìn)行通信**5. 使用步驟
Socket可基于TCP或者UDP協(xié)議,但TCP更加常用
所以下面的使用步驟 & 實例的Socket將基于TCP協(xié)議
// 步驟1:創(chuàng)建客戶端 & 服務(wù)器的連接 // 創(chuàng)建Socket對象 & 指定服務(wù)端的IP及端口號 Socket socket = new Socket("192.168.1.32", 1989); // 判斷客戶端和服務(wù)器是否連接成功 socket.isConnected()); // 步驟2:客戶端 & 服務(wù)器 通信 // 通信包括:客戶端 接收服務(wù)器的數(shù)據(jù) & 發(fā)送數(shù)據(jù) 到 服務(wù)器 <-- 操作1:接收服務(wù)器的數(shù)據(jù) --> // 步驟1:創(chuàng)建輸入流對象InputStream InputStream is = socket.getInputStream() // 步驟2:創(chuàng)建輸入流讀取器對象 并傳入輸入流對象 // 該對象作用:獲取服務(wù)器返回的數(shù)據(jù) InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); // 步驟3:通過輸入流讀取器對象 接收服務(wù)器發(fā)送過來的數(shù)據(jù) br.readLine(); <-- 操作2:發(fā)送數(shù)據(jù) 到 服務(wù)器 --> // 步驟1:從Socket 獲得輸出流對象OutputStream // 該對象作用:發(fā)送數(shù)據(jù) OutputStream outputStream = socket.getOutputStream(); // 步驟2:寫入需要發(fā)送的數(shù)據(jù)到輸出流對象中 outputStream.write(("Carson_Ho"+" ").getBytes("utf-8")); // 特別注意:數(shù)據(jù)的結(jié)尾加上換行符才可讓服務(wù)器端的readline()停止阻塞 // 步驟3:發(fā)送數(shù)據(jù)到服務(wù)端 outputStream.flush(); // 步驟3:斷開客戶端 & 服務(wù)器 連接 os.close(); // 斷開 客戶端發(fā)送到服務(wù)器 的連接,即關(guān)閉輸出流對象OutputStream br.close(); // 斷開 服務(wù)器發(fā)送到客戶端 的連接,即關(guān)閉輸入流讀取器對象BufferedReader socket.close(); // 最終關(guān)閉整個Socket連接6. 具體實例
實例 Demo 代碼包括:客戶端 & 服務(wù)器
本文著重講解客戶端,服務(wù)器僅采用最簡單的寫法進(jìn)行展示
6.1 客戶端 實現(xiàn)步驟1:加入網(wǎng)絡(luò)權(quán)限
步驟2:主布局界面設(shè)置
包括創(chuàng)建Socket連接、客戶端 & 服務(wù)器通信的按鈕
步驟3:創(chuàng)建Socket連接、客戶端 & 服務(wù)器通信
具體請看注釋
MainActivity.java
package scut.carson_ho.socket_carson; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class MainActivity extends AppCompatActivity { /** * 主 變量 */ // 主線程Handler // 用于將從服務(wù)器獲取的消息顯示出來 private Handler mMainHandler; // Socket變量 private Socket socket; // 線程池 // 為了方便展示,此處直接采用線程池進(jìn)行線程管理,而沒有一個個開線程 private ExecutorService mThreadPool; /** * 接收服務(wù)器消息 變量 */ // 輸入流對象 InputStream is; // 輸入流讀取器對象 InputStreamReader isr ; BufferedReader br ; // 接收服務(wù)器發(fā)送過來的消息 String response; /** * 發(fā)送消息到服務(wù)器 變量 */ // 輸出流對象 OutputStream outputStream; /** * 按鈕 變量 */ // 連接 斷開連接 發(fā)送數(shù)據(jù)到服務(wù)器 的按鈕變量 private Button btnConnect, btnDisconnect, btnSend; // 顯示接收服務(wù)器消息 按鈕 private TextView Receive,receive_message; // 輸入需要發(fā)送的消息 輸入框 private EditText mEdit; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); /** * 初始化操作 */ // 初始化所有按鈕 btnConnect = (Button) findViewById(R.id.connect); btnDisconnect = (Button) findViewById(R.id.disconnect); btnSend = (Button) findViewById(R.id.send); mEdit = (EditText) findViewById(R.id.edit); receive_message = (TextView) findViewById(R.id.receive_message); Receive = (Button) findViewById(R.id.Receive); // 初始化線程池 mThreadPool = Executors.newCachedThreadPool(); // 實例化主線程,用于更新接收過來的消息 mMainHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case 0: receive_message.setText(response); break; } } }; /** * 創(chuàng)建客戶端 & 服務(wù)器的連接 */ btnConnect.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 利用線程池直接開啟一個線程 & 執(zhí)行該線程 mThreadPool.execute(new Runnable() { @Override public void run() { try { // 創(chuàng)建Socket對象 & 指定服務(wù)端的IP 及 端口號 socket = new Socket("192.168.1.172", 8989); // 判斷客戶端和服務(wù)器是否連接成功 System.out.println(socket.isConnected()); } catch (IOException e) { e.printStackTrace(); } } }); } }); /** * 接收 服務(wù)器消息 */ Receive.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 利用線程池直接開啟一個線程 & 執(zhí)行該線程 mThreadPool.execute(new Runnable() { @Override public void run() { try { // 步驟1:創(chuàng)建輸入流對象InputStream is = socket.getInputStream(); // 步驟2:創(chuàng)建輸入流讀取器對象 并傳入輸入流對象 // 該對象作用:獲取服務(wù)器返回的數(shù)據(jù) isr = new InputStreamReader(is); br = new BufferedReader(isr); // 步驟3:通過輸入流讀取器對象 接收服務(wù)器發(fā)送過來的數(shù)據(jù) response = br.readLine(); // 步驟4:通知主線程,將接收的消息顯示到界面 Message msg = Message.obtain(); msg.what = 0; mMainHandler.sendMessage(msg); } catch (IOException e) { e.printStackTrace(); } } }); } }); /** * 發(fā)送消息 給 服務(wù)器 */ btnSend.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 利用線程池直接開啟一個線程 & 執(zhí)行該線程 mThreadPool.execute(new Runnable() { @Override public void run() { try { // 步驟1:從Socket 獲得輸出流對象OutputStream // 該對象作用:發(fā)送數(shù)據(jù) outputStream = socket.getOutputStream(); // 步驟2:寫入需要發(fā)送的數(shù)據(jù)到輸出流對象中 outputStream.write((mEdit.getText().toString()+" ").getBytes("utf-8")); // 特別注意:數(shù)據(jù)的結(jié)尾加上換行符才可讓服務(wù)器端的readline()停止阻塞 // 步驟3:發(fā)送數(shù)據(jù)到服務(wù)端 outputStream.flush(); } catch (IOException e) { e.printStackTrace(); } } }); } }); /** * 斷開客戶端 & 服務(wù)器的連接 */ btnDisconnect.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { // 斷開 客戶端發(fā)送到服務(wù)器 的連接,即關(guān)閉輸出流對象OutputStream outputStream.close(); // 斷開 服務(wù)器發(fā)送到客戶端 的連接,即關(guān)閉輸入流讀取器對象BufferedReader br.close(); // 最終關(guān)閉整個Socket連接 socket.close(); // 判斷客戶端和服務(wù)器是否已經(jīng)斷開連接 System.out.println(socket.isConnected()); } catch (IOException e) { e.printStackTrace(); } } }); } }6.2 服務(wù)器 實現(xiàn)
因本文主要講解客戶端,所以服務(wù)器僅僅是為了配合客戶端展示;
為了簡化服務(wù)器使用,此處采用Mina框架
> 1. 服務(wù)器代碼請在`eclipse`平臺運行 > 2. 按照我的步驟一步步實現(xiàn)就可以無腦運行了
步驟1:導(dǎo)入Mina包
請直接移步到百度網(wǎng)盤:下載鏈接(密碼: q73e)
步驟2:創(chuàng)建服務(wù)器線程
TestHandler.java
package mina; // 導(dǎo)入包 public class TestHandler extends IoHandlerAdapter { @Override public void exceptionCaught(IoSession session, Throwable cause) throws Exception { System.out.println("exceptionCaught: " + cause); } @Override public void messageReceived(IoSession session, Object message) throws Exception { System.out.println("recieve : " + (String) message); session.write("hello I am server"); } @Override public void messageSent(IoSession session, Object message) throws Exception { } @Override public void sessionClosed(IoSession session) throws Exception { System.out.println("sessionClosed"); } @Override public void sessionOpened(IoSession session) throws Exception { System.out.println("sessionOpen"); } @Override public void sessionIdle(IoSession session, IdleStatus status) throws Exception { } }
步驟3:創(chuàng)建服務(wù)器主代碼
TestHandler.java
package mina; import java.io.IOException; import java.net.InetSocketAddress; import org.apache.mina.filter.codec.ProtocolCodecFilter; import org.apache.mina.filter.codec.textline.TextLineCodecFactory; import org.apache.mina.transport.socket.nio.NioSocketAcceptor; public class TestServer { public static void main(String[] args) { NioSocketAcceptor acceptor = null; try { acceptor = new NioSocketAcceptor(); acceptor.setHandler(new TestHandler()); acceptor.getFilterChain().addLast("mFilter", new ProtocolCodecFilter(new TextLineCodecFactory())); acceptor.setReuseAddress(true); acceptor.bind(new InetSocketAddress(8989)); } catch (Exception e) { e.printStackTrace(); } } }
至此,客戶端 & 服務(wù)器的代碼均實現(xiàn)完畢。
6.3 測試結(jié)果點擊 Connect按鈕: 連接成功
輸入發(fā)送的消息,點擊 Send 按鈕發(fā)送
服務(wù)器接收到客戶端發(fā)送的消息
點擊 Receive From Message按鈕,客戶端 讀取 服務(wù)器返回的消息
點擊 DisConnect按鈕,斷開 客戶端 & 服務(wù)器的連接
6.4 源碼地址Socket具體實例
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/70564.html
摘要:方法即為收集器,它接收高階函數(shù)和的后端掘金年的第一天,我坐在獨墅湖邊,寫下這篇文章。正因如此,所以最全系列教程后端掘金是從版本開始引入的一個新的,可以替代標(biāo)準(zhǔn)的。 設(shè)計模式之單例模式 - 掘金前言 作為一個好學(xué)習(xí)的程序開發(fā)者,應(yīng)該會去學(xué)習(xí)優(yōu)秀的開源框架,當(dāng)然學(xué)習(xí)的過程中不免會去閱讀源碼,這也是一個優(yōu)秀程序員的必備素養(yǎng),在學(xué)習(xí)的過程中很多人會遇到的障礙,那就是設(shè)計模式。很多優(yōu)秀的框架會運...
摘要:如何自學(xué)知識儲備本知識點不做重點講解對于有基礎(chǔ)的同學(xué)推薦看編程思想,鞏固基礎(chǔ),查漏補全,了解并熟悉更多細(xì)節(jié)知識點。基礎(chǔ)學(xué)習(xí)基礎(chǔ)學(xué)習(xí)對于這些基礎(chǔ)的使用谷歌官網(wǎng)給出了很好的實例。是谷歌根據(jù)自帶的改進(jìn)的。是基于谷歌內(nèi)核的一個可以作為瀏覽器的視圖。 如何自學(xué)Android 1. Java知識儲備 本知識點不做重點講解: 對于有基礎(chǔ)的同學(xué)推薦看《Java編程思想》,鞏固基礎(chǔ),查漏補全,了解...
閱讀 1772·2021-10-11 10:57
閱讀 2356·2021-10-08 10:14
閱讀 3399·2019-08-29 17:26
閱讀 3356·2019-08-28 17:54
閱讀 3029·2019-08-26 13:38
閱讀 2903·2019-08-26 12:19
閱讀 3613·2019-08-23 18:05
閱讀 1282·2019-08-23 17:04