Android主流IOC框架淺析

TextView mTextView;
mTextView=(TextView) findViewById(R.id.mTextView);
mTextView.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
// TODO Auto-generated method stub

   }
});

作為一名Android程序員,對于上面這種機械化的代碼你一定寫到想吐了,或許多數時候你只是copy ,paste,然后再改一改,完了你可能又會覺得這種代碼毫無營養,寫得實在沒勁。俗話說:“不會偷懶的程序員不是好程序員”,今天我們就來探討下如何偷懶。

到這里可能你已經知道我要說的是什么了,是的,我要說的就是Android中的IOC框架,這類框架中比較早的有:Afinal,Xutils,目前開發者中呼聲比較高的有Android Annotations,ButterKnife,Dagger,RoboGuice等。我在這里就簡單介紹下比較有代表性的Android Annotations和ButterKnife。

什么是IOC?

Inversion of Control,英文縮寫為IOC,字面翻譯:控制反轉。什么意思呢?就是一個類里面需要用到很多個成員變量,傳統的寫法,你要用這些成員變量,那么你就new 出來用唄!IOC的原則是:NO,我們不要new,這樣耦合度太高,你配置個xml文件,里面標明哪個類,里面用了哪些成員變量,等待加載這個類的時候,我幫你注入(new)進去;當然了,你又會覺得,寫個配置文件,臥槽,這多麻煩。于是乎,又出現了另一種方案,得你嫌配置文件麻煩,那用注解唄在你需要注入的成員變量上面加個注解,例如:@Inject,這樣就行了,你總不能說這么個單詞麻煩吧。當然了,有了配置文件和注解,那么怎么注入呢?其實就是把字符串類路徑變成類么,這個時候需要用到反射。

什么是反射?

首先JAVA語言并不是一種動態編程語言,而為了使語言更靈活,JAVA引入了反射機制。 JAVA反射機制是在運行狀態中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法;這種動態獲取的信息以及動態調用對象的方法的功能稱為java語言的反射機制。Java反射機制主要提供了以下功能: 在運行時判斷任意一個對象所屬的類;在運行時構造任意一個類的對象;在運行時判斷任意一個類所具有的成員變量和方法;在運行時調用任意一個對象的方法;生成動態代理。

什么是注解?

JAVA1.5之后引入的注解和反射,注解的實現依賴于反射。JAVA中的注解是一種繼承自接口java.lang.annotation.Annotation的特殊接口。那么接口怎么能夠設置屬性呢?簡單來說就是JAVA通過動態代理的方式為你生成了一個實現了接口Annotation的實例,然后對該代理實例的屬性賦值,這樣就可以在程序運行時(如果將注解設置為運行時可見的話)通過反射獲取到注解的配置信息。說的通俗一點,注解相當于一種標記,在程序中加了注解就等于為程序打上了某種標記。程序可以利用JAVA的反射機制來了解你的類及各種元素上有無何種標記,針對不同的標記,就去做相應的事件。標記可以加在包,類,方法,方法的參數以及成員變量上。

以上算是背景介紹吧,也正是基于以上需求和實現原理,一大波Android IOC框架應運而生。我們先來看第一個:Android Annotations。我們先比較下常規寫法和Annotations寫法的代碼。

常規寫法

先隨便來一段吧~~

public class MainActivity extends Activity {
 TextView textView;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  textView = (TextView) findViewById(R.id.text);
  textView.setText("test");

  textView.setOnClickListener(new OnClickListener() {
   
   @Override
   public void onClick(View v) {
    // TODO Auto-generated method stub
    Intent intent = new Intent(this, ChildActivity.class);
    startActivity(intent);
     }
    });
  }
}

Android Annotations寫法

@EActivity(R.layout.activity_main)
public class MainActivity extends Activity {
 @ViewById(R.id.text)
 TextView textView;
 
 @AfterViews
 public void init() {
  textView.setText("annotations test");
 }

 @Click(R.id.text)
 void buttonClick() {
  Intent intent = new Intent(this,ChildActivity_.class);
  startActivity(intent);
}
}

就是這么簡單

