1、網路
網路協議模型
應用層:負責處理特定的應用程式細節
HTTP、FTP、DNS
傳輸層:為兩臺主機提供端到端的基礎通信
TCP、UDP
網路層:控制分組傳輸、路由選擇等
IP
鏈路層:作業系統設備驅動程式、網卡相關介面
TCP 和 UDP 區別
TCP 連接;可靠;有序;面向位元組流;速度慢;較重量;全雙工;適用于檔案傳輸、瀏覽器等
- 全雙工:A 給 B 發訊息的同時,B 也能給 A 發
- 半雙工:A 給 B 發訊息的同時,B 不能給 A 發
UDP 無連接;不可靠;無序;面向報文;速度快;輕量;適用于即時通訊、視頻通話等
TCP 三次握手
A:你能聽到嗎?
B:我能聽到,你能聽到嗎?
A:我能聽到,開始吧
A 和 B 兩方都要能確保:我說的話,你能聽到;你說的話,我能聽到,所以需要三次握手
TCP 四次揮手
A:我說完了
B:我知道了,等一下,我可能還沒說完
B:我也說完了
A:我知道了,結束吧
B 收到 A 結束的訊息后 B 可能還沒說完,沒法立即回復結束標示,只能等說完后再告訴 A :我說完了,
POST 和 GET 區別
Get 引數放在 url 中;Post 引數放在 request Body 中
Get 可能不安全,因為引數放在 url 中
HTTPS
HTTP 是超文本傳輸協議,明文傳輸;HTTPS 使用 SSL 協議對 HTTP 傳輸資料進行了加密
HTTP 默認 80 埠;HTTPS 默認 443 埠
優點:安全
缺點:費時、SSL 證書收費,加密能力還是有限的,但是比 HTTP 強多了
2、Java 基礎&容器&同步&設計模式
StringBuilder、StringBuffer、+、String.concat 鏈接字串:
- StringBuffer 執行緒安全,StringBuilder 執行緒不安全
- +實際上是用 StringBuilder 來實作的,所以非回圈體可以直接用 +,回圈體不行,因為會頻繁創建 StringBuilder
- String.concat 實質是 new String ,效率也低,耗時排序:StringBuilder < StringBuffer < concat < +
Java 泛型擦除
- 修飾成員變數等類結構相關的泛型不會被擦除
- 容器類泛型會被擦除
ArrayList、LinkedList
ArrayList
基于陣列實作,查找快:o(1),增刪慢:o(n)
初始容量為10,擴容通過 System.arrayCopy 方法
LinkedList
基于雙向鏈表實作,查找慢:o(n),增刪快:o(1)
封裝了佇列和堆疊的呼叫
HashMap 、HashTable
HashMap
- 基于陣列和鏈表實作,陣列是 HashMap 的主體;鏈表是為解決哈希沖突而存在的
- 當發生哈希沖突且鏈表 size 大于閾值時會擴容,JAVA 8 會將鏈表轉為紅黑樹提高性能
允許 key/value 為 null
HashTable
- 資料結構和 HashMap 一樣
- 不允許 value 為 null
- 執行緒安全
ArrayMap、SparseArray
ArrayMap
1.基于兩個陣列實作,一個存放 hash;一個存放鍵值對,擴容的時候只需要陣列拷貝,不需要重建哈希表
2.記憶體利用率高
3.不適合存大量資料,因為會對 key 進行二分法查找(1000以下)
SparseArray
1.基于兩個陣列實作,int 做 key
2.記憶體利用率高
3.不適合存大量資料,因為會對 key 進行二分法查找(1000以下)
volatile 關鍵字
- 只能用來修飾變數,適用修飾可能被多執行緒同時訪問的變數
- 相當于輕量級的 synchronized,volatitle 能保證有序性(禁用指令重排序)、可見性;后者還能保證原子性
- 變數位于主記憶體中,每個執行緒還有自己的作業記憶體,變數在自己執行緒的作業記憶體中有份拷貝,執行緒直接操作的是這個拷貝
- 被 volatile 修飾的變數改變后會立即同步到主記憶體,保持變數的可見性,
雙重檢查單例,為什么要加 volatile?
1.volatile想要解決的問題是,在另一個執行緒中想要使用instance,發現instance!=null,但是實際上instance還未初始化完畢這個問題
2.將instance =newInstance();拆分為3句話是,1.分配記憶體2.初始化3.將instance指向分配的記憶體空
3.volatile可以禁止指令重排序,確保先執行2,后執行3
wait 和 sleep
- sleep 是 Thread 的靜態方法,可以在任何地方呼叫
- wait 是 Object 的成員方法,只能在 synchronized 代碼塊中呼叫,否則會報 IllegalMonitorStateException 非法監控狀態例外
- sleep 不會釋放共享資源鎖,wait 會釋放共享資源鎖
lock 和 synchronized
- synchronized 是 Java 關鍵字,內置特性;Lock 是一個介面
- synchronized 會自動釋放鎖;lock 需要手動釋放,所以需要寫到 try catch 塊中并在 finally 中釋放鎖
- synchronized 無法中斷等待鎖;lock 可以中斷
- Lock 可以提高多個執行緒進行讀/寫操作的效率
- 競爭資源激烈時,lock 的性能會明顯的優于 synchronized
可重入鎖
- 定義:已經獲取到鎖后,再次呼叫同步代碼塊/嘗試獲取鎖時不必重新去申請鎖,可以直接執行相關代碼
- ReentrantLock 和 synchronized 都是可重入鎖
公平鎖
- 定義:等待時間最久的執行緒會優先獲得鎖
- 非公平鎖無法保證哪個執行緒獲取到鎖,synchronized 就是非公平鎖
- ReentrantLock 默認時非公平鎖,可以設定為公平鎖
樂觀鎖和悲觀鎖
- 悲觀鎖:執行緒一旦得到鎖,其他執行緒就掛起等待,適用于寫入操作頻繁的場景;synchronized 就是悲觀鎖
- 樂觀鎖:假設沒有沖突,不加鎖,更新資料時判斷該資料是否過期,過期的話則不進行資料更新,適用于讀取操作頻繁的場景
- 樂觀鎖 CAS:Compare And Swap,更新資料時先比較原值是否相等,不相等則表示資料過去,不進行資料更新
- 樂觀鎖實作:AtomicInteger、AtomicLong、AtomicBoolean
死鎖 4 個必要條件
- 互斥
- 占有且等待
- 不可搶占
- 回圈等待
synchronized 原理
- 每個物件都有一個監視器鎖:monitor,同步代碼塊會執行 monitorenter 開始,motnitorexit 結束
- wait/notify 就依賴 monitor 監視器,所以在非同步代碼塊中執行會報 IllegalMonitorStateException 例外
3、Java 虛擬機&記憶體結構&GC&類加載&四種參考&動態代理
JVM
- 定義:可以理解成一個虛構的計算機,解釋自己的位元組碼指令集映射到本地 CPU 或 OS 的指令集,上層只需關注 Class 檔案,與作業系統無關,實作跨平臺
- Kotlin 就是能解釋成 Class 檔案,所以可以跑在 JVM 上
JVM 記憶體模型
- Java 多執行緒之間是通過共享記憶體來通信的,每個執行緒都有自己的本地記憶體
- 共享變數存放于主記憶體中,執行緒會拷貝一份共享變數到本地記憶體
- volatile 關鍵字就是給記憶體模型服務的,用來保證記憶體可見性和順序性
JVM 記憶體結構
執行緒私有:
1.程式計數器:記錄正在執行的位元組碼指令地址,若正在執行 Native 方法則為空
2.虛擬機堆疊:執行方法時把方法所需資料存為一個堆疊幀入堆疊,執行完后出堆疊
3.本地方法堆疊:同虛擬機堆疊,但是針對的是 Native 方法
執行緒共享:
1.堆:存盤 Java 實體,GC 主要區域,分代收集 GC 方法會吧堆劃分為新生代、老年代
2.方法區:存盤類資訊,常量池,靜態變數等資料
GC
回收區域:只針對堆、方法區;執行緒私有區域資料會隨執行緒結束銷毀,不用回收
回收型別:
1.堆中的物件
- 分代收集 GC 方法會吧堆劃分為新生代、老年代
- 新生代:新建小物件會進入新生代;通過復制演算法回收物件
- 老年代:新建大物件及老物件會進入老年代;通過標記-清除演算法回收物件
2.方法區中的類資訊、常量池
判斷一個物件是否可被回收:
1.參考計數法
缺點:回圈參考
2.可達性分析法
定義:從 GC ROOT 開始搜索,不可達的物件都是可以被回收的
GC ROOT
1.虛擬機堆疊/本地方法堆疊中參考的物件
2.方法區中常量/靜態變數參考的物件
四種參考
- 強參考:不會被回收
- 軟參考:記憶體不足時會被回收
- 弱參考:gc 時會被回收
- 虛參考:無法通過虛參考得到物件,可以監聽物件的回收
ClassLoader
類的生命周期:
1.加載;2.驗證;3.準備;4.決議;5.初始化;6.使用;7.卸載
類加載程序:
1.加載:獲取類的二進制位元組流;生成方法區的運行時存盤結構;在記憶體中生成 Class 物件
2.驗證:確保該 Class 位元組流符合虛擬機要求
3.準備:初始化靜態變數
4.決議:將常量池的符號參考替換為直接參考
5.初始化:執行靜態塊代碼、類變數賦值
類加載時機:
1.實體化物件
2.呼叫類的靜態方法
3.呼叫類的靜態變數(放入常量池的常量除外)
類加載器:負責加載 class 檔案
分類:
1.引導類加載器 - 沒有父類加載器
2.拓展類加載器 - 繼承自引導類加載器
3.系統類加載器 - 繼承自拓展類加載器
雙親委托模型:
當要加載一個 class 時,會先逐層向上讓父加載器先加載,加載失敗才會自己加載
為什么叫雙親?不考慮自定義加載器,系統類加載器需要網上詢問兩層,所以叫雙親
判斷是否是同一個類時,除了類資訊,還必須時同一個類加載器
優點:
- 防止重復加載,父加載器加載過了就沒必要加載了
- 安全,防止篡改核心庫類
動態代理原理及實作
- InvocationHandler 介面,動態代理類需要實作這個介面
- Proxy.newProxyInstance,用于動態創建代理物件
- Retrofit 應用: Retrofit 通過動態代理,為我們定義的請求介面都生成一個動態代理物件,實作請求
4、Android 基礎&性能優化&Framwork
Activity 啟動模式
-
standard 標準模式
-
singleTop 堆疊頂復用模式,
-
推送點擊訊息界面
-
singleTask 堆疊內復用模式,
-
首頁
-
singleInstance 單例模式,單獨位于一個任務堆疊中
-
撥打電話界面
細節: -
taskAffinity:任務相關性,用于指定任務堆疊名稱,默認為應用包名
-
allowTaskReparenting:允許轉移任務堆疊
View 作業原理
-
DecorView (FrameLayout)
-
LinearLayout
-
titlebar
-
Content
-
呼叫 setContentView 設定的 View
ViewRoot 的 performTraversals 方法呼叫觸發開始 View 的繪制,然后會依次呼叫:
- performMeasure:遍歷 View 的 measure 測量尺寸
- performLayout:遍歷 View 的 layout 確定位置
- performDraw:遍歷 View 的 draw 繪制
事件分發機制
-
一個 MotionEvent 產生后,按 Activity -> Window -> decorView -> View 順序傳遞,View 傳遞程序就是事件分發,主要依賴三個方法:
-
dispatchTouchEvent:用于分發事件,只要接受到點擊事件就會被呼叫,回傳結果表示是否消耗了當前事件
-
onInterceptTouchEvent:用于判斷是否攔截事件,當 ViewGroup 確定要攔截事件后,該事件序列都不會再觸發呼叫此 ViewGroup 的 onIntercept
-
onTouchEvent:用于處理事件,回傳結果表示是否處理了當前事件,未處理則傳遞給父容器處理
-
細節:
-
一個事件序列只能被一個 View 攔截且消耗
-
View 沒有 onIntercept 方法,直接呼叫 onTouchEvent 處理
-
OnTouchListener 優先級比 OnTouchEvent 高,onClickListener 優先級最低
-
requestDisallowInterceptTouchEvent 可以屏蔽父容器 onIntercet 方法的呼叫
Window 、 WindowManager、WMS、SurfaceFlinger
- Window:抽象概念不是實際存在的,而是以 View 的形式存在,通過 PhoneWindow 實作
- WindowManager:外界訪問 Window 的入口,內部與 WMS 互動是個 IPC 程序
- WMS:管理視窗 Surface 的布局和次序,作為系統級服務單獨運行在一個行程
- SurfaceFlinger:將 WMS 維護的視窗按一定次序混合后顯示到螢屏上
View 影片、幀影片及屬性影片
View 影片:
- 作用物件是 View,可用 xml 定義,建議 xml 實作比較易讀
- 支持四種效果:平移、縮放、旋轉、透明度
幀影片:
- 通過 AnimationDrawable 實作,容易 OOM
屬性影片:
- 可作用于任何物件,可用 xml 定義,Android 3 引入,建議代碼實作比較靈活
- 包括 ObjectAnimator、ValuetAnimator、AnimatorSet
- 時間插值器:根據時間流逝的百分比計算當前屬性改變的百分比
- 系統預置勻速、加速、減速等插值器
- 型別估值器:根據當前屬性改變的百分比計算改變后的屬性值
- 系統預置整型、浮點、色值等型別估值器
- 使用注意事項:
- 避免使用幀影片,容易OOM
- 界面銷毀時停止影片,避免記憶體泄漏
- 開啟硬體加速,提高影片流暢性 ,硬體加速:
- 將 cpu 一部分作業分擔給 gpu ,使用 gpu 完成繪制作業
- 從作業分攤和繪制機制兩個方面優化了繪制速度
Handler、MessageQueue、Looper
- Handler:開發直接接觸的類,內部持有 MessageQueue 和 Looper
- MessageQueue:訊息佇列,內部通過單鏈表存盤訊息
- Looper:內部持有 MessageQueue,回圈查看是否有新訊息,有就處理,沒就阻塞
- 如何實作阻塞:通過 nativePollOnce 方法,基于 Linux epoll 事件管理機制
- 為什么主執行緒不會因為 Looper 阻塞:系統每 16ms 會發送一個重繪 UI 訊息喚醒
MVC、MVP、MVVM
-
MVP:Model:處理資料;View:控制視圖;Presenter:分離 Activity 和 Model
-
MVVM:Model:處理獲取保存資料;View:控制視圖;ViewModel:資料容器
-
使用 Jetpack 組件架構的 LiveData、ViewModel 便捷實作 MVVM
Serializable、Parcelable
- Serializable :Java 序列化方式,適用于存盤和網路傳輸,serialVersionUID 用于確定反序列化和類版本是否一致,不一致時反序列化回失敗
- Parcelable :Android 序列化方式,適用于組件通信資料傳遞,性能高,因為不像 Serializable 一樣有大量反射操作,頻繁 GC
Binder
- Android 行程間通信的中流砥柱,基于客戶端-服務端通信方式
- 使用 mmap 一次資料拷貝實作 IPC,傳統 IPC:用戶A空間->內核->用戶B空間;mmap 將內核與用戶B空間映射,實作直接從用戶A空間->用戶B空間
- BinderPool 可避免創建多 Service
IPC 方式
-
Intent extras、Bundle:要求傳遞資料能被序列化,實作 Parcelable、Serializable ,適用于四大組件通信
-
檔案共享:適用于交換簡單的資料實時性不高的場景
-
AIDL:AIDL 介面實質上是系統提供給我們可以方便實作 BInder 的工具
-
Android Interface Definition Language,可實作跨行程呼叫方法
-
服務端:將暴漏給客戶端的介面宣告在 AIDL 檔案中,創建 Service 實作 AIDL 介面并監聽客戶端連接請求
-
客戶端:系結服務端 Service ,系結成功后拿到服務端 Binder 物件轉為 AIDL 介面呼叫
-
RemoteCallbackList 實作跨行程介面監聽,同個 Binder 物件做 key 存盤客戶端注冊的 listener
-
監聽 Binder 斷開:1.Binder.linkToDeath 設定死亡代理;2. onServiceDisconnected 回呼
-
Messenger:基于 AIDL 實作,服務端串行處理,主要用于傳遞訊息,適用于低并發一對多通信
-
ContentProvider:基于 Binder 實作,適用于一對多行程間資料共享
-
Socket:TCP、UDP,適用于網路資料交換
Android 系統啟動流程
- 按電源鍵 -> 加載引導程式 BootLoader 到 RAM -> 執行 BootLoader 程式啟動內核 -> 啟動 init 行程 -> 啟動 Zygote 和各種守護行程 ->
- 啟動 System Server 服務行程開啟 AMS、WMS 等 -> 啟動 Launcher 應用行程
App 啟動流程
Launcher 中點擊一個應用圖示 -> 通過 AMS 查找應用行程,若不存在就通過 Zygote 行程 fork
行程保活
-
行程優先級:1.前臺行程 ;2.可見行程;3.服務行程;4.后臺行程;5.空行程
-
行程被 kill 場景:1.切到后臺記憶體不足時被殺;2.切到后臺廠商省電機制殺死;3.用戶主動清理
-
保活方式:
-
1.Activity 提權:掛一個 1像素 Activity 將行程優先級提高到前臺行程
-
2.Service 提權:啟動一個前臺服務(API>18會有正在運行通知欄)
-
3.廣播拉活
-
4.Service 拉活
-
5.JobScheduler 定時任務拉活
-
6.雙行程拉活
網路優化及檢測
- 速度:1.GZIP 壓縮(okhttp 自動支持);2.Protocol Buffer 替代 json;3.優化圖片/檔案流量;4.IP 直連省去 DNS 決議時間
- 成功率:1.失敗重試策略;
- 流量:1.GZIP 壓縮(okhttp 自動支持);2.Protocol Buffer 替代 json;3.優化圖片/檔案流量;5.檔案下載斷點續傳 ;6.快取
- 協議層的優化,比如更優的 http 版本等
- 監控:Charles 抓包、Network Monitor 監控流量
UI卡頓優化
- 減少布局層級及控制元件復雜度,避免過度繪制
- 使用 include、merge、viewstub
- 優化繪制程序,避免在 Draw 中頻繁創建物件、做耗時操作
記憶體泄漏場景及規避
1.靜態變數、單例強引跟生命周期相關的資料或資源,包括 EventBus
2.游標、IO 流等資源忘記主動釋放
3.界面相關影片在界面銷毀時及時暫停
4.內部類持有外部類參考導致的記憶體泄漏
- handler 內部類記憶體泄漏規避:1.使用靜態內部類+弱參考 2.界面銷毀時清空訊息佇列
- 檢測:Android Studio Profiler
LeakCanary 原理
- 通過弱參考和參考佇列監控物件是否被回收
- 比如 Activity 銷毀時開始監控此物件,檢測到未被回收則主動 gc ,然后繼續監控
OOM 場景及規避
- 加載大圖:減小圖片
- 記憶體泄漏:規避記憶體泄漏
5、Android 模塊化&熱修復&熱更新&打包&混淆&壓縮
Dalvik 和 ART
-
Dalvik
-
谷歌設計專用于 Android 平臺的 Java 虛擬機,可直接運行 .dex 檔案,適合記憶體和處理速度有限的系統
-
JVM 指令集是基于堆疊的;Dalvik 指令集是基于暫存器的,代碼執行效率更優
-
ART
-
Dalvik 每次運行都要將位元組碼轉換成機器碼;ART 在應用安裝時就會轉換成機器碼,執行速度更快
-
ART 存盤機器碼占用空間更大,空間換時間
APK 打包流程
1.aapt 打包資源檔案生成 R.java 檔案;aidl 生成 java 檔案
2.將 java 檔案編譯為 class 檔案
3.將工程及第三方的 class 檔案轉換成 dex 檔案
4.將 dex 檔案、so、編譯過的資源、原始資源等打包成 apk 檔案
5.簽名
6.資源檔案對齊,減少運行時記憶體
App 安裝程序
- 首先要解壓 APK,資源、so等放到應用目錄
- Dalvik 會將 dex 處理成 ODEX ;ART 會將 dex 處理成 OAT;
- OAT 包含 dex 和安裝時編譯的機器碼
組件化路由實作
ARoute:通過 APT 決議 @Route 等注解,結合 JavaPoet 生成路由表,即路由與 Activity 的映射關系
6、音視頻&FFmpeg&播放器
FFmpeg
基于命令方式實作了一個音視頻編輯 App
集成編譯了 AAC、MP3、H264 編碼器
播放器原理
視頻播放原理:(mp4、flv)-> 解封裝 -> (mp3/aac、h264/h265)-> 解碼 -> (pcm、yuv)-> 音視頻同步 -> 渲染播放
音視頻同步:
- 選擇參考時鐘源:音頻時間戳、視頻時間戳和外部時間三者選擇一個作為參考時鐘源(一般選擇音頻,因為人對音頻更敏感,ijk 默認也是音頻)
- 通過等待或丟幀將視頻流與參考時鐘源對齊,實作同步
IjkPlayer 原理
集成了 MediaPlayer、ExoPlayer 和 IjkPlayer 三種實作,其中 IjkPlayer 基于 FFmpeg 的 ffplay
音頻輸出方式:AudioTrack、OpenSL ES;視頻輸出方式:NativeWindow、OpenGL ES
作者2013年從java開發,轉做Android開發,在小廠待過,也去過華為,OPPO等大廠待過,18年四月份進了阿里一直到現在,
參與過不少面試,也當面試官 面試過很多人,深知大多數初中級Android工程師,想要提升技能,往往是自己摸索成長,不成體系的學習效果低效漫長,而且極易碰到天花板技術停滯不前!
我整理了一份阿里P7級別的最系統的Android開發主流技術,特別適合有3-5年以上經驗的小伙伴深入學習提升,
主要包括阿里,以及位元組跳動,騰訊,華為,小米,等一線互聯網公司主流架構技術,如果你想深入系統學習Android開發,成為一名合格的高級工程師,可以收藏一下這些Android進階技術選型
我搜集整理過這幾年阿里,以及騰訊,位元組跳動,華為,小米等公司的面試題,把面試的要求和技術點梳理成一份大而全的“ Android架構師”面試 Xmind(實際上比預期多花了不少精力),包含知識脈絡 + 分支細節,
Java語言與原理;
大廠,小廠,Android面試先看你熟不熟悉Java語言
高級UI與自定義view;
自定義view,Android開發的基本功,
性能調優;
資料結構演算法,設計模式,都是這里面的關鍵基礎和重點需要熟練的,
NDK開發;
未來的方向,高薪必會,
前沿技術;
組件化,熱升級,熱修復,框架設計
網上學習 Android的資料一大堆,但如果學到的知識不成體系,遇到問題時只是淺嘗輒止,不再深入研究,那么很難做到真正的技術提升,希望這份系統化的技術體系對大家有一個方向參考,
我在搭建這些技術框架的時候,還整理了系統的高級進階教程,會比自己碎片化學習效果強太多,CodeChina上可見;
當然,想要深入學習并掌握這些能力,并不簡單,關于如何學習,做程式員這一行什么作業強度大家都懂,但是不管作業多忙,每周也要雷打不動的抽出 2 小時用來學習,
不出半年,你就能看出變化!
**本文已被[CODING開源專案:《Android學習筆記總結+移動架構視頻+大廠面試真題+專案實戰原始碼》](
)

