前言#
之前我們已經學習了注解在運行時的使用,結合反射來實現了一個基本的數據庫框架,網上有很多人都說反射是一個效率低下的操作行為,是不提倡的,所以這次來親自驗證一下。
正文#
這次的測試還是用之前的例子,看一下代碼:
@ContentView(value = R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.e("lzp", "start setContentView:" + System.currentTimeMillis());
// AnnotationUtils.injectObject(this);
setContentView(R.layout.activity_main);
Log.e("lzp", "start setContentView over:" + System.currentTimeMillis());
}
}
我們先來看一下兩種方式只運行一次的效率差,每種8組數據:
setContentView():164、125、221、264、219、242、285、289
平均時間:226.125@ContentView():307、244、238、225、230、231、235、253
平均時間:245.375
從以上兩組數據對比,如果只是一次幾乎沒有什么太大的差別,回顧一下之前的上一篇的數據庫框架,每次操作數據庫只進行了一次反射,所以影響是很小的。
接下來測試一下100次的效率,還是每種8組數據:
setContentView():1050、1069、1066、1051、1213、1197、1058、1047
平均時間:1093.875@ContentView():1228、1080、1088、1075、1136、1076、1081、1080
平均時間:1105.5
從平均時間來看,跟只運行一次的差別并沒有預期的成比增加,setContentView()一般是在1050-1060之間,@ContentView()大概是在1080來回浮動。
我用的是android5.0系統,2g運行內存的真機測試,不管系統是否已經對反射進行了優化,或者是現在的運行內存的提高,縮小了兩者的差別,最重要的是,我們已經親自驗證了反射確實效率要稍微低一些。
為什么會這樣呢?仔細一想其實也可以理解,就好像一個已經包裝好的快遞,人家給你了直接能打開的地方,你偏要拿剪刀從其他剪開弄開他,肯定是多耗費一些力氣,而且這違背了作者設計的初衷,所以反射一般是不受待見的,但是破壞者卻覺得很high。
<h2>如何解決效率低下的問題</h2>
我們不排除在某些機型或者系統的情況下,反射的效率比我們測試的結果要糟糕的多,所以必須要解決這個問題。
如果并發非常的大,最簡單有效的方法就是緩存,防止同一個類反射多次,造成效率低下的問題,例如Lrucache。
為了能看到明顯的差距,我把循環次數加到100次,先貼出優化后的代碼:
private static LruCache<String, Method> methodLruCache = new LruCache<>(10);
private static LruCache<String, ContentView> annotationLruCache = new LruCache<>(10);
public static void injectObject(Object handler){
// 拿到參數的Class類型
Class<?> handleType = handler.getClass();
// 獲取Class類型上的ContentView注解
String key = handleType.getCanonicalName();
ContentView contentView = annotationLruCache.get(key);
if (contentView == null){
contentView = handleType.getAnnotation(ContentView.class);
annotationLruCache.put(key, contentView);
}
// ContentView contentView = handleType.getAnnotation(ContentView.class);
if (contentView != null){
try {
// 通過反射找到Class類型中的setContentView(int param)方法
Method setContentViewMethod = methodLruCache.get("setContentView_method");
if (setContentViewMethod == null){
setContentViewMethod = handleType.getMethod("setContentView", int.class);
methodLruCache.put("setContentView_method", setContentViewMethod);
}
// Method setContentViewMethod = handleType.getMethod("setContentView", int.class);
// 調用setContentView(int param),第一個是操作的對象,第二個是方法的參數
setContentViewMethod.invoke(handler, contentView.value());
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
代碼中進行了兩處修改,一個是contentView注解的緩存,一個是Methd的緩存,當然這種緩存策略是有問題,重點是看否是提高了效率:
優化前:9772、9694、9956、9849、9933、9747、9984、10254(這個數據不計算,差距太大)
平均時間:9847.85優化后:9592、9621、9615、9466、9646、9649、9677、9545
平均時間:9601.375
總結#
從測試結果上,可以總結出三點:
1、反射的效率確實比直接的方法調用,慢了一些。
2、android應該已經對反射機會進行了優化(至少是android 5.0),網上很早的帖子中描述的那么大的效率差已經縮短了很多。
3、如果是高并發量的操作,或者是循環量大的操作,效率還是會低一些,所以不能因此而忽略了優化。
有對反射更好的理解和優化建議,希望大家留言共同學習進步。