Android Annotations就這么寫?可能你會問,oncreat()去哪了,這可是執行入口,你又會擔心setContentView()呢,布局怎么加載?看上面的代碼第一行@EActivity(R.layout.activity_main),就這一句全搞定,同時也不需要搞一大堆findViewById,不需要搞一大推setOnClickListener。Android Annotations能干的事可遠不止這些,Http請求,開啟線程,事件綁定等等都可以通過一個注解標記搞定。

其他語法舉例

色值 @ColorRes
  @ColorRes(R.color.backgroundColor)
  int someColor;

  @ColorRes
  int backgroundColor;
動畫 @AnimationRes
  @AnimationRes(R.anim.fadein)
  XmlResourceParser xmlResAnim;

  @AnimationRes
  Animation fadein;
自定義View @EViewGroup
@EViewGroup(R.layout.title_with_subtitle)
public class TitleWithSubtitle extends RelativeLayout {

    @ViewById
    protected TextView title, subtitle;

    public TitleWithSubtitle(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public void setTexts(String titleText, String subTitleText) {
        title.setText(titleText);
        subtitle.setText(subTitleText);
    }

}
HttpClient @HttpsClient
@HttpsClient
HttpClient httpsClient;
UiThread @UiThread
void myMethod() {
    doInUiThread("hello", 100);
}

@UiThread
void doInUiThread(String aParam, long anotherParam) {
    [...]
}

怎么實現的?

Android Annotations的實現原理其實很簡單。它會使用標準的Java注解處理工具自動添加一個額外的編譯步驟生成的源代碼。其實就是生成一個原有類的子類,這個子類才是真正運行用的類。例如上面代碼使用@EActivity注解的MainActivity,將生成這個MainActivity的一個子類,它的名字是“MainActivity_”。該子類重載一些方法(例如onCreate()),通過委托方式調用了activity的相關方法。所以這里有個大坑,所有Activity的相關操作都要操作其子類,例如 AndroidManifest.xml中類名要寫成android:name=".MainActivity_",開啟MainActivity要寫成 startActivity(new Intent(this, MainActivity_.class));下面是上文中AndroidAnnotations方式寫法的MainActivity的子類MainActivity_,我貼出來大家隨便感受下。

public final class MainActivity_
    extends MainActivity
    implements HasViews, OnViewChangedListener
{

    private final OnViewChangedNotifier onViewChangedNotifier_ = new OnViewChangedNotifier();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        OnViewChangedNotifier previousNotifier = OnViewChangedNotifier.replaceNotifier(onViewChangedNotifier_);
        init_(savedInstanceState);
        super.onCreate(savedInstanceState);
        OnViewChangedNotifier.replaceNotifier(previousNotifier);
        setContentView(layout.activity_main);
    }

    private void init_(Bundle savedInstanceState) {
        OnViewChangedNotifier.registerOnViewChangedListener(this);
    }

    @Override
    public void setContentView(int layoutResID) {
        super.setContentView(layoutResID);
        onViewChangedNotifier_.notifyViewChanged(this);
    }

    @Override
    public void setContentView(View view, LayoutParams params) {
        super.setContentView(view, params);
        onViewChangedNotifier_.notifyViewChanged(this);
    }

    @Override
    public void setContentView(View view) {
        super.setContentView(view);
        onViewChangedNotifier_.notifyViewChanged(this);
    }

    public static MainActivity_.IntentBuilder_ intent(Context context) {
        return new MainActivity_.IntentBuilder_(context);
    }

    public static MainActivity_.IntentBuilder_ intent(Fragment supportFragment) {
        return new MainActivity_.IntentBuilder_(supportFragment);
    }

    @Override
    public void onViewChanged(HasViews hasViews) {
        textView = ((TextView) hasViews.findViewById(id.text));
        if (textView!= null) {
            textView.setOnClickListener(new OnClickListener() {


                @Override
                public void onClick(View view) {
                    MainActivity_.this.buttonClick();
                }

            }
            );
        }
        init();
    }