收錄**
最后自我介紹一下,小編13年上海交大畢業,曾經在小公司待過,也去過華為、OPPO等大廠,18年進入阿里一直到現在,
深知大多數初中級Android工程師,想要提升技能,往往是自己摸索成長或者是報班學習,但對于培訓機構動則近萬的學費,著實壓力不小,自己不成體系的自學效果低效又漫長,而且極易碰到天花板技術停滯不前!
Hwkxc3dt-1634459383310)]
網上學習 Android的資料一大堆,但如果學到的知識不成體系,遇到問題時只是淺嘗輒止,不再深入研究,那么很難做到真正的技術提升,希望這份系統化的技術體系對大家有一個方向參考,
我在搭建這些技術框架的時候,還整理了系統的高級進階教程,會比自己碎片化學習效果強太多,CodeChina上可見;
當然,想要深入學習并掌握這些能力,并不簡單,關于如何學習,做程式員這一行什么作業強度大家都懂,但是不管作業多忙,每周也要雷打不動的抽出 2 小時用來學習,
不出半年,你就能看出變化!
**本文已被[CODING開源專案:《Android學習筆記總結+移動架構視頻+大廠面試真題+專案實戰原始碼》](
)
[外鏈圖片轉存中…(img-LPTXsoiE-1634459383311)]
收錄**
最后自我介紹一下,小編13年上海交大畢業,曾經在小公司待過,也去過華為、OPPO等大廠,18年進入阿里一直到現在,
深知大多數初中級Android工程師,想要提升技能,往往是自己摸索成長或者是報班學習,但對于培訓機構動則近萬的學費,著實壓力不小,自己不成體系的自學效果低效又漫長,而且極易碰到天花板技術停滯不前!
因此也是希望能夠幫助到想自學提升又不知道該從何學起的朋友,同時減輕大家的負擔,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/323478.html
標籤:其他
上一篇:第一個Android程式






