android開發,你們還在findviewbyid嗎?


Why not?

反射是有效率問題的,註解什麼的,也是有多餘的內存消耗的!

update:

放一張Android官方的圖:

地址在這裡:Managing Your App"s Memory


註解反射確實挺不錯的,不過我現在是這樣兒:
private & T $(int resId) {
return (T) super.findViewById(resId);
}

用的時候就這樣兒 ibTakePicture = $(R.id.ib_camera_take_picture);
主要是不用寫那麼多findViewById了~


很久之前看到這個問題,因為習慣的原因,依舊老老實實地寫著findviewbyid。

前幾天突然萌生寫個插件,自動生成findviewbyid代碼的插件,然後花了點時間了解了下插件的開發,歷時三天,擼好了這個插件,然後取了個名字叫FindViewByMe :)

插件地址:JetBrains Plugin Repository :: FindViewByMe

更多使用介紹:第一個插件:FindViewByMe

github地址:FindViewByMe · GitHub (代碼還沒怎麼整理,可能有點亂,後面再優化整理下)


一些註解框架並不是使用反射,只是利用aapt處理資源生成代碼。如

excilys/androidannotations · GitHub

也有一些框架甚至不需要註解,如evant/holdr · GitHub

當然,官方的DataBinding必然是未來的趨勢。

這些解決方案並不會產生大的性能損耗卻會一定程度上提高開發效率。


做了2年安卓你居然會覺得反射好,這東西是逼不得以才用的。


分享一下自己的野路子

材料 :搜狗打字法

1.複製這句話

2. 隨便輸入一個提示語,然後滑鼠放在這一帶,會出現一個添加短語

3.粘貼進去

4.下次你再輸入ftv的時候就會有這個提示了

同理toast也可以這麼玩

以後只要登錄搜狗賬號,換了電腦也不怕噠

----------------------

不要吐槽這個方法啦,這是在用notepad的時候想到的,

idea 可以去搜一下 live template ,

idea的setting各個版本都通用,Android studio過度webstorm幾乎無壓力


用的Android Studio插件——Android Layout ID Converter (現在應該也有其他類似的東西可以根據自己需要選擇) 自動生成相應xml文件里所有的findViewById,實際上跟用ButterKnife之類的依賴注入便捷都差不多了,按一下快捷鍵就自動生成代碼了。依賴注入如果只是用來替代view注入,除了代碼看起來簡短也並沒有多大優點。


