距離安迪·魯賓和他的團隊著手開發一個希望顛覆傳統手機操作模式的操作系統已經過去 12 年了,這套系統有可能讓手機或者智能機給消費者以及軟件開發人員帶來全新的體驗。之前的智能機僅限于收發短信和查看電子郵件(當然還可以打電話),給用戶和開發者帶來很大的限制。
安卓,作為打破這個枷鎖的系統,擁有非常優秀的框架設計,給大家提供的不僅僅是一組有限的功能,更多的是自由的探索。有人會說 iPhone 才是手機產業的顛覆產品,不過我們說的不是 iPhone 有多么酷(或者多么貴,是吧?),它還是有限制的,而這是我們從來都不希望有的。
不過,就像本大叔說的,能力越大責任越大,我們也需要更加認真對待安卓應用的設計方式。我看到很多教程都忽略了向初學者傳遞這個理念,在動手之前請先充分理解系統架構。他們只是把一堆的概念和代碼丟給讀者,卻沒有解釋清楚相關的優缺點,它們對系統的影響,以及該用什么不該用什么等等。
在這篇文章里,我們將介紹一些初學者以及中級開發人員都應該掌握的技巧,以幫助更好地理解安卓框架。后續我們還會在這個系列里寫更多這樣的關于實用技巧的文章。我們開始吧。
1、@+id 和 @id 的區別
要在 Java 代碼里訪問一個圖形控件(或組件),或者是要讓它成為其他控件的依賴,我們需要一個唯一的值來引用它。這個唯一值用 android:id 屬性來定義,本質上就是把用戶提供的 id 附加到 @+id/ 后面,寫入到 id 資源文件,供其他控件使用。一個 Toolbar 的 id 可以這樣定義,
android:id="@+id/toolbar"
然后這個 id 值就能被 findViewById(…) 識別,這個函數會在資源文件里查找 id,或者直接從 R.id 路徑引用,然后返回所查找的 View 的類型。
而另一種,@id,和 findViewById(…) 行為一樣 - 也會根據提供的 id 查找組件,不過僅限于布局時使用。一般用來布置相關控件。
android:layout_below="@id/toolbar"
2、使用 @string 資源為 XML 提供字符串
簡單來說,就是不要在 XML 里直接用字符串。原因很簡單。當我們在 XML 里直接使用了字符串,我們一般會在其它地方再次用到同樣的字符串。想像一下當我們需要在不同的地方調整同一個字符串的噩夢,而如果使用字符串資源就只改一個地方就夠了。另一個好處是,使用資源文件可以提供多國語言支持,因為可以為不同的語言創建相應的字符串資源文件。
android:text="My Awesome Application"
當你直接使用字符串時,你會在 Android Studio 里收到警告,提示說應該把寫死的字符串改成字符串資源。可以點擊這個提示,然后按下 ALT + ENTER 打開字符串編輯。你也可以直接打開 res 目錄下的 values 目錄里的 strings.xml 文件,然后像下面這樣聲明一個字符串資源。
My Awesome Application
然后用它來替換寫死的字符串,
android:text="@string/app_name"
3、使用 @android 和 ?attr 常量
盡量使用系統預先定義的常量而不是重新聲明。舉個例子,在布局中有幾個地方要用白色或者 #ffffff 顏色值。不要每次都直接用 #ffffff 數值,也不要自己為白色重新聲明資源,我們可以直接用這個:
@android:color/white
安卓預先定義了很多常用的顏色常量,比如白色,黑色或粉色。最經典的應用場景是透明色:
@android:color/transparent
另一個引用常量的方式是 ?attr,用來將預先定義的屬性值賦值給不同的屬性。舉個自定義 Toolbar 的例子。這個 Toolbar 需要定義寬度和高度。寬度通常可以設置為 MATCH_PARENT,但高度呢?我們大多數人都沒有注意設計指導,只是簡單地隨便設置一個看上去差不多的值。這樣做不對。不應該隨便自定義高度,而應該這樣做,
android:layout_height="?attr/actionBarSize"
?attr 的另一個應用是點擊視圖時畫水波紋效果。SelectableItemBackground 是一個預定義的 drawable,任何視圖需要增加波紋效果時可以將它設為背景:
android:background="?attr/selectableItemBackground"
也可以用這個:
android:background="?attr/selectableItemBackgroundBorderless"
來顯示無邊框波紋。
4、SP 和 DP 的區別
雖然這兩個沒有本質上的區別,但知道它們是什么以及在什么地方適合用哪個很重要。
SP 的意思是縮放無關像素,一般建議用于 TextView,首先文字不會因為顯示密度不同而顯示效果不一樣,另外 TextView 的內容還需要根據用戶設定做拉伸,或者只調整字體大小。
其他需要定義尺寸和位置的地方,可以使用 DP,也就是密度無關像素。之前說過,DP 和 SP 的性質是一樣的,只是 DP 會根據顯示密度自動拉伸,因為安卓系統會動態計算實際顯示的像素,這樣就可以讓使用 DP 的組件在不同顯示密度的設備上都可以擁有相同的顯示效果。
5、Drawable 和 Mipmap 的應用
這兩個最讓人困惑的是 - drawable 和 mipmap 有多少差異?
雖然這兩個好像有同樣的用途,但它們設計目的不一樣。mipmap 是用來儲存圖標的,而 drawable 用于任何其他格式。我們可以看一下系統內部是如何使用它們的,就知道為什么不能混用了。
你可以看到你的應用里有幾個 mipmap 和 drawable 目錄,每一個分別代表不同的顯示分辨率。當系統從 drawable 目錄讀取資源時,只會根據當前設備的顯示密度選擇確定的目錄。然而,在讀取 mipmap 時,系統會根據需要選擇合適的目錄,而不僅限于當前顯示密度,主要是因為有些啟動器會故意顯示較大的圖標,所以系統會使用較大分辨率的資源。
總之,用 mipmap 來存放圖標或標記圖片,可以在不同顯示密度的設備上看到分辨率變化,而其它根據需要顯示的圖片資源都用 drawable。
比如說,Nexus 5 的顯示分辨率是 xxhdpi。當我們把圖標放到 mipmap 目錄里時,所有 mipmap 目錄都將讀入內存。而如果放到 drawable 里,只有 drawable-xxhdpi 目錄會被讀取,其他目錄都會被忽略。
6、使用矢量圖形
為了支持不同顯示密度的屏幕,將同一個資源的多個版本(大小)添加到項目里是一個很常見的技巧。這種方式確實有用,不過它也會帶來一定的性能開支,比如更大的 apk 文件以及額外的開發工作。為了消除這種影響,谷歌的安卓團隊發布了新增的矢量圖形。
矢量圖形是用 XML 描述的 SVG(可拉伸矢量圖形),是用點、直線和曲線組合以及填充顏色繪制出的圖形。正因為矢量圖形是由點和線動態畫出來的,在不同顯示密度下拉伸也不會損失分辨率。而矢量圖形帶來的另一個好處是更容易做動畫。往一個 AnimatedVectorDrawable 文件里添加多個矢量圖形就可以做出動畫,而不用添加多張圖片然后再分別處理。
android:width="24dp"?
android:height="24dp"?
android:viewportWidth="24.0"?
android:viewportHeight="24.0">
上面的向量定義可以畫出下面的圖形:
要在你的安卓項目里添加矢量圖形,可以右鍵點擊你項目里的應用模塊,然后選擇 New >> Vector Assets。然后會打開 Assets Studio,你可以有兩種方式添加矢量圖形。第一種是從 Material 圖標里選擇,另一種是選擇本地的 SVG 或 PSD 文件。
谷歌建議與應用相關都使用 Material 圖標,來保持安卓的連貫性和統一體驗。這里(https://material.io/icons/)有全部圖標,記得看一下。
7、設定邊界的開始和結束
這是人們最容易忽略的地方之一。邊界!增加邊界當然很簡單,但是如果要考慮支持很舊的平臺呢?
邊界的“開始”和“結束”分別是“左”和“右”的超集,所以如果應用的 minSdkVersion 是 17 或更低,邊界和填充的“開始”和“結束”定義是舊的“左”/“右”所需要的。在那些沒有定義“開始”和“結束”的系統上,這兩個定義可以被安全地忽略。可以像下面這樣聲明:
android:layout_marginEnd="20dp"
android:paddingStart="20dp"
8、使用 Getter/Setter 生成工具
在創建一個容器類(只是用來簡單的存放一些變量數據)時很煩的一件事情是寫多個 getter 和 setter,復制/粘貼該方法的主體再為每個變量重命名。
幸運的是,Android Studio 有一個解決方法。可以這樣做,在類里聲明你需要的所有變量,然后打開 Toolbar >> Code。快捷方式是 ALT + Insert。點擊 Code 會顯示 Generate,點擊它會出來很多選項,里面有 Getter 和 Setter 選項。在保持焦點在你的類頁面然后點擊,就會為當前類添加所有的 getter 和 setter(有需要的話可以再去之前的窗口操作)。很爽吧。
9、使用 Override/Implement 生成工具
這是另一個很好用的生成工具。自定義一個類然后再擴展很容易,但是如果要擴展你不熟悉的類呢。比如說 PagerAdapter,你希望用 ViewPager 來展示一些頁面,那就需要定制一個 PagerAdapter 并實現它的重載方法。但是具體有哪些方法呢?Android Studio 非常貼心地為自定義類強行添加了一個構造函數,或者可以用快捷鍵(ALT + Enter),但是父類 PagerAdapter 里的其他(虛擬)方法需要自己手動添加,我估計大多數人都覺得煩。
要列出所有可以重載的方法,可以點擊 Code >> Generate and Override methods 或者 Implement methods,根據你的需要。你還可以為你的類選擇多個方法,只要按住 Ctrl 再選擇方法,然后點擊 OK。
10、正確理解 Context
Context 有點恐怖,我估計許多初學者從沒有認真理解過 Context 類的結構 - 它是什么,為什么到處都要用到它。
簡單地說,它將你能從屏幕上看到的所有內容都整合在一起。所有的視圖(或者它們的擴展)都通過 Context 綁定到當前的環境。Context 用來管理應用層次的資源,比如說顯示密度,或者當前的關聯活動。活動、服務和應用都實現了 Context 類的接口來為其他關聯組件提供內部資源。舉個添加到 MainActivity 的 TextView 的例子。你應該注意到了,在創建一個對象的時候,TextView 的構造函數需要 Context 參數。這是為了獲取 TextView 里定義到的資源。比如說,TextView 需要在內部用到 Roboto 字體。這樣的話,TextView 需要 Context。而且在我們將 Context(或者 this)傳遞給 TextView 的時候,也就是告訴它綁定當前活動的生命周期。
另一個 Context 的關鍵應用是初始化應用層次的操作,比如初始化一個庫。庫的生命周期和應用是不相關的,所以它需要用 getApplicationContext() 來初始化,而不是用 getContext 或 this 或 getActivity()。掌握正確使用不同 Context 類型非常重要,可以避免內存泄漏。另外,要用到 Context 來啟動一個活動或服務。還記得 startActivity(…) 嗎?當你需要在一個非活動類里切換活動時,你需要一個 Context 對象來調用 startActivity 方法,因為它是 Context 類的方法,而不是 Activity 類。
getContext().startActivity(getContext(), SecondActivity.class);
如果你想了解更多 Context 的行為,可以看看這里(https://blog.mindorks.com/understanding-context-in-android-application-330913e32514)或這里(https://developer.android.com/reference/android/content/Context.html)。第一個是一篇關于 Context 的很好的文章,介紹了在哪些地方要用到它。而另一個是安卓關于 Context 的文檔,全面介紹了所有的功能 - 方法,靜態標識以及更多。
獎勵 #1:格式化代碼
有人會不喜歡整齊,統一格式的代碼嗎?好吧,幾乎我們每一個人,在寫一個超過 1000 行的類的時候,都希望我們的代碼能有合適的結構。而且,并不僅僅大的類才需要格式化,每一個小模塊類也需要讓代碼保持可讀性。
使用 Android Studio,或者任何 JetBrains IDE,你都不需要自己手動整理你的代碼,像增加縮進或者 = 之前的空格。就按自己希望的方式寫代碼,在想要格式化的時候,如果是 Windows 系統可以按下 ALT + CTRL + L,Linux 系統按下 ALT + CTRL + SHIFT + L。代碼就自動格式化好了
獎勵 #2:使用庫
面向對象編程的一個重要原則是增加代碼的可重用性,或者說減少重新發明輪子的習慣。很多初學者錯誤地遵循了這個原則。這條路有兩個方向,
不用任何庫,自己寫所有的代碼。
用庫來處理所有事情。
不管哪個方向走到底都是不對的。如果你徹底選擇第一個方向,你將消耗大量的資源,僅僅是為了滿足自己擁有一切的驕傲。很可能你的代碼沒有做過替代庫那么多的測試,從而增加模塊出問題的可能。如果資源有限,不要重復發明輪子。直接用經過測試的庫,在有了明確目標以及充分的資源后,可以用自己的可靠代碼來替換這個庫。
而徹底走向另一個方向,問題更嚴重 - 別人代碼的可靠性。不要習慣于所有事情都依賴于別人的代碼。在不用太多資源或者自己能掌控的情況下盡量自己寫代碼。你不需要用庫來自定義一個 TypeFaces(字體),你可以自己寫一個。
所以要記住,在這兩個極端中間平衡一下 - 不要重新創造所有事情,也不要過分依賴外部代碼。保持中立,根據自己的能力寫代碼。
?
評論
查看更多