寫在前面
去年,哦不,前年五月的時候,谷歌宣佈了Kotlin-First 這一重要概念,主張Kotlin 是 Android 開發者的首選語言。
消息發出去後,很多人心中都有了一個疑問:那我一個搞Android開發的,我現在是不是以後就只用學Kotlin 就可以了?
一般有這個疑問或者想法的人都是外行人或者說是入行不久的人。
今天在這裏就分享一個行業大佬的看法吧,本文也可以當做移動開發新手手冊。希望本文對大家有幫助。
來自公衆號:承香墨影
本文內容:
- Kotlin可能帶來的坑
- 分析總結
- 移動開發學習
Kotlin可能帶來的坑
一. 前言
新語言有新特性,開發者還保持 Java 的編程習慣去寫 Kotlin,也不是不行,但是總感覺差點意思。
最近公衆號「谷歌開發者」連載了一個《實用 Kotlin 構建 Android 應用 | Kotlin 遷移指南》的系列文章,就舉例了一些 Kotlin 編碼的小技巧。
既然是一種指南性質的文章,自然在「多而廣」的基礎上,有意去省略一些細節,同時舉例的場景,可能還有一些不恰當的地方。
這裏我就來補齊這些細節,今天聊聊利用 Kotlin 的方法默認參數的特性,完成類似 Java 的方法重載的效果。完全解析這個特性的使用方式和原理,以及在使用過程中的一個深坑。
二. Kotlin 的簡易方法重載
2.1 Kotlin 如何簡化方法重載?
在 Java 中,我們可以在同一個類中,定義多個同名的方法,只需要保證每個方法具有不同的參數類型或參數個數,這就是 Java 的方法重載。
class Hello {
public static void hello() {
System.out.println("Hello, world!");
}
public static void hello(String name) {
System.out.println("Hello, "+ name +"!");
}
public static void hello(String name, int age) {
if (age > 0) {
System.out.println("Hello, "+ name + "(" +age +")!");
} else {
System.out.println("Hello, "+ name +"!");
}
}
}
在這個例子中,我們定義了三個同名的 hello()
方法,具有不同的邏輯細節。
在 Kotlin 中,因爲它支持在同一個方法裏,通過 「?」標出可空參數,以及通過「=」給出參數的默認值。那這三個方法就可以在 Kotlin 中,被柔和成一個方法。
object HelloDemo{
fun hello(name: String = "world", age: Int = 0) {
if (age > 0) {
System.out.println("Hello, ${name}(${age})!");
} else {
System.out.println("Hello, ${name}!");
}
}
}
在 Kotlin 類中調用,和前面 Java 實現的效果是一致的。
HelloDemo.hello()
HelloDemo.hello("承香墨影")
HelloDemo.hello("承香墨影", 16)
但是這個通過 Kotlin 方法參數默認值的特性申明的方法,在 Java 類中使用時,就有些區別了。因爲 HelloDemo 類被聲明爲 object,所以在 Java 中需要使用 INSTANCE
來調用它的方法。
HelloDemo.INSTANCE.hello("承香墨影",16);
Kotlin 中調用 hello()
方法很方便,可以選擇性的忽略參數,但是在 Java 中使用,必須全量的顯式的去做參數賦值。
這就是使用了參數默認值的方法申明時,分別在 Kotlin 和 Java 中的使用方式,接下來我們看看原理。
2.2 Kotlin 方法參數指定默認值的原理
Kotlin 編寫的代碼,之所以可以在 Java 系的虛擬機中運行,主要是因爲它在編譯的過程中,會被編譯成虛擬機可識別的 Java 字節碼。所以我們通過兩次轉換的方式(Show Kotlin Bytecode + Decompile),就可以得到 Kotlin 生成的對應 Java 代碼了。
public final void hello(@NotNull String name, int age) {
Intrinsics.checkParameterIsNotNull(name, "name");
if (age > 0) {
System.out.println("Hello, " + name + '(' + age + ")!");
} else {
System.out.println("Hello, " + name + '!');
}
}
// $FF: synthetic method
public static void hello$default(HelloDemo var0, String var1, int var2, int var3, Object var4) {
if ((var3 & 1) != 0) {
var1 = "world";
}
if ((var3 & 2) != 0) {
var2 = 0;
}
var0.hello(var1, var2);
}
在這裏會生成一個 hello()
方法,同時還會有一個合成方法(synthetic method)hello$default
,用來處理默認參數的問題。在 Kotlin 中調用 hello()
方法,會在編譯期間,有選擇性的自動替換成 hello()
的合成方法去調用。
// Kotlin 調用
HelloDemo.hello()
HelloDemo.hello("承香墨影")
HelloDemo.hello("承香墨影", 16)
// 編譯後的 Java 代碼
HelloDemo.hello$default(HelloDemo.INSTANCE, (String)null, 0, 3, (Object)null);
HelloDemo.hello$default(HelloDemo.INSTANCE, "承香墨影", 0, 2, (Object)null);
HelloDemo.INSTANCE.hello("承香墨影", 16);
注意看示例的末尾,當使用 hello(name,age)
這個方法重載時,其實與 Java 中的調用,是一致的,這沒什麼好說的。
這就是 Kotlin 方法重載時,使用指定默認參數的方式,省去多個方法重載代碼的原理。
理解原理後,發現它確實減少了我們編寫的代碼量,但是有沒有場景,是我們就需要顯式的存在這幾個方法的重載的?自然是有的,例如自定義 View 時。
三. 自定義 View 遇上 Kotlin
3.1 構造方法也是方法
再回到前面提到的谷歌開發者的《實用 Kotlin 構建 Android 應用 | Kotlin 遷移指南》系列文章中,舉的例子其實很不恰當。
它這裏的例子中,使用了 View 這個詞,並且重載的幾個方法,都是 View 的構造方法,我們在自定義 View 時,經常會和這三個方法打交道。
但是谷歌工程師在這裏舉的例子,很容易讓人誤會,實際上你如果在自定義 View 時,這麼寫一定是會報錯的。
例如我們自定義一個 DemoView,它繼承自 EditView。
class DemoView(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : EditText(context, attrs, defStyleAttr) {
}
這個自定義的 DemoView,當使用在 XML 佈局中時,雖然編譯不會出錯,但是運行時,你會得到一個 NoSuchMethodException。
Caused by: java.lang.NoSuchMethodException: <init> [class android.content.Context, interface android.util.AttributeSet]
什麼問題呢?
在 LayoutInflater 創建控件時,找不到 DemoView(Context, AttributeSet)
這個重載方法,所以就報錯了。
這其實很好理解,在前面說到 Kotlin 在使用帶默認值的方法的原理,其實 Kotlin 最終會在編譯後,額外生成一個合成方法
,來處理方法的參數默認值的情況,它和 Java 的方法重載還不一樣,用它生成的方法,確實不會存在多個方法的重載。
所以要明白,Kotlin 的方法指定默認參數與 Java 的方法重載,並不等價。只能說它們在某些場景下,特性是類似的。
3.2 使用 @JvmOverloads
那麼回到這裏的問題,在自定義 View 或者其他需要保留 Java 方法重載的場景下,怎麼讓 Kotlin 在編譯時,真實的去生成對應的重載方法?
這裏就需要用到 @JvmOverloads
了。
當 Kotlin 使用了默認值的方法,被增加了 @JvmOverloads
註解後,它的含義就是在編譯時,保持並暴露出該方法的多個重載方法。
其實當我們自定義 View 時,AS 已經給了我們充分的提示,它會自動幫我們生成帶@JvmOverloads
構造方法。
AS 幫我們補全的代碼如下:
class DemoView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AppCompatEditText(context, attrs, defStyleAttr) {
}
再用「Kotlin Bytecode + Decompile」查看一下編譯後的代碼,來驗證 @JvmOverloads
的效果。
@JvmOverloads
public DemoView(@NotNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
Intrinsics.checkParameterIsNotNull(context, "context");
super(context, attrs, defStyleAttr);
}
// $FF: synthetic method
public DemoView(Context var1, AttributeSet var2, int var3, int var4, DefaultConstructorMarker var5) {
if ((var4 & 2) != 0) {
var2 = (AttributeSet)null;
}
if ((var4 & 4) != 0) {
var3 = 0;
}
this(var1, var2, var3);
}
@JvmOverloads
public DemoView(@NotNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0, 4, (DefaultConstructorMarker)null);
}
@JvmOverloads
public DemoView(@NotNull Context context) {
this(context, (AttributeSet)null, 0, 6, (DefaultConstructorMarker)null);
}
可以看到,@JvmOverloads
生效後,會按照我們的預期生成對應的重載方法,同時保留合成方法,完成在 Kotlin 中使用時,使用默認參數的需求。
是不是以爲到這裏就完了?並不是,如果你在自定義 View 時,完全按照 AS 給你的提示生成代碼,雖然程序不會崩潰了,但你會得到一些未知的錯誤。
3.3 View 中別直接用 AS 生成代碼
在自定義 View 時,依賴 AS 的提示生成代碼,會遇到一些未知的錯誤。例如在本文的例子中,我們想要實現一個 EditView 的子類,用 AS 提示生成了代碼。
會出現什麼問題呢?
在 EditView 的場景下,你會發現焦點沒有了,點擊之後軟鍵盤也不會自動彈出。
那爲什麼會出現這種問題?
原因就在 AS 在自動生成的代碼時,對參數默認值的處理。
當在自定義 View 時,通過 AS 生成重載方法時,它對參數默認值的處理規則是這樣的。
-
遇到對象,默認值爲 null。
-
遇到基礎數據類型,默認值爲基本數據類型的默認值。例如 Int 就是 0,Boolean 就是 false。
而在這裏的場景下, defStyleAttr
這個參數的類型爲 Int,所以默認值會被賦值爲 0,但是它並不是我們需要的。
在 Android 中,當 View 通過 XML 文件來佈局使用時,會調用兩個參數的構造方法(Context context, AttributeSet attrs)
,而它內部會調用三個參數的構造方法,並傳遞一個默認的 defStyleAttr
,注意它並不是 0。
既然找到了問題,就很好解決了。我們看看自定義 View 的父類中,兩個參數的構造方法如何實現的,將 defStyleArrt
當默認值傳遞進去就好了。
那我們先看看 AppCompatEditText
中的實現。
public AppCompatEditText(Context context,
AttributeSet attrs) {
this(context, attrs, R.attr.editTextStyle);
}
再修改 DemoView 中對 defStyleAttr
默認值的指定即可。
class DemoView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = R.attr.editTextStyle
) : AppCompatEditText(context, attrs, defStyleAttr) {
}
到這裏,自定義 View 中,使用默認參數的構造方法重載問題,也解決了。
在自定義 View 的場景下,當然也可以通過重寫多個 constructor
方法來實現類似的效果,但是既然已經明白了它的原理,那就放心大膽的使用吧。
二、小結時刻
到這裏就弄清楚 Kotlin 中,使用默認參數來減少方法重載代碼的使用技巧和原理,以及注意事項了。
弄清楚原理以及需要注意的點,可以幫助我們更好的使用 Kotlin 的特性。我們最後再總結一下本文的知識點:
-
Kotlin 可以通過對一個方法的參數,通過指定默認值的方式,來完成類似 Java 中「方法重載」的效果。
-
若想保留 Java 的重載方法,可以使用
@JvmOverloads
註解標記,它會自動生成該方法的全部重載方法。 -
在自定義 View 時,需要注意指定參數
defStyleAttr
的默認值,而不應該是 0。
三、移動開發學習指南
上面已經對Kotlin可能帶來的一個深坑進行了解析,我想大家心中對一開始的問題也有了答案。
移動開發,特別是Android 開發,現在依然是以Java語言爲主(Java語言依然是最通用的),現在的Android 開發需要學習的東西主要是以下內容 。
(一)架構師築基語言基礎
目前Android APP開發主流語言就是Java語言,Java語言最大的特性就是提高了軟件的交互可能性,可以說安卓手機幾乎所有應用程序都是利用Java語言來進行編寫的。
知識要點:
1、深入理解Java泛型
2、註解深入淺出
3、併發編程
4、數據傳輸與序列化
5、Java虛擬機原理
6、高效IO
(二)設計思想解讀開源框架
隨着互聯網企業的不斷髮展,產品項目中的模塊越來越多,用戶體驗要求也越來越高,想實現小步快跑、快速迭代的目的越來越難,插件化技術應用而生。如果沒有插件化技術,美團、淘寶這些集成了大量“app”的應用,可能會有幾個g那麼大。
所以,當今的Android移動開發,不會熱修復、插件化、組件化,80%以上的面試都過不了。
知識要點:
1、熱修復設計
2、插件化框架設計
3、組件化框架設計
4、圖片加載框架
5、網絡訪問框架設計
6、RXJava響應式編程框架設計
(三)360°全方位性能調優
在不同層次的開發工程師手裏,因爲技術水平的參差不齊,即使很多手機在跑分軟件性能非常高,打開應用依然存在卡頓現象。
另外,隨着產品內容迭代,功能越來越複雜,UI頁面也越來越豐富,也成爲流暢運行的一種阻礙。綜上所述,對APP進行性能優化已成爲開發者該有的一種綜合素質,也是開發者能夠完成高質量應用程序作品的保證。
知識要點:
1、設計思想與代碼質量優化
2、程序性能優化
啓動速度與執行效率優化
佈局檢測與優化
內存優化
耗電優化
網絡傳輸與數據儲存優化
APK大小優化3、開發效率優化
分佈式版本控制系統Git
自動化構建系統Gradle4、項目實戰
啓動速度
流暢度
抖音在APK包大小資源優化的實踐
優酷響應式佈局技術全解析
網絡優化
手機淘寶雙十一性能優化項目揭祕
高德APP全鏈路源碼依賴分析
徹底幹掉OOM的實戰經驗分享
微信Android終端內存優化實踐
(四)Android框架體系架構
Android框架體系架構(高級UI+FrameWork源碼) 這塊知識是現今使用者最多的,我們稱之Android2013~2016年的技術。
Android開發者也往往因爲網上Copy代碼習慣了而導致對這塊經常“使用”的代碼熟悉而又陌生:熟悉的是幾乎天天在和它們打交道, 天天在複製這些代碼 ;陌生的是雖然天天和這些代碼打交道,但是並沒有深入研究過這些代碼的原理,代碼深處的內涵。
知識要點:
1、高級UI晉升
2、Android內核組件
3、大型項目必備IPC
4、數據持久與序列化
5、Framework內核解析**
(五)NDK模塊開發(音視頻開發系列)
NDK(Native Development Kit縮寫)一種基於原生程序接口的軟件開發工具包,可以讓您在 Android 應用中利用 C 和 C++ 代碼的工具。通過此工具開發的程序直接在本地運行,而不是虛擬機。
在Android中,NDK是一系列工具的集合,主要用於擴展Android SDK。NDK提供了一系列的工具可以幫助開發者快速的開發C或C++的動態庫,並能自動將so和Java應用一起打包成apk。
知識要點:
1、NDK開發之C/C++入門
2、JNI模塊開發
3、Linux編程
4、底層圖片處理
5、音視頻開發
6、機器學習
(六)Flutter學習進階
2020 年無疑是 Flutter 技術如火如荼發展的一年。現在這門技術也依然非常有價值。
每一個移動開發者都在爲 Flutter 帶來的“快速開發、富有表現力和靈活的 UI、原生性能”的特色和理念而癡狂,從超級 App 到獨立應用,從純 Flutter 到混合棧,開發者們在不同的場景下樂此不疲的探索和應用着 Flutter 技術,也在面臨着各種各樣不同的挑戰。
知識要點:
1、Flutter跨平臺開發概述
2、Windows中Flutter開發環境搭建
3、編寫你的第一個Flutter APP
4、Flutter Dart語言系統入門**
……
(七)微信小程序開發
微信小程序作爲現在比較火的編程開發應用場景之一,深受市場的青睞,這讓不少開發者眼饞不已。但是對於初學者來說,就完全摸不着頭腦了,不知道微信小程序開發製作需要學習那些知識,有需要的朋友可以參考本篇。
本篇知識要點:
1、小程序概述及入門
2、小程序UI開發
3、API操作
4、購物商場項目實戰
(八)Android相關源碼解讀
這裏大家可以去看這一篇文章:牛掰!阿里P7大佬爆肝半個月,把安卓源碼解析編成了508頁的PDF。
文中完整版資料已經整理成爲PDF文檔,感興趣的朋友可以點擊此處,快速獲取!希望能幫助到大家!
最後
祝大家工作順利!預祝新年快樂!
Android 開發之路,你我共勉!2021年,一起牛掰!