╮( ̄▽ ̄")╭ 用工具生成的findviewbyid

不喜歡用註解,因為那對代碼有種篡改的感覺。

2016年更新。Google 說 世界上已經31個 find view by ID 的插件了。不過 Google 推出了最後一個 那就是 data binding.

所以也就用它了。


使用註解框架是用來降低冗餘重複代碼的開發工作量。自有它存在的價值,仁者見仁智者見智。
但是你少寫一半代碼,框架就需要兩倍甚至多倍的代碼去完成,不可避免會有效率問題,所以要根據自己的喜好抉擇。

註解對比

View註解方式目前我知道的有運行時註解(比如xUtils的ViewInject)

編譯時註解(比如ButterKnife的InjectView):

工作原理:

xUtils的ViewInject也就是在class執行的時候依然有效。ViewUtils.inject方法內部就是以反射的形式,尋找當前target里的的帶ViewInject的欄位,並通過target進行findViewById操作然後賦值,反射所有欄位賦值。這種方式也是Google文檔里不推薦註解框架的一個原因。

而ButterKnife的InjectView是CLASS級別,是通過annotation processors,讀取你的註解欄位並自動生成java代碼,然後被編譯成生成class文件。

內容為:

可見,它的註解其實是幫你寫了一坨(請原諒我用這個量詞::&>_&<:: )findViewById的代碼。

怎麼生效呢?ButterKnife.inject(this);這句代碼其實就是反射找到這個inject靜態方法,並調用。是反射調用一個方法。而xUtils呢,是反射所有欄位並賦值。相比較,性能的損耗,要少一些。這麼對比,我肯定選擇ButterKnife。

但是你可能會說,畢竟一個findViewById的操作還是給包裝了這麼多次,還被反射了一下,但是如果配合AndroidStdio(IDEA)里的Android ButterKnife Zelezny,你應該會再考慮一下要不要用一下。`(*∩_∩*)′

當然,findViewById也有類似自動生成的插件可以使用,比如Android Studio Prettify,但是這個貌似只能在activity的contentView上用,ViewHolder上還不支持,並且還有一些挺討厭的bug(如果你選中的這個layout在其他module里有重名的,會把別的module的同名布局裡的欄位生成出來,v0.3.1版本),而且它生成的是這樣子的!!

我真的不想看到那麼多的findViewById的代碼。::&>_&<::

性能對比:

說到性能的對比,我做過一次不嚴謹的測試,從activity_main布局中find出20個button,

時間:

findViewById: 34851 ns.

ButterKnife:time: 43059 ns

xUtils.ViewUtils: 5015032 ns

考慮到我自己每次運行時間都不一樣,我選擇運行多次,取不大不小中間的值,當然這個不同的設備結果肯定也不一樣。但是起碼可以看到ButterKnife相比普通的FBC操作,損耗的時候基本可以忽略。而ViewUtils對比下來就比較慘了。

所以,我喜歡ButterKnife+Zelezny。。哈哈


在android studio中使用快捷鍵ctrl+J,然後按fb,就會有現成的模板幫你寫好findviewbyid


findViewById是最基本的方式,寫多了不免讓人覺得蛋疼。

於是就有人開始基於註解的方式,在運行時動態進行查找,比如ViewMapping,不過這種方式使反射,性能實在不高,整個運行過程中,大概有90%的時間是耗費在反射上。初期還好,項目大了臃腫了,性能差的時候就成了累贅。我想現在基本沒人用這種方式了吧

同樣是基於註解,到把時間放到編譯期,生成查找類的代碼,不使用放射,不就可以了?於是就有了Jake 大神的 ButterKnife。確實很不錯,性能跟手寫findViewById差不多,所以深受Android程序員的喜愛。不過這種方式會會生成代碼,增加方法數,稍微有些年頭的成熟的大項目引進的話還是要考慮下影響。

最後就是谷歌爸爸去年發布的DataBinding框架了,同樣是編譯期生成代碼,但並不是用註解,而是用數據綁定的方式把變數引入了布局,能在布局內做更多的事,查找控制項只是它的一個附加功能,當你用熟了它之後,代碼內操作控制項的機會會比以前少很多很多。而且重點是性能感人,超越了findViewById!findViewById本質上是一次對VIEW樹的遍歷查找,每次調用查找控制項都會查找一次,雖然是O(n)的性能,但多次調用就變成了O(n) x m。但DataBinding則不然,通過一次遍歷把所有的控制項查找出來,然後賦值給對應的變數,完全不依賴findViewById,在任何情況下,複雜度都是O(n)。同樣的是生成代碼,但數據綁定框架提供了更多的功能,提高工作效率,編寫更安全可靠的代碼。這幾點簡直讓人愛不釋手。當然大工程的引入還是要考慮下,我覺得利是遠遠大於弊的。

數據綁定的主創甚至說了:我們發布得太晚了,這個東西應該在7,8年前我們發布Android的時候就推出的。

所以,還在猶豫什麼呢?大膽上吧


建議:迫不得已不要用反射

官方的 java文檔。 Android內存是個大問題 更要避免反射。

還有 產品卡了代碼再酷炫也沒用…再說反射哪酷炫了…

Drawbacks of Reflection

Reflection is powerful, but should not be used indiscriminately. If it is possible to perform an operation without using reflection, then it is preferable to avoid using it. The following concerns should be kept in mind when accessing code via reflection.

Performance OverheadBecause reflection involves types that are dynamically resolved, certain Java virtual machine optimizations can not be performed. Consequently, reflective operations have slower performance than their non-reflective counterparts, and should be avoided in sections of code which are called frequently in performance-sensitive applications.Security RestrictionsReflection requires a runtime permission which may not be present when running under a security manager. This is in an important consideration for code which has to run in a restricted security context, such as in an Applet.Exposure of InternalsSince reflection allows code to perform operations that would be illegal in non-reflective code, such as accessing private fields and methods, the use of reflection can result in unexpected side-effects, which may render code dysfunctional and may destroy portability. Reflective code breaks abstractions and therefore may change behavior with upgrades of the platform.


看到這個習慣強答一下個人的baseActivity/baseFragment常用幾個

1.setContentView或者onCreateView里設定

rootView,initViews(),initListeners()

2.initView裡邊使用

T& find(int rid){
return (T)findViewById(rid)
}

3.實現onClickListener,然後綁定

void initListeners(View ...views){
for(View view:views){
view.setOnClickListeners(this);
}
}

4.初始化toolbar,如果不為空的話。

還有左側返回onBackPressed()

5.關於onActivityResult的小奇技淫巧。

通常情況下 需要兩個Activity交互的常見寫法,比如選擇朋友並創建群組:

case R.id.tv_new_group:
//初始化數據,進入選人界面
startActivityForResult(...);
break;

//....other code

//響應回調:
onActivityResult(...){
if(re.. == 88){
if(resultCode==-1){
//創建群組的邏輯
}
}

}

顯然,代碼看起來不夠連貫,如果代碼再臃腫點,給後邊維護的同學就可能不小心挖坑了。

我希望的調用方式:

case R.id.tv_new_group:
//初始化數據,進入選人界面
startActivityForResult(... , new ActivityResultAction(){
onSuccess(..){
//創建群組的邏輯
}
});
break;

//....other code

這樣就很容易理解了。

(當然是為了代碼閱讀連貫,不然還能是為了多創建對象?)

具體實現方案:

protected SparseArray& mResultHandlers;

public void startActivityForResult(Intent intent, ActivityResultAction action) {
int rc = -100;
if (action != null) {
if(mResultHandlers == null) {
mResultHandlers = new SparseArray&();
}
rc = action.hashCode();
rc = 0x0000ffff;
mResultHandlers.append(rc,action);
startActivityForResult(intent, rc);
}else{
startActivity(intent);
}
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
ActivityResultAction activityResultAction = mResultHandlers.get(requestCode);
if(null!=activityResultAction){
activityResultAction.invoke(resultCode,data);
}
}

public abstract class ActivityResultAction{
private void invoke(Integer resultCode,Intent data){
switch (resultCode.intValue()){

case Activity.RESULT_OK:
onSuccess(data);
break;
case Activity.RESULT_CANCELED:
onCancel();
break;
default:
break;
}
}

abstract void onSuccess(Intent data);
abstract void onCancel();
}


反射不太好,性能開銷大,官方文檔說過的。優雅的方式是這樣的: AndroidAnnotations ,它能做的可不止省掉findViewById,非常強大,具體可以去前面的官網鏈接看,有非常直觀的demo對比。這個開源框架的目的是使用註解,使Android開發更快,代碼更易維護。與之前同類的依賴注入框架RoboGuice相比,其優勢是在編譯時根據註解生成代碼,而不是在運行時通過反射依賴注入,從而避免了性能問題。

如果想知道通過註解生成代碼的原理,可以看我這篇翻譯的博客 [譯]註解處理Annotation Processing

16.03.01 修改,增加使用感受

過完年回來,我們組也在推行這個庫了,我在某一個頁面接入了,這是一些用到了註解的代碼,也只用了這個庫的一點特性,主要有綁定view,依賴注入,綁定點擊事件,注入回調等,還有更多的特性沒有體驗。而點擊事件的註解,公司同事還給加上了埋點的功能,又省去了手寫埋點的代碼。

總結感受就是:寫的爽,依賴注入,綁定views省去了重複的new,findviewbyid代碼,閱讀起來由於代碼量減少了,也更爽了。

我們老大一直奉行的理念是:不規範的東西要標準化,標準的東西要自動化。提倡自動化,提升工程師效率。我覺得是挺有道理的。在Android開發里,綁定View,綁定事件,控制項點擊埋點就是非常標準化的東西,用註解來簡化代碼,不僅在開發時能省去一點點工作量,我覺得更多的好處是閱讀起來更輕鬆了。

@RootContext
Context context;

@ViewById(R.id.grids_container)
ViewGroup container;

@ViewById(R.id.trip_hotel_keyword_unusual)
View unusualView;

@ViewById(R.id.trip_tv_error_hint)
TextView unusualTextView;

@ViewById(R.id.trip_iv_error_img)
ImageView unusualImageView;

@ViewById(R.id.trip_btn_refresh)
View unusualRefreshBtn;

@Bean(HotelKeywordSearchHistoryManager.class)
IHotelKeywordSearchHistoryManager historyManager;

@AfterInject
void initPresenter() {
presenter.setmDynamicGridViews(this);
mLp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, DensityPixel
.dip2px(context, 30));
mLp.topMargin = DensityPixel.dip2px(context, 15);
}

@Click(tagName = "Back", resName = "trip_hotel_keyword_btn_title_left")
void onBackClicked(View btn) {
fragment.popToBack();
}


現在有好多視圖注入庫(如ButterKnife, Roboguice)都使用註解避免枯躁的findViewById。使用註解和反射肯定會犧牲一些效率,但現在手機硬體性能都提高了,犧牲這點效率換掉代碼的優雅,我覺得完全值得。而且現在Android平台使用註解和反射的開源庫已經很多了。


人們只會看程序運行流不流暢,有沒有明顯的bug. 至於代碼是不是完美,有沒有好的設計模式,用戶不關心,產品經理不關心,只有中了毒愛上進的程序員自己關心。其實人生沒有那麼累,規定時間內完成項目,沒有bug,回家陪老婆孩子多好。 自己費心費力寫好的代碼,下一個程序員兩天就改沒了,何苦。


自從用了ButterKnife框架,我就再也不喜歡用findViewById(冗餘,醜陋)。ButterKnife處理響應事件簡單明了,提高了可讀性。


升級到android studio1.3.0,你會發現新大陸Data binding.MVVM你值得擁有


android studio自動生成ID插件


推薦使用Google出的Data Binding Library,它支持數據綁定,而且綁定的代碼自動生成,不使用反射,完全沒有性能的問題,大家可以看下用法:


數據類和事件處理類

public class User {
public final String firstName;
public final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
public class MyHandlers {
public void onClickButton(View view) { ... }
public void afterFirstNameChanged(Editable s) { ... }
}

Activity

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
User user = new User("Test", "User");
binding.setUser(user);
}

Layout

&
http://schemas.android.com/apk/res/android">
&
&
&
&

&
&
&

Android程序員技術等級標準?
為什麼Android的Void實現和JDK有區別?
小米的miui能否解決安卓的SD卡文件夾「碎片化」?
你遇到過哪些代碼優雅的安卓項目?
為什麼 Android 版手機 QQ 不遵循 Android Design?

TAG:Android開發 | Android | Android程序員 |