反射 vs 正常調(diào)用:一場方法調(diào)用的“速度對決”
反射調(diào)用就像穿著拖鞋跑馬拉松——看似自由,實則步步維艱,而正常調(diào)用則是專業(yè)跑鞋加持的百米沖刺。
主角:小明(一個普通的Android程序員)
敵人:Method.invoke() —— 那個看似方便卻偷偷拖慢APP的“慢郎中”
賽道設(shè)定:誰先跑到目標(biāo)方法?
想象一下,你要去參加一場百米賽跑:
? 選手A:穿運動服、熱身完畢、槍響就沖!
? 選手B:穿著拖鞋,一邊跑一邊查地圖、問路、換衣服,還中途停下做安檢……
你說誰能贏?
在代碼世界里,正常方法調(diào)用就是那個穿運動服的選手A,而反射調(diào)用……嗯,就是那位拖拖拉拉的B君。
今天我們就來看看,為啥這位B君總是卡頓你家APP的主線程。
場景一:正常調(diào)用 —— “熟門熟路,秒到家”
public class Hero {
public void attack() {
System.out.println("?? 發(fā)動普通攻擊!");
}
}
// 小明調(diào)用:
Hero hero = new Hero();
hero.attack(); // ?? 直接呼叫!這就是我們每天寫的代碼,干凈利落。
它經(jīng)歷了什么?
Hero對象JVM/ART小明(調(diào)用者)
Hero對象
JVM/ART
小明(調(diào)用者)編譯時已綁定直接跳轉(zhuǎn)地址調(diào)用 hero.attack()
invoke-virtual
執(zhí)行完成
返回結(jié)果? 編譯的時候,Java就知道你要調(diào) Hero.attack()。
? 安裝APP時,ART(Android Runtime)早就把這條路鋪好了,連門牌號都刻在石頭上。
? 運行時?一句話指令下去:invoke-virtual,直接飛過去,連導(dǎo)航都不用開!
場景二:反射調(diào)用 —— “臨時查戶口,層層審批”
try {
Class<?> clazz = Class.forName("Hero"); // 找到類
Object hero = clazz.newInstance(); // 實例化對象
Method method = clazz.getMethod("attack"); // 查找方法
method.invoke(hero); // 執(zhí)行方法!
} catch (Exception e) {
e.printStackTrace();
}典型的反射四步走:找類 → 實例化 → 找方法 → 調(diào)用。看著挺酷,實則暗藏玄機。
它經(jīng)歷了什么?
我們來拆解這場“行政流程”??
Hero對象Native層JNI橋接層Java層小明(調(diào)用者)
Hero對象
Native層
JNI橋接層
Java層
小明(調(diào)用者)method.invoke(hero)
進入JNI橋接
開始權(quán)限檢查
查找方法ID驗證訪問權(quán)限參數(shù)打包裝箱最終派發(fā)調(diào)用
執(zhí)行完成
返回結(jié)果
跨層返回
最終返回結(jié)果第一步:你是誰?證明一下身份!(權(quán)限檢查)
每次 .invoke(),JVM都要問一遍:“你有權(quán)訪問這個方法嗎?”
哪怕它是public,也要走一遍流程——相當(dāng)于進公司大樓每次刷臉+保安打電話確認。
?? 如果沒開 setAccessible(true),那就更慘,層層審核。
method.setAccessible(true); // ?? 提前打聲招呼:“我是自己人!”即便開了綠燈,系統(tǒng)還是會瞟一眼:“哦,這人備案過。”——依然有判斷開銷。
第二步:參數(shù)要打包!拆箱裝箱忙不停
假設(shè)你有個帶參數(shù)的方法:
public void castSpell(int level, String name) {
System.out.println("? 施展 " + name + " 法術(shù),等級:" + level);
}用反射怎么傳參?
Method spellMethod = clazz.getMethod("castSpell", int.class, String.class);
spellMethod.invoke(hero, 5, "火球術(shù)"); // 參數(shù)會被自動包裝成 Object[]但實際上發(fā)生了啥?
操作 | 后果 |
5 → Integer.valueOf(5) | 新建一個Integer對象(堆內(nèi)存分配) |
"火球術(shù)" | 沒問題,String本身就是Object |
所有參數(shù)放進 Object[] args | 再新建一個數(shù)組對象 |
?? 到了方法內(nèi)部,又要反過來:
? Object[0] 拆箱成 int
? 類型匹配檢查一遍
?? 一次調(diào)用,憑空多了好幾個對象,GC看了都想哭。
第三步:跨世界傳送 —— JNI 大門開啟!
.invoke() 是個 native 方法,背后是 C++ 寫的。
這意味著:
?? 從 Java 層 → Native 層(C/C++),要進行 上下文切換,就像從客廳走到地下室翻工具箱。
?? 在 Android 的 ART 虛擬機中,這段邏輯位于:
art/runtime/native/java_lang_reflect_Method.cc每調(diào)一次,都要來回折騰一趟,CPU表示:“累死了。”
第四步:編譯器放棄了你
現(xiàn)代 JVM 和 ART 最擅長干一件事:內(nèi)聯(lián)優(yōu)化(Inlining)
void sayHello() { log("Hello"); }JIT 編譯器可能會直接把 log("Hello") 插進去,連函數(shù)調(diào)用都省了!
?? 但反射?
? 目標(biāo)方法在運行時才確定,編譯器一臉懵:“我不知道你要調(diào)哪個方法啊!”
?? 內(nèi)聯(lián)失敗,其他優(yōu)化統(tǒng)統(tǒng)靠邊站。
?? 相當(dāng)于高速公路給你限速20km/h。
總結(jié)
項目 | 正常調(diào)用 | 反射調(diào)用 |
綁定時機 | 編譯時 ? | 運行時 ? |
權(quán)限檢查 | 一次過 | 每次都要查 |
參數(shù)傳遞 | 直接傳值 | 裝箱+數(shù)組封裝 |
調(diào)用路徑 | 直接跳轉(zhuǎn) | JNI → Native → 查找 → 派發(fā) |
是否可優(yōu)化 | 可內(nèi)聯(lián),極致優(yōu)化 | 幾乎無法優(yōu)化 |
性能 | ? 極快 | ?? 明顯變慢 |
別急著刪掉所有反射代碼,有些地方它確實香。但我們得聰明地用!
正確姿勢1:緩存反射對象
別每次都要 getMethod(),太貴了!
public class SpellInvoker {
private static Method sCastSpellMethod;
static {
try {
Class<?> clazz = Class.forName("Hero");
sCastSpellMethod = clazz.getMethod("castSpell", int.class, String.class);
sCastSpellMethod.setAccessible(true); // 提前放行
} catch (Exception e) {
e.printStackTrace();
}
}
public void quickInvoke(Hero hero) throws Exception {
sCastSpellMethod.invoke(hero, 5, "雷電術(shù)");
}
}?? 只查一次方法,后面反復(fù)用,省下查找+校驗成本。
正確姿勢2:高版本考慮 MethodHandle
Android API 26+ 支持:
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType type = MethodType.methodType(void.class, int.class, String.class);
MethodHandle mh = lookup.findVirtual(Hero.class, "castSpell", type);
// 調(diào)用
mh.invokeExact(hero, 5, "冰霜新星");?? 特點:比反射更貼近JVM底層,更容易被優(yōu)化,性能高出一大截!
正確姿勢3:能不用就不用,用代碼生成代替
像 Glide、Dagger、Room 這些庫,早就不靠反射了,它們用 APT(注解處理器) 在編譯期生成代碼。
@BindView(R.id.textView)
TextView tv;以前 Butter Knife 用反射綁定控件,現(xiàn)在主流做法是編譯時生成 findViewById() 代碼,零運行時開銷!
?? 思想轉(zhuǎn)變:把“運行時找”變成“編譯時準(zhǔn)備好”。
反射不是洪水猛獸,但它絕對不適合出現(xiàn)在“高頻調(diào)用”或“主線程”的舞臺上。“能靜態(tài)就不動態(tài),能提前就不臨時。”























