您的位置:首页 > 移动开发 > Android开发

Android学习进阶和IoC

2016-07-21 09:48 489 查看
认识IOC-不用你找,我来提供给你

英文名:Inversion of Control

中文名:控制反转

简单解释:生命周期由框架控制

为什么是反转?

         框架在前,程序在后

         框架调用程序

         程序的生命周期由框架控制

         汇总-流程决定于framework


性能如何

关于IOC框架的基本写法和实现原理,通过上面两个例子,相信大家都已经有所了解。但是前面已经说了IOC的注解机制是依赖JAVA的反射,可能很多开发者都会嗤之以鼻:反射会影响性能。在早期的JAVA语言中反射是会带来不小的性能消耗,而随着语言自身的进步和完善,到了现在情况有所好转。但我们移动设备的性能,不比后台服务器拥有充足的内存和运算能力。当大量的使用注解的时候,会不会对APP造成什么不良的影响,会不会影响到APP的执行性能?在这里先明确的声明,上述框架不会给APP带来任何副作用,相反它强大易用的api能为你带来前所未有的编程体验。

上述框架的实现,都是通过使用jdk 1.6引入的Java Annotation Processing Tool,在编译器中加了一层额外的自动编译步骤,用来生成基于你源码的代码。运行期运行的其实就是这个二次编译的代码,实际上反射注解这一过程是在编译期完成的,而并不会影响Runtime。所以上述IOC框架并不会影响到APP得性能。简单的做了几组测试,分别用Android Annotations,ButterKnife,以及常规写法写了同样实现的代码,然后打印代码执行时间。每组大概跑200次,然后计算代码执行的平均耗时,发现三组数据基本是在同一个水平上,并不能判断孰优孰劣。


什么是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写法的代码。

1.成为Android高手一般分为六个阶段:

        第一阶段:熟练掌握Java SE,尤其是对其内部类、线程、并发、网络编程等需要深入研究;熟练掌握基于HTTP协议的编程,清楚POST和GET等请求方式流程和细节;能够进行基本的Java Web编程,如果能够使用Java EE则更好;

        第二阶段:精通Android的核心API的使用,例如四大组件所涉及的API、Context等,精通核心界面的编程,例如ListView的编程;到达这个阶段已经能够做大部分基本的应用开发了;

        第三阶段:精通应用框架的原理,尤其是对IoC的理解及其在Android应用框架中的应用,精通基本的23种设计模式在Android中的应用;

        第四阶段:精通JNI,熟练Android类库中C/C++组件开发;并能够使用JNI机制把现有的C/C++组件移植成为应用框架的核心组件;具备修改和编写自己的应用框架的能力;

        第五阶段:做出自己的Android系统,无论是底层还是上层都能够了如指掌;能够根据实际需要设计和实现比较大Android系统,例如带领比较大的团队做出自己的Android手机产品等;

        第六阶段:势。一切的思考均进入“势”的思考,一切都是思路问题,判断和预测Android的形势,引领市场,通往自由的天堂。

2. Android之IoC (Inversion of Control)

1)概念

        一种设计思想。使调用者和被调用者解耦和分离,便于更改和代码重用,便于移植。

        许多应用都是由多个类通过彼此合作来实现业务逻辑,每个对象之间都相互依赖,这将导致代码高度耦合并且难以测试、难以修改难以重用。

        IoC很好的解决了该问题,它将实现组件间关系从程序内部提到外部容器来管理。也就是说由容器在运行期将组件间的某种依赖关系动态的注入组件中。控制程序间关系的实现交给了外部容器来完成。

2)案例

      为了实现调用者和被调用者解耦,分离,一般是通过工厂模式实现的,下面将通过比较工厂模式和Ioc模式不同,加深理解Ioc模式。

      假设有两个类B 和 C:B作为调用者,C是被调用者。一般的做法是:

public class B{
   private C comp=new C();
  ......
}


      工厂模式实现如下:

public class B{
  private C comp;
  private final static MyFactory myFactory = MyFactory.getInstance();

  public B(){
    this.comp = myFactory.createInstanceOfC();

  }
   public void someMethod(){
    this.comp.sayHello();
  }
}


     使用Ioc依赖性注射实现Picocontainer如下:

public class B{
  private C comp;
  public B(C comp){
    this.comp = comp;
   }
   public void someMethod(){
    this.comp.sayHello();
   }
}


//外部容器实现,控制程序间的关系。CImp类为C接口/类的一个具体实现。

public class client{
   public static void main( String[] args ) {
    DefaultPicoContainer container = new DefaultPicoContainer();
    container.registerComponentImplementation(CImp.class); //向B类实现注射C类具体实现
    container.registerComponentImplementation(B.class);
    B b = (B) container.getComponentInstance(B.class);
    b.someMethod();
   }
}


3)工厂模式和Ioc不同的特点和区别:

  如上,主要区别体现在B类的代码,如果使用Ioc,在B类代码中将不需要嵌入任何工厂模式等的代码,因为这些工厂模式其实还是与C有些间接的联系,这样,使用Ioc彻底解耦了B和C之间的联系。

  使用Ioc带来的代价是:需要在客户端或其它某处进行B和C之间联系的组装。

  所以,Ioc并没有消除B和C之间这样的联系,只是转移了这种联系。

  总之,使用Ioc模式,可以不管将来具体实现,完全在一个抽象层次进行描述和技术架构。


Android主流IOC框架浅析

字数3212 阅读1714 评论9 喜欢22

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方法数问
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: