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阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容