    public static class IntentBuilder_
        extends ActivityIntentBuilder<MainActivity_.IntentBuilder_>
    {

        private Fragment fragmentSupport_;

        public IntentBuilder_(Context context) {
            super(context, MainActivity_.class);
        }

        public IntentBuilder_(Fragment fragment) {
            super(fragment.getActivity(), MainActivity_.class);
            fragmentSupport_ = fragment;
        }

        @Override
        public void startForResult(int requestCode) {
            if (fragmentSupport_!= null) {
                fragmentSupport_.startActivityForResult(intent, requestCode);
            } else {
                if (context instanceof Activity) {
                    Activity activity = ((Activity) context);
                    ActivityCompat.startActivityForResult(activity, intent, requestCode, lastOptions);
                } else {
                    context.startActivity(intent);
                }
            }
        }

    }

}

ButterKnife

再說說ButterKnife吧,其實原理和Android Annotations差不多,只是一些寫法和細節上有略微差別,最主要一點是沒有Android Annotations的坑,Android Studio上有個ButterKnife的插件,這個插件提供的炫酷技能幾乎是一鍵搞定findViewById和setOnClickListener,基于此ButterKnife被很多開發者所推崇,我們還是簡單對比下代碼吧。

常規寫法

public class MainActivity extends Activity {
    TextView textView;
    ListView listView;
    ListViewAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = (TextView) findViewById(R.id.text);
        listView = (ListView) findViewById(R.id.ListView);
        textView.setText("test");
        adapter = new ListViewAdapter(MainActivity.this);
        listView.setAdapter(adapter);
        textView.setOnClickListener(new OnClickListener() {
               
                @Override
                public void onClick(View v) {
                 text.setText("你點擊了按鈕");
          }
    });
}
       
    class ListViewAdapter extends BaseAdapter {

        private Context mContext;

        public ListViewAdapter(Context context) {
            mContext = context;
        }

        @Override
        public int getCount() {
            // TODO Auto-generated method stub
            return 1000;
        }

        @Override
        public Object getItem(int position) {
            // TODO Auto-generated method stub
            return position;
        }

        @Override
        public long getItemId(int position) {
            // TODO Auto-generated method stub
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder = null;
            if (convertView == null) {
                holder = new ViewHolder();
                LayoutInflater layoutInflater = ((Activity) mContext)
                        .getLayoutInflater();
                convertView = layoutInflater.inflate(R.layout.list_item,
                        parent, false);
                holder.imageview = (ImageView) convertView
                        .findViewById(R.id.headshow);
                holder.textview0 = (TextView) convertView
                        .findViewById(R.id.name);
                holder.textview1 = (TextView) convertView
                        .findViewById(R.id.text);
                convertView.setTag(convertView);
            } else {
                convertView = (View) convertView.getTag();
            }
            holder.textview0.setText("star");
            holder.textview1.setText("test");
            return convertView;
        }

    }

    static class ViewHolder {
        ImageView imageview;
        TextView textview0;
        TextView textview1;
    }
}

ButterKnife寫法

public class MainActivity extends Activity {

    @InjectView(R.id.text)
    TextView text;

    @InjectView(R.id.ListView)
    ListView listView;

    ListViewAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.inject(this);
        adapter = new ListViewAdapter(MainActivity.this);
        listView.setAdapter(adapter);
        text.setText("ButterKnife test");
    }

    @OnClick(R.id.text)
    void onClick() {
        text.setText("你點擊了按鈕");
    }

    class ListViewAdapter extends BaseAdapter {
        private Context mContext;

        public ListViewAdapter(Context context) {
            mContext = context;
        }

        @Override
        public int getCount() {
            // TODO Auto-generated method stub
            return 1000;
        }

        @Override
        public Object getItem(int position) {
            // TODO Auto-generated method stub
            return position;
        }

        @Override
        public long getItemId(int position) {
            // TODO Auto-generated method stub
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder = null;
            if (convertView == null) {
                LayoutInflater layoutInflater = ((Activity) mContext)
                        .getLayoutInflater();
                convertView = layoutInflater.inflate(
                        R.layout.list_item, parent, false);
                holder = new ViewHolder(convertView);
                convertView.setTag(convertView);
            } else {
                convertView = (View) convertView.getTag();
            }
            holder.textview0.setText("butterknife");
            holder.textview1.setText("test");
            return convertView;
        }

    }

    static class ViewHolder {
        @InjectView(R.id.headshow)
        ImageView imageview;
        @InjectView(R.id.name)
        TextView textview0;
        @InjectView(R.id.text)
        TextView textview1;
        public ViewHolder(View view) {
            ButterKnife.inject(this, view);
        }
    }
}

