zoukankan      html  css  js  c++  java
  • 解锁Dagger2使用姿势(一)

    毫无疑问,Dagger2的 上手是有门槛的,有门槛是因为它里边的概念多,用起来复杂,可是一旦你学会了Dagger2的使用,你一定会爱不释手的。与ButterKnife和AndroidAnnotations不同,Dagger2是由Google开发和维护(之前是Squreup),在 性能上可以说是做到的极致(Dagger2在编译期间进行了依赖注入,完全去除了反射机制),Dagger2要解决的问题也和ButterKnife以及AndroidAnnotations不同,后者主要是解决控件的初始化,线程的切换等等,而Dagger2则类似于Java中的Spring框架,主要是为了解决应用程序在运行时的耦合问题,使用Dagger2可以帮助我们实现低耦合高聚合。OK,那么今天我主要是想通过几个小Demo带大家来学习一下Dagger2的使用。


    本文主要包括以下三方面内容:

    1.Dagger2的引入

    2.ViewPager加载网络图片,使用Dagger2实现解耦

    3.ViewPager+TabLayout+Fragment制作导航页,使用Dagger2实现解耦


    OK,那就开始吧!

    1.Dagger2的引入

    在项目中引入Dagger2需要修改两个地方的gradle文件,首先是project的gradle文件,修改成如下样子:

    dependencies {
            classpath 'com.android.tools.build:gradle:2.1.0'
            classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    
            // NOTE: Do not place your application dependencies here; they belong
            // in the individual module build.gradle files
        }

    然后修改module的gradle文件:

    apply plugin: 'com.neenbedankt.android-apt'
    dependencies {
        compile fileTree(include: ['*.jar'], dir: 'libs')
        testCompile 'junit:junit:4.12'
        compile 'com.android.support:appcompat-v7:24.1.1'
        compile 'com.google.dagger:dagger:2.6'
        apt 'com.google.dagger:dagger-compiler:2.6'
        compile 'com.squareup.picasso:picasso:2.5.2'
    }

    这里有几个地方需要说一下,首先Dagger2我们可以直接在jCenter中搜索到的,可以搜到之后直接添加,那么和Dagger2有关的是两个文件:

    compile 'com.google.dagger:dagger:2.6'
    apt 'com.google.dagger:dagger-compiler:2.6'

    但是这两个一个用了compile一个用了apt,使用apt表示该引用类库只在编译的时候起作用,在打包的时候并不会打包到apk中去。


    2.ViewPager加载网络图片,使用Dagger2实现解耦

    正常情况下,我们使用ViewPager加载网络图片可能是这样写(这里我就贴出关键的ViewPagerAdapter,完整代码大家在文末自行下载):

    public class VpAdapter extends PagerAdapter {
        private Context context;
        private List<String> list;
    
        
        public VpAdapter(Context context, List<String> list) {
            this.context = context;
            this.list = list;
        }
    
        @Override
        public int getCount() {
            return list.size();
        }
    
        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view==object;
        }
    
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            ImageView iv = new ImageView(context);
            iv.setScaleType(ImageView.ScaleType.CENTER_CROP);
            Picasso.with(context).load(list.get(position)).into(iv);
            container.addView(iv);
            return iv;
        }
    
        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            container.removeView((View) object);
        }
    }

    然后在Activity中来初始化Adapter,并设置给ViewPager:

    VpAdapter adapter = new VpAdapter(this,list);
            viewPager.setAdapter(adapter);

    OK,这样我们的ViewPager就能加载出网络图片了,可是这种写法耦合性太强,我们需要进行适当的解耦,解耦,当然是使用Dagger2进行解耦了,OK,使用了Dagger2之后,我们先来看看怎么改造ViewPagerAdapter,如下:

    public class VpAdapter extends PagerAdapter {
    	
    	....
    	....
    	
        @Inject
        public VpAdapter(Context context, List<String> list) {
            this.context = context;
            this.list = list;
        }
    	
    	....
    	....
    }

    首先我在VpAdapter的构造方法上添加了一个@Inject注解,这是我们接触到Dagger2中的第一个注解,那它有什么含义呢?@Inject注解有两个不同含义和用法,第一种就是标记在构造方法上,一会在Activity中当系统需要对VpAdater的实例进行注入的时候,会自动调用具有@Inject注解的构造方法;第二种用法就是标记在需要依赖的变量上,以让Dagger2为其提供依赖。OK,改造完VpAdapter之后,我们再来看看如何改造MainActivity,首先,当我再需要获取一个VpAdapter实例的时候我已经不需要new了:

        @Inject
        VpAdapter adapter;

    声明一个变量,该变量具有@Inject注解(注意,这是@Inject注解的第二种用法),这个表示该变量需要依赖,需要由Dagger2为其提供依赖。可是仅仅这样就能实现VpAdapter变量的初始化吗?肯定是不可以。一个现实的问题是VpAdapter在实例化的时候需要传递的参数怎么传?这时,我们就得引出Dagger2中的另外一个注解了,@Module,使用了@Module注解的类专门提供依赖,谁需要依赖,都可以从这里获取。OK,那我们来看看我们这里提供依赖的类:

    @Module
    public class AppModule {
        private Context context;
    
        public AppModule(Context context) {
            this.context = context;
        }
    
        @Provides
        Context providesContext() {
            return context;
        }
    
        @Provides
        List<String> providesImageUrlList() {
            List<String> list = new ArrayList<>();
            list.add("http://img4.cache.netease.com/photo/0001/2016-08-13/900x600_BUBDM7GI00AO0001.jpg");
            list.add("http://img4.cache.netease.com/photo/0001/2016-08-13/900x600_BUBDM7JI00AO0001.jpg");
            list.add("http://img3.cache.netease.com/photo/0001/2016-08-13/900x600_BUBDM85900AO0001.jpg");
            list.add("http://img3.cache.netease.com/photo/0001/2016-08-13/900x600_BUBDM8F500AO0001.jpg");
            list.add("http://i0.sinaimg.cn/dy/slidenews/76_img/2016_32/76522_1882718_992616.jpg");
            return list;
        }
    }

    我在VpAdapter构造方法初始化的时候需要传递两个参数,一个是上下文,还有一个是图片地址的集合,这两个参数我都在这个Module中来提供,@Module注解上文已经说过了,这里还有一个@Provides注解,该注解是专门用来注解方法的,并且该注解只可以在@Module中使用,使用了@Provides注解的方法,在需要提供依赖的时候被调用(这里就是当系统初始化VpAdapter的时候调用)。OK,仅仅这样还不够,我们还需要有一个东西能够将@Module和@Inject连接起来,这个东西叫做@Compoent,也可以称作为一个注入器。所有的注入器都要以接口的形式来定义,接口中添加注入的方法,一般情况下我们使用inject作为方法名,方法的参数就是我们要注入的容器。Dagger2会帮我们生成一个名为DaggerXXXX的@Component的实现类。OK,我们来看看本案例的注入器:

    @Component(modules = AppModule.class)
    public interface AppComponent {
        void inject(MainActivity activity);
    }

    modules参数表示我们要注入的@Module类,也可以是多个@Module类,注意inject方法的参数为MainActivity,这里不可以写作MainActivity的父类Activity,因为这里写啥,Dagger就回去对应的类中寻找@Inject注解进行注入,很明显我们需要注入的变量都在MainActivity中,而不是在Activity中。

    注入器也有了,那我们最后再来看看怎样在MainActivity中进行注入吧:

        @Inject
        VpAdapter adapter;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            DaggerAppComponent.builder().appModule(new AppModule(this)).build().inject(this);
            ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
            viewPager.setAdapter(adapter);
        }

    有没有发现代码一下变得非常简洁。注意DaggerAppComponent类是根据我们的AppComponent类自动生成的,appModule方法中传入提供依赖的Module,而inject方法则表示调用@Component的实现类将Module提供的实例注入的VpAdapter的构造方法中。这样写了之后我们的工程就算完成了。

    运行效果如下:

    OK,我们再来总结一下我们这里所接触到的几个注解:


    1. @Inject(两个作用,一个是标记在构造方法上让Dagger来使用,另一个是标记在需要依赖的变量上让Dagger2为其提供依赖)

    2. @Provides(注解方法,该注解只可以在@Module中使用,使用了该注解的方法在需要提供依赖时被调用)

    3. @Module(用@Module注解的类是专门用来提供依赖)

    4. @Component(一个接口,@Inject和@Module之间的桥梁,也称作注入器)


    3.ViewPager+TabLayout+Fragment制作导航页,使用Dagger2实现解耦

    上面那个例子是不是觉得还不过瘾?那我们再来看一个稍微复杂一点的案例:

    先来看看运行效果:

    上面滚动的东西是一个TabLayout(不懂TabLayout的小伙伴可以参考使用TabLayout快速实现一个导航栏),下面是ViewPager,ViewPager中放的是Fragment。

    先来看看布局文件:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:orientation="vertical"
        tools:context="org.lenve.dagger2vpfragment.MainActivity">
    
        <android.support.design.widget.TabLayout
            android:id="@+id/tab_layout"
            app:tabMode="scrollable"
            android:layout_width="match_parent"
            android:layout_height="48dp"></android.support.design.widget.TabLayout>
    
        <android.support.v4.view.ViewPager
            android:id="@+id/viewpager"
            android:layout_width="match_parent"
            android:layout_height="match_parent"></android.support.v4.view.ViewPager>
    </LinearLayout>

    再来看看Fragment的适配器:

    public class MyAdapter extends FragmentPagerAdapter {
        private List<String> titles;
        private List<Fragment> fragments;
    
        @Inject
        public MyAdapter(FragmentManager fm, List<String> titles, List<Fragment> fragments) {
            super(fm);
            this.titles = titles;
            this.fragments = fragments;
        }
    
        @Override
        public Fragment getItem(int position) {
            return fragments.get(position);
        }
    
        @Override
        public int getCount() {
            return fragments.size();
        }
    
        @Override
        public CharSequence getPageTitle(int position) {
            return titles.get(position);
        }
    }

    注意该适配器的构造方法有三个参数,所以我一会需要在Module中提供至少三个对应的方法,来为这个构造方法进行注入,那么Module是什么样子呢?

    @Module
    public class AppModule {
        private AppCompatActivity appCompatActivity;
    
        public AppModule(AppCompatActivity appCompatActivity) {
            this.appCompatActivity = appCompatActivity;
        }
        @Provides
        FragmentManager providesFragmentManager() {
            return appCompatActivity.getSupportFragmentManager();
        }
        @Provides
        List<String> providesTitles() {
            List<String> list = new ArrayList<>();
            for (int i = 0; i < 9; i++) {
                list.add("张三:" + i);
            }
            return list;
        }
        @Provides
        List<Fragment> providesFragmentList(List<String> titles) {
            List<Fragment> fragments = new ArrayList<>();
            for (String title : titles) {
                fragments.add(BaseFragment.getInstance(title));
            }
            return fragments;
        }
    }

    三个方法分别返回Adapter需要的三个参数。再来看看AppComponent:

    @Component(modules = AppModule.class)
    public interface AppComponent {
        void inject(MainActivity activity);
    }
    OK,万事俱备,只差最后在MainActivity进行注入了:

    public class MainActivity extends AppCompatActivity {
    
        private ViewPager viewPager;
        private TabLayout tabLayout;
        @Inject
        MyAdapter adapter;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            DaggerAppComponent.builder().appModule(new AppModule(this)).build().inject(this);
            tabLayout = (TabLayout) findViewById(R.id.tab_layout);
            viewPager = (ViewPager) findViewById(R.id.viewpager);
            viewPager.setAdapter(adapter);
            tabLayout.setupWithViewPager(viewPager);
        }
    }

    OK,这个Demo和我们第一Demo有点类似,所以我没有做过多解释,完整的Project在文末下载。


    Dagger2其他常用注解我会在下一篇文章中介绍。


    以上。


    Demo 下载http://download.csdn.net/detail/u012702547/9603154




  • 相关阅读:
    PHP foreach 循环
    C#导出Excel时间格式问题
    vs2015 key
    C# 的Chart
    线程暂停与继续实现
    CCNA网络工程师学习进程(2)基本的网络设备
    CCNA网络工程师学习进程(1)网络的基本概述
    安卓学习进程(3)安卓开发工具的简介
    安卓学习进程(2)Android开发环境的搭建
    安卓学习进程(1)移动平台开发的简介
  • 原文地址:https://www.cnblogs.com/qitian1/p/6461678.html
Copyright © 2011-2022 走看看