[TOC]
# 危險權限

# 存儲
來源:[Android 存儲使用參考](https://www.liaohuqiu.net/cn/posts/storage-in-android/)
```plain
($rootDir)
+- /data -> Environment.getDataDirectory()
| |
| | ($appDataDir)
| +- data/com.srain.cube.sample
| |
| | ($filesDir)
| +- files -> Context.getFilesDir() / Context.getFileStreamPath("")
| | |
| | +- file1 -> Context.getFileStreamPath("file1")
| | ($cacheDir)
| +- cache -> Context.getCacheDir()
| |
| +- app_$name ->(Context.getDir(String name, int mode)
|
| ($rootDir)
+- /storage/sdcard0 -> Environment.getExternalStorageDirectory()
| / Environment.getExternalStoragePublicDirectory("")
|
+- dir1 -> Environment.getExternalStoragePublicDirectory("dir1")
|
| ($appDataDir)
+- Andorid/data/com.srain.cube.sample
|
| ($filesDir)
+- files -> Context.getExternalFilesDir("")
| |
| +- file1 -> Context.getExternalFilesDir("file1")
| +- Music -> Context.getExternalFilesDir(Environment.Music);
| +- Picture -> ... Environment.Picture
| +- ...
|
| ($cacheDir)
+- cache -> Context.getExternalCacheDir()
|
+- ???
```
* 應用數據目錄($appDataDir)包含:內部存儲路徑:`/data/data/$packageName` 和外部存儲路徑`/sdcard/Android/data/$packageName`,在 App被卸載后,會被系統刪除,我們應該講應用的數據存放于這兩個目錄中,需要用戶額外保存的存放到外部存儲其他目錄中。
* 在$appDataDir下,一般包含數據緩存($cacheDir)和文件目錄($filesDir)兩個目錄。
* 機身存儲不足時,內部存儲的$cacheDir目錄下的文件會被刪除,外部存儲的$cacheDir目錄不會
* 內部存儲的$cacheDir和$filesDir文件是App安全的,其他應用無法讀取;外部存儲的這兩個目錄則不是,其他應用也可訪問
* 外部存儲的$filesDir中的媒體文件不會被當做媒體掃描出來加到媒體庫中
# Proguard
## 關鍵字
關鍵字|描述
---|---
keep|保留類名和類中的成員,防止它們被混淆或移除
keepnames|保留類名和類中的成員,只能防止它們被混淆(即不被改名),不能防止被移除。也就是假如成員沒被引用就會被移除
keepclassmembers|只保留類中的成員,防止被混淆或移除
keepclassmembernames|只保留類中的成員,只能防止被混淆,不能防止被移除
keepclasseswithmembers|如果擁有某成員,才會保留類和類成員。并能防止它們被混淆和移除
keepclasseswithmembernames|如果擁有某成員,會保留類和類成員,只能防止被混淆,不能防止無引用的成員被移除
總結:
* 帶 names 的關鍵字,都只能防止被混淆,不能防止被移除。也就是假如成員沒被引用,就會被移除
## 通配符
通配符|描述
---|---
<field>|匹配類中所有字段
<method>|匹配類中所有方法
<init>|匹配類中所有構造函數
\*|匹配任意長度字符,但不包括包名分隔符。不寫任何內容,只寫一個 \* 時,匹配所有東西
\**|匹配任意長度字符,包括包名分隔符
***|匹配任意參數類型
...|匹配任意長度的任意參數類型
參考文檔:
[Android安全攻防戰,反編譯與混淆技術完全解析(下)](http://blog.csdn.net/guolin_blog/article/details/50451259)
[Android混淆從入門到精通](http://www.jianshu.com/p/7436a1a32891)
# MIME 類型
## 定義
MIME 類型即互聯網媒體類型,是互聯網上傳輸的內容的分類類型。
一份內容的互聯網媒體類型由其文件格式和內容決定。互聯網媒體類型與文件擴展名相對應,所以計算機通常根據文件擴展名來確定其媒體類型并確定關聯軟件。
MIME 類型至少包括兩部分:類型(type)和子類型(subtype),還可包括一個或多個可選參數。格式如下:
```plain
類型名/子類型名 [; 可選參數]
```
示例:
```plain
text/html; charset = UTF-8
```
## 常用媒體類型列表
### Type Application
媒體類型|說明
---|---
application/ecmascript|ECMAScript/JavaScript(嚴格)
application/javascript|ECMAScript/JavaScript(寬松)
apllication/json|JSON
application/octet-stream|任意二進制文件
apllication/pdf|PDF
application/xml|XML 文件
application/zip|ZIP
application/gzip|GZIP
...|...
### Type text
媒體類型|說明
---|---
text/plain|純文本文件
text/xml|XML 文件
text/css|CSS 文件
text/html|HTML 文件
...|...
### Type image
媒體類型|說明
---|---
image/gif|GIF 圖像文件
image/jpeg|JPEG 圖像文件
image/png|PNG 圖像文件
image/webp|WebP 圖像文件
...|...
### Type audio
媒體類型|說明
---|---
audio/mp4|MP4 音頻文件
audio/mpeg|MP3 或其他 MPEG 音頻文件
audio/vnd.wave|wave 音頻文件
...|...
### Type video
媒體類型|說明
---|---
video/mp4|MP4 視頻文件
video/ogg|Ogg 視頻文件
video/quicktime|QuickTime 視頻文件
...|...
參考
[Wikipedia:互聯網媒體類型](https://zh.wikipedia.org/wiki/%E4%BA%92%E8%81%94%E7%BD%91%E5%AA%92%E4%BD%93%E7%B1%BB%E5%9E%8B)
# Parcelable 使用
## 標準使用
```java
public class Student implements Parcelable {
// 成員變量
private String mName;
private int mAge;
// Parcelable 接口方法
public int describeContents() {
return 0;
}
// Parcelable 接口方法,用于向 Parcel 寫入數據
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mName);
dest.writeInt(mAge);
}
// 用于從 Parcel 中讀取數據,并創建對象
public static final Parcelable.Creator<Student> CREATOR
= new Parcelable.Creator<Student>() {
public Student createFromParcel(Parcel in) {
return new Student(in);
}
public Student[] newArray(int size) {
return new Student[size];
}
};
private Student(Parcel in) {
mName = in.readString();
mAge = in.readInt();
}
}
```
注意:writeToParcel 方法和 createFromParcel 方法中的順序必須一致。
## 注意事項
### boolean 類型的序列化和反序列化
```java
// 序列化
dest.writeByte((byte) (isChecked ? 1 : 0));
// 反序列化
isChecked = in.readByte() != 0;
```
### 類成員變量中存在非基本類型時
將該成員變量也實現 Parcelable 接口:
```java
// 序列化
dest.writeParcelable(mBook, 0);
// 反序列化,反序列化過程需要傳遞當前線程的上下文類加載器,否則會報無法找到該類的錯誤
mBook = in.readParcelable(Thread.currentThread().getContextClassLoader());
```
### 類的某個成員變量不可能實現 Parcelable 接口時
將該成員變量拆分為若干個必要的屬性進行傳遞,反序列化后再進行組裝:
```java
// 序列化
dest.writeLong(calendar.getTimeInMillis());
dest.writeInt(calendar.getTimeZone().getID());
// 反序列化
mCalendar.setTimeInMillis(in.readLong());
mCalendar.getTimeZone().setID(in.readString());
```
參考
[Parcelable接口的使用](http://www.jianshu.com/p/97503d7faaf3)
# Android 庫模塊注意事項
* **資源合并沖突**
構建工具會將庫模塊中的資源與相關應用模塊的資源合并。如果在兩個模塊中均定義了給定資源 ID,將使用應用中的資源。
如果多個 AAR 庫之間發生沖突,將使用依賴項列表首先列出(位于 dependencies 塊頂部)的庫中的資源。
為了避免常用資源 ID 的資源沖突,請使用在模塊(或在所有項目模塊)中具有唯一性的前綴或其他一致的命名方案。
* **庫模塊不得包含原始資源**
不支持在庫模塊中使用原始資源文件(保存在 assets/ 目錄中)。應用使用的任何原始資源都必須存儲在應用模塊自身的 assets/ 目錄中。
* **應用模塊的 minSdkVersion 必須大于或等于庫定義的版本**
庫作為相關應用模塊的一部分編譯,因此,庫模塊中使用的 API 必須與應用模塊支持的平臺版本兼容。
參考
[創建 Android 庫](https://developer.android.com/studio/projects/android-library.html?hl=zh-cn#CreateLibrary)
# Android 手機方向問題
## 方向坐標系定義
* X 軸的方向是沿著屏幕的水平方向從左向右,如果手機不是正方形的話,較短的邊需要水平放置,較長的邊需要垂直放置。
* Y 軸的方向是從屏幕的左下角開始沿著屏幕的的垂直方向指向屏幕的頂端。
* 手機放在桌子上,Z 軸的方向是從手機指向天空。
* 手機屏幕較長的一邊定義為縱向,較短的一邊定義為橫向
* 手機圍繞 X 軸旋轉稱為仰俯,圍繞 Y 軸旋轉稱為反轉,圍繞 Z 軸旋轉稱為方向變化

## screenOrientation
清單文件中為 Activity 配置 screenOrientation 的常用值:
屬性|含義
---|---
unspecified|默認值,由系統選擇方向,不同設備、系統可能會有差異
landscape|橫向方向(顯示界面的寬度大于高度)
portrait|縱向方向(顯示界面的高度大于寬度)
reverseLandscape|與正常橫向方向相反的橫向方向
reversePortrait|與正常縱向方向相反的縱向方向
sensor|方向由設備方向傳感器決定,取決于用戶如何手持設備(一些設備默認不會旋轉到上面所有 4 種可能的方向)
sensorLandscape|橫向方向,但根據設備傳感器,可以是正常或反向的橫向方向
sensorPortrait|縱向方向,但根據設備傳感器,可以是正常或反向的縱向方向
fullSensor|方向由 4 種方向中任一方向的設備方向傳感器決定
全部取值請見官方文檔:[<activity>](https://developer.android.com/guide/topics/manifest/activity-element.html?hl=zh-cn)
## 相機方向問題
以下摘自 [Android 相機預覽方向及其適配探索](https://dev.qq.com/topic/583ba1df25d735cd2797004d) 這篇文章,非常不錯,值得一讀。
相機圖像數據來自于相機硬件的圖像傳感器(Image Sensor),這個 Sensor 被固定到手機之后是有一個默認的取景方向,且不會改變。

對于一個橫屏應用來說,屏幕“自然”方向和后置相機的圖像傳感器方向一致,因此看到的圖像是正的,這個很好理解。

而對于一個豎屏應用來說,屏幕“自然”方向和后置相機的圖像傳感器方向是不一致,從圖像傳感器的角度看,它看到的圖像是側過來的。
可以這樣理解,將我們自己的眼睛比做相機取景器,手機在由水平方向旋轉到豎直方向時,進行了順時針旋轉 90 度,而我們眼睛看圖片的方向是沒變的,所以頭要跟著轉 90 度。將此時看到的圖像展示到手機上,就如下圖所示了。

需要將相機預覽圖像順時針旋轉 90 度,才和屏幕“自然”方向一致。在 Android 系統中,提供 camera.setDisplayOrientation(angle)方法,用來設置相機預覽圖像順時針旋轉的角度。
參考
[Android 利用方向傳感器獲得手機的相對角度](http://blog.csdn.net/dlutbrucezhang/article/details/9005281)
[獲取Android設備的方向](http://www.cnblogs.com/bpasser/archive/2011/10/17/2214517.html)
[Android Orientation Sensor(方向傳感器)詳解與應用](http://blog.csdn.net/wlwlwlwl015/article/details/41759553)
[Android 相機預覽方向及其適配探索](https://dev.qq.com/topic/583ba1df25d735cd2797004d)
# 系統簽名
```plain
signapk.jar platform.x509.pem platform.pk8 app-release.apk new.apk
```
參考
[Android 將自己的應用改為系統應用](http://blog.csdn.net/xx326664162/article/details/53406933)
# Android虛擬機演進史

* Android1.0 Dalvik(DVM)+解釋器
DVM是Google開發的Android平臺虛擬機,可讀取.dex的字節碼。Android1.0時期,程序一邊運行,DVM中的解釋器一邊解釋字節碼為機器碼,效率較低
* Android2.0 DVM+JIT
Android2.0引入JIT(Just In Time)機制,在App打開的時候就開始編譯,等到使用該功能時已經準備好了。
* Android4.4 Dalvik和ART共存
* Android5.0 ART+AOT
Android5.0更改虛擬機為ART(Android Runtime),在應用安裝時就把字節碼編譯為機器碼,這個過程稱為AOT(Ahead Of Time)。App安裝時間會變長,并且占用空間也會變大,但用戶使用時不再需要解釋,使用時感覺速度變快。(但每次OTA啟動,比如系統更新或刷機,都需要重新安裝所有App)
* Android7.0 混合編譯
安裝時并不預先編譯,而是等手機空閑時使用AOT模式進行編譯,使用時如果發現還未編譯的代碼就采用JIT和解釋器來進行編譯
* Android8.0 改進解釋器
* Android9.0 改進編譯模板
預先放置熱點代碼,應用在安裝時就知道這是常用代碼會提前編譯
# NDK
原生開發套件(NDK,Native Development Kit),使用NDK我們可以在Android應用中使用C和C++代碼,原生代碼可以進一步提升設備性能,降低延遲,高效率運行計算密集型應用,如游戲或物理模擬等。
我們可以使用NDK將C和C++代碼編譯到so庫中,再使用Gradle將so庫打包到APK中,Java代碼通過Java原生接口(JNI,Java Native Interface)來調用原生庫中的代碼。
Android Studio默認使用CMake編譯原生代碼,但也支持ndk-build工具,CMake支持跨平臺,ndk-build僅支持
Android但速度更快。
LLDB是Android Studio中用于調試原生代碼的工具。
## 創建支持C/C++的新項目
在向導的Choose your project部分中,選擇Native C++項目類型,即可創建一個支持原生代碼的項目。創建后的項目會自動生成cpp組,其中包含原生源代碼文件、頭文件、CMake編譯腳本或ndk-build編譯腳本。

我們運行項目時原生代碼的編譯及調用流程如下:
1、Gradle調用編譯腳本CMakeLists.txt給CMake使用
2、CMake按照CMakeLists中的命令編譯原生代碼到對象庫中,并命名為***.so文件
3、Gradle再將so庫打包到APK中
4、應用運行時,先調用`System.loadLibrary()`方法加載原生庫,然后就可以通過JNI調用原生庫中的方法了
## 為舊項目引入C/C++代碼
1、在項目`src/main`目錄下創建`cpp`目錄
2、在cpp目錄下創建編寫C/C++源文件和頭文件
3、配置CMake編譯腳本
4、配置Gradle,讓CMake項目作為編譯依賴項,這樣Gradle編譯時才會把調用CMake編譯原生代碼
其實在創建新的支持C/C++項目時,Android Studio已經自動幫我們創建了上面的這些文件了。
## CMake編譯腳本配置
CMake編譯腳本是一個純文本文件,且必須命名為`CMakeLists.txt`。其中幾個重要的命令如下:
1、cmake_minimium_required()命令用來設置CMake的最低版本需求
```txt
cmake_minimum_required(VERSION 3.4.1)
```
2、add_library()命令用來設置源碼文件路徑和生成原生庫的信息
```txt
add_library(
# 設置原生庫的名字
native-lib
# 設置原生庫是STATIC類型還是SHARED類型
SHARED
# 設置原生源代碼相對路徑
native-lib.cpp)
```
3、include_directories()命令用來設置原生代碼頭文件的路徑
```txt
include_directories(src/main/cpp/include/)
```
4、find_library()命令可以用來找到NDK庫并將其路徑存儲為一個變量。Android手機平臺已經內置了預編譯的NDK庫,我們只需將自己的原生庫與其關聯,就可以使用內置的NDK庫了。
```txt
find_library(
# 設置路徑變量
log-lib
# 指定需要CMake幫我們尋找的內置NDK庫
log)
```
5、target_link_libraries()命令用來關聯我們自己的原生庫和內置的預編譯庫。
```txt
target_link_libraries(
# 指定我們自己的原生庫
native-lib
# 指定內置的預編譯庫
${log-lib})
```
關聯后,我們的原生庫就可以調用內置的預編譯庫中的函數了。
6、除了Android內置的預編譯過的NDK庫,NDK還提供了一些源代碼形式的庫,我們自己的原生庫需要使用時,也可以進行關聯。
```txt
add_library(
app-glue
STATIC
# 這里指定源代碼的路徑
${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c )
# 進行關聯
target_link_libraries( native-lib app-glue ${log-lib} )
```
7、添加其他預編譯庫
8、包含其他CMake項目
## 配置Gradle
配置好CMake編譯腳本后,我們還需要向Gradle提供CMake腳本文件的路徑,這樣Gradle編譯時才能找到。
1、配置模塊的build.gradle文件
```plain
android {
...
defaultConfig {...}
buildTypes {...}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
```
2、指定可選配置
可以在模塊的build.gradle文件的defaultConfig塊中配置另一個externalNativeBuild塊,為CMake或ndk-build指定可選參數和標記。
# EditText自動彈出軟鍵盤
## 方式一
1、在 EditText 的父布局中添加:
```xml
android:focusable="true"
android:focusableInTouchMode="true"
```
2、在 java 代碼中添加
```java
et_input.setFocusable(true);
et_input.setFocusableInTouchMode(true);
et_input.requestFocus();
activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
```
## 方式二

1、View參數最好是 EditText 或者它的子類。
考慮到軟鍵盤就是為了輸入,EditText 就是一個接收輸入的控件。而這不是絕對的,如果不是一個 EditText ,就必須要求這個 View 有兩個屬性,分別是:android:focusable="true" 和android:focusableInTouchMode="true"。
2、View參數必須是可獲取焦點的,并且當前已經獲取到焦點。
EditText 默認是允許獲取焦點的,但是假如布局中,存在多個可獲取焦點的控件,就需要提前讓我們傳遞進去的 View 獲取到焦點。獲取焦點可以使用 requestFocus() 方法。
3、布局必須加載完成。
在 onCreate() 中,如果立即調用 showSoftInput() 是不會生效的。想要在頁面一啟動的時候就彈出鍵盤,可以在 Activity 上,設置 android:windowSoftInputMode 屬性來完成,或者做一個延遲加載,View.postDelayed() 也是一個解決方案。
## 收起軟鍵盤
```java
InputMethodManager manager = (InputMethodManager) mView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
if (manager != null) {
manager.hideSoftInputFromWindow(mView.getWindowToken(), 0);
}
```
# 禁止軟鍵盤自動彈出
xml文件中,為EditText及其父布局添加如下屬性即可:
```xml
android:focusable="true"
android:focusableInTouchMode="true"
```
# 復制文本
```java
String needCopyString = "need";
ClipboardManager manager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
if (manager != null !TextUtils.isEmpty(needCopyString)) {
manager.setPrimaryClip(ClipData.newPlainText("copy_string", needCopyString));
Toast.makeText(mContext, "復制成功", Toast.LENGTH_SHORT).show();
}
```
# ScrollView使用注意事項
1、`ScrollView`的直接子View只能有一個,當內部有復雜布局時可以使用`LinearLayout`、`RelativeLayout`等進行包裹。
2、可以使用`layout_width`和`layout_height`給`ScrollView`指定大小。
3、`ScrollView`只用來處理需要滾動的不規則視圖的組合。大批量的列表數據展示可以使用`ListView`、`GridView`或者`RecyclerView`。
4、`ScrollView`只支持豎直滑動,水平滑動使用`HorizontalScrollView`。
5、當`ScrollView`內部的視圖不足以撐滿屏幕時,使用`ScrollView`的`android:fillViewport`屬性可以實現
# 軟鍵盤的回車按鈕變成搜索按鈕
布局文件修改如下:
```xml
<EditText
//...
android:imeOptions="actionSearch"
android:singleLine="true" />
```
事件監聽:
```java
mEt.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
// 判斷是否為搜索鍵
if(actionId == EditorInfo.IME_ACTION_SEARCH){
String key = mEt.getText().toString().trim();
if(TextUtils.isEmpty(key)){
ToastManager.showToastShort("請輸入您想要搜索的地址");
return true;
}
// 業務邏輯
doSomething(key);
// 隱藏鍵盤
hideKeyBoard();
return true;
}
return false;
}
});
```
# SpannableString 使用技巧
```java
String s = "請您在使用鹿用招聘前仔細閱讀并理解《用戶協議》和《隱私政策》。";
int userProtocolStart = s.indexOf("《用戶協議》");
int userProtocolEnd = userProtocolStart + "《用戶協議》".length();
int secretStart = s.indexOf("《隱私政策》");
int secretEnd = secretStart + "《隱私政策》".length();
SpannableString spannableString = new SpannableString(s);
spannableString.setSpan(new ForegroundColorSpan(0xff1890ff), userProtocolStart, userProtocolEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spannableString.setSpan(new ForegroundColorSpan(0xff1890ff), secretStart, secretEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spannableString.setSpan(new ClickableSpan() {
@Override
public void onClick(@NonNull View widget) {
HyBirdWebViewActivity.startActivity(SplashActivity.this, NetUrl.URL_STRING_AGREEMENT_URL);
}
@Override
public void updateDrawState(@NonNull TextPaint ds) {
super.updateDrawState(ds);
//設置文本的顏色
ds.setColor(ContextCompat.getColor(context, R.color.final_blue_main_35ABDC));
//超鏈接形式的下劃線,false 表示不顯示下劃線,true表示顯示下劃線
ds.setUnderlineText(false);
}
}, userProtocolStart, userProtocolEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spannableString.setSpan(new ClickableSpan() {
@Override
public void onClick(@NonNull View widget) {
HyBirdWebViewActivity.startActivity(SplashActivity.this, NetUrl.URL_STRING_PRIACY_URL);
}
@Override
public void updateDrawState(@NonNull TextPaint ds) {
super.updateDrawState(ds);
//設置文本的顏色
ds.setColor(ContextCompat.getColor(context, R.color.final_blue_main_35ABDC));
//超鏈接形式的下劃線,false 表示不顯示下劃線,true表示顯示下劃線
ds.setUnderlineText(false);
}
}, secretStart, secretEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mTvProtocol.setMovementMethod(LinkMovementMethod.getInstance());
mTvProtocol.setText(spannableString);
```
# 顏色透明度轉換
100% — FF
95% — F2
90% — E6
85% — D9
80% — CC
75% — BF
70% — B3
65% — A6
60% — 99
55% — 8C
50% — 80
45% — 73
40% — 66
35% — 59
30% — 4D
25% — 40
20% — 33
15% — 26
10% — 1A
5% — 0D
0% — 00
- 導讀
- Java知識
- Java基本程序設計結構
- 【基礎知識】Java基礎
- 【源碼分析】Okio
- 【源碼分析】深入理解i++和++i
- 【專題分析】JVM與GC
- 【面試清單】Java基本程序設計結構
- 對象與類
- 【基礎知識】對象與類
- 【專題分析】Java類加載過程
- 【面試清單】對象與類
- 泛型
- 【基礎知識】泛型
- 【面試清單】泛型
- 集合
- 【基礎知識】集合
- 【源碼分析】SparseArray
- 【面試清單】集合
- 多線程
- 【基礎知識】多線程
- 【源碼分析】ThreadPoolExecutor源碼分析
- 【專題分析】volatile關鍵字
- 【面試清單】多線程
- Java新特性
- 【專題分析】Lambda表達式
- 【專題分析】注解
- 【面試清單】Java新特性
- Effective Java筆記
- Android知識
- Activity
- 【基礎知識】Activity
- 【專題分析】運行時權限
- 【專題分析】使用Intent打開三方應用
- 【源碼分析】Activity的工作過程
- 【面試清單】Activity
- 架構組件
- 【專題分析】MVC、MVP與MVVM
- 【專題分析】數據綁定
- 【面試清單】架構組件
- 界面
- 【專題分析】自定義View
- 【專題分析】ImageView的ScaleType屬性
- 【專題分析】ConstraintLayout 使用
- 【專題分析】搞懂點九圖
- 【專題分析】Adapter
- 【源碼分析】LayoutInflater
- 【源碼分析】ViewStub
- 【源碼分析】View三大流程
- 【源碼分析】觸摸事件分發機制
- 【源碼分析】按鍵事件分發機制
- 【源碼分析】Android窗口機制
- 【面試清單】界面
- 動畫和過渡
- 【基礎知識】動畫和過渡
- 【面試清單】動畫和過渡
- 圖片和圖形
- 【專題分析】圖片加載
- 【面試清單】圖片和圖形
- 后臺任務
- 應用數據和文件
- 基于網絡的內容
- 多線程與多進程
- 【基礎知識】多線程與多進程
- 【源碼分析】Handler
- 【源碼分析】AsyncTask
- 【專題分析】Service
- 【源碼分析】Parcelable
- 【專題分析】Binder
- 【源碼分析】Messenger
- 【面試清單】多線程與多進程
- 應用優化
- 【專題分析】布局優化
- 【專題分析】繪制優化
- 【專題分析】內存優化
- 【專題分析】啟動優化
- 【專題分析】電池優化
- 【專題分析】包大小優化
- 【面試清單】應用優化
- Android新特性
- 【專題分析】狀態欄、ActionBar和導航欄
- 【專題分析】應用圖標、通知欄適配
- 【專題分析】Android新版本重要變更
- 【專題分析】唯一標識符的最佳做法
- 開源庫源碼分析
- 【源碼分析】BaseRecyclerViewAdapterHelper
- 【源碼分析】ButterKnife
- 【源碼分析】Dagger2
- 【源碼分析】EventBus3(一)
- 【源碼分析】EventBus3(二)
- 【源碼分析】Glide
- 【源碼分析】OkHttp
- 【源碼分析】Retrofit
- 其他知識
- Flutter
- 原生開發與跨平臺開發
- 整體歸納
- 狀態及狀態管理
- 零碎知識點
- 添加Flutter到現有應用
- Git知識
- Git命令
- .gitignore文件
- 設計模式
- 創建型模式
- 結構型模式
- 行為型模式
- RxJava
- 基礎
- Linux知識
- 環境變量
- Linux命令
- ADB命令
- 算法
- 常見數據結構及實現
- 數組
- 排序算法
- 鏈表
- 二叉樹
- 棧和隊列
- 算法時間復雜度
- 常見算法思想
- 其他技術
- 正則表達式
- 編碼格式
- HTTP與HTTPS
- 【面試清單】其他知識
- 開發歸納
- Android零碎問題
- 其他零碎問題
- 開發思路