整體寫法與Android Annotations類似,實現原理上ButterKnife的實現也是在編譯的過程中生成了另外一個類來幫我們完成一些基本操作,以上面的代碼為例,生成了一個名為MainActivity$$ViewInjector的類,這里我就不再貼代碼了。但與Android Annotations所不同的是,我們在代碼中操作的還是MainActivity,而并不是MainActivity$$ViewInjector。

ButterKnife其他語法列舉
資源注入
class ExampleActivity extends Activity {
  @BindString(R.string.title) String title;
  @BindDrawable(R.drawable.graphic) Drawable graphic;
  @BindColor(R.color.red) int red; 
  @BindDimen(R.dimen.spacer) Float spacer; 
  // ...
}
Fragment注入
public class FancyFragment extends Fragment {
  @InjectView(R.id.button1) Button button1;
  @InjectView(R.id.button2) Button button2;
 
  @Override View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fancy_fragment, container, false);
    ButterKnife.inject(this, view);
    // TODO Use "injected" views...
    return view;
  }
}
回調函數注入
// 帶有 Button 參數
@OnClick(R.id.submit)
public void sayHi(Button button) {
  button.setText("Hello!");
}
 
// 不帶參數
@OnClick(R.id.submit)
public void submit() {
  // TODO submit data to server...
}
 
// 同時注入多個 View 事件
@OnClick({ R.id.door1, R.id.door2, R.id.door3 })
public void pickDoor(DoorView door) {
  if (door.hasPrizeBehind()) {
    Toast.makeText(this, "You win!", LENGTH_SHORT).show();
  } else {
    Toast.makeText(this, "Try again", LENGTH_SHORT).show();
  }
}

性能如何

關于IOC框架的基本寫法和實現原理,通過上面兩個例子,相信大家都已經有所了解。但是前面已經說了IOC的注解機制是依賴JAVA的反射,可能很多開發者都會嗤之以鼻:反射會影響性能。在早期的JAVA語言中反射是會帶來不小的性能消耗,而隨著語言自身的進步和完善,到了現在情況有所好轉。但我們移動設備的性能,不比后臺服務器擁有充足的內存和運算能力。當大量的使用注解的時候,會不會對APP造成什么不良的影響,會不會影響到APP的執行性能?在這里先明確的聲明,上述框架不會給APP帶來任何副作用,相反它強大易用的api能為你帶來前所未有的編程體驗。

上述框架的實現,都是通過使用jdk 1.6引入的Java Annotation Processing Tool,在編譯器中加了一層額外的自動編譯步驟,用來生成基于你源碼的代碼。運行期運行的其實就是這個二次編譯的代碼,實際上反射注解這一過程是在編譯期完成的,而并不會影響Runtime。所以上述IOC框架并不會影響到APP得性能。簡單的做了幾組測試,分別用Android Annotations,ButterKnife,以及常規寫法寫了同樣實現的代碼,然后打印代碼執行時間。每組大概跑200次,然后計算代碼執行的平均耗時,發現三組數據基本是在同一個水平上,并不能判斷孰優孰劣。

IDE集成方法

不管是Eclipse還是Android Studio都可以很方便的集成上述框架,而且資源包很小,具體方法大家網上找一找,我就不再列出具體步驟了。從網上偷了張ButterKnife的插件技能圖,大家隨便感受下。

20140122235713140.gif

既然此類框架如此炫酷高效,那么我們是否應該大肆推崇,廣泛采用呢。這個具體還是根據自己的項目實際情況來定吧,據我小范圍了解,目前部分大廠出品的部分應用并未采用此類框架,最后我們還是來看看此類框架的優缺點吧。

優點

1:提高開發效率
2:減少代碼量

缺點

1:代碼可讀性差
2:增加新人學習成本
3:加速觸及65535方法數問

你用或者不用,框架就在那里,不悲不喜!
(如有刊物,歡迎指正)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內容