ARouter解析五:IoC與依賴注入

終于來到了ARouter解析的第五篇了,前面陸陸續續分享了四篇ARouter框架的使用和源碼內容:

ARouter解析一:基本使用及頁面注冊源碼解析
ARouter解析二:頁面跳轉源碼分析
ARouter解析三:URL跳轉本地頁面源碼分析
ARouter解析四:發現服務和Fragment

這次分享下IoC思想和ARouter的自動注入這塊內容。IoC是控制反轉的意思,這個在后端開發中用的會比較多,也是Spring框架的核心。 控制反轉一般分為兩種類型,依賴注入(Dependency Injection,簡稱DI)和依賴查找(Dependency Lookup)。在Android開發中一般自己實現倒比較少,但并不是說不常用,很多大名鼎鼎的框架都用的這種思想,比如ButterKnife,Dagger等。我們今天先分享下IoC主要的兩種實現方式,再分析下ARouter在這方面的相關實踐。

這次分享會按照下面的步驟進行:

1.常用的注入方式

2.依賴注入和依賴查找

3.ARouter注入源碼分析

IoC這塊涉及到的內容會比較多,比如注解,反射,動態代理,我們盡量用簡單的語言描述,我之前的博客也有分享到這些內容,不明白的小伙伴可自行參考。

1.常用的注入方式

在開發過程中,肯定會涉及到類之間的依賴,一般都是直接new出來,比如:

class Hello{
    public Hello(){

    }
    
    public void sayHello(){
        
    }
}

public class Human {
    public static void main(String[]args) {
        Hello hello = new Hello();
        hello.sayHello();
    }
}

這里的hello實例就是用戶自己new出來的,這時控制權還是在用戶手中,這有幾個缺點:互相依賴, 用戶需要管理hello的聲明周期。

所以,更進一步的注入方式有構造函數或者set方法,比如下面代碼。

public class Human {
    private Hello hello;

    public Human(Hello hello) {
        this.hello = hello;
    }

    public void setHello(Hello hello) {
        this.hello = hello;
    }
}

這會就比上面代碼更靈活點,也減少了依賴,但是還是需要用戶去調用方法設置實例。有木有更好的辦法呢?可能你也想到了注解的辦法,沒錯,我們接著往下看。

2.依賴注入和依賴查找

依賴注入和依賴查找有什么區別呢?我們平時用的依賴注入會比較多一點,依賴注入在Android上大部分是通過自定義注解來實現,其實嚴格說起來依賴注入也是通過依賴查找來實現的,依賴注入用起來會比依賴查找方便。我們先來看一個簡單的栗子,再看看ARouter的實踐。
先來看下依賴查找,其實我們上一期分享的ARouter解析四:發現服務和Fragment就是通過依賴查找來發現服務和Fragment的。看下面的栗子,helloService是通過ARouter框架來創建的,比上面直接new構造會方便很多,但是還是用戶來找到這個類的實例。

interface HelloService{
    void sayHello();
}

@Route(path = "/service/hello")
class HelloServiceImpl implements HelloService {
    Context mContext;

    @Override
    public void sayHello() {
        Toast.makeText(mContext, "Hello ", Toast.LENGTH_SHORT).show();
    }
}

public class Human {
    public static void main(String[]args) {
        HelloService helloService = ARouter.getInstance().navigation(HelloService.class);
        helloService.sayHello();
    }
}

那么上面的栗子用依賴注入該怎么實現?是不是更為方便了,不需要用戶再去給出實例的路徑,通過注解@Autowired就可以得到我們需要的實例。

public class Human {
    @Autowired
    private HelloService helloService;

    public static void main(String[]args) {
        ARouter.getInstance().inject(this);
        helloService.sayHello();
    }
}

上面的栗子是為了講解清楚簡單拼湊的,我們來看下ARouter官方Demo的栗子。點擊依賴注入,可以給Test1Activity傳遞對象參數

依賴注入1.png
依賴注入2.png

我們看下Test1Activity的代碼,可以看出來沒有new,沒有查找,只有一個注解@Autowired,和一行關鍵代碼ARouter.getInstance().inject(this);

@Route(path = "/test/activity1")
public class Test1Activity extends AppCompatActivity {

    @Autowired
    String name;

    @Autowired
    int age;

    @Autowired(name = "boy")
    boolean girl;

    @Autowired
    TestParcelable pac;

    @Autowired
    TestObj obj;

    private long high;

    @Autowired
    String url;

    @Autowired
    HelloService helloService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test1);

        ARouter.getInstance().inject(this);

        String params = String.format(
                "name=%s,\n age=%s,\n girl=%s,\n high=%s,\n url=%s,\n pac=%s,\n obj=%s",
                name,
                age,
                girl,
                high,
                url,
                pac,
                obj
        );
        helloService.sayHello("Hello moto.");

        ((TextView)findViewById(R.id.test)).setText("I am " + Test1Activity.class.getName());
        ((TextView)findViewById(R.id.test2)).setText(params);
    }
}

那么源碼是怎么做到的呢?我們接著往下看。

3.ARouter注入源碼分析

上面其實就是一行關鍵代碼ARouter.getInstance().inject(this);,所以我們跟進去看看,來到ARouter的代理類_ARouter中,首先通過以來查找獲取AutowiredService的具體實現類,然后得到實例,這個不清楚的可以看下上篇分享,ARouter解析四:發現服務和Fragment。這里就不再解釋了。

static void inject(Object thiz) {
        AutowiredService autowiredService = ((AutowiredService) ARouter.getInstance().build("/arouter/service/autowired").navigation());
        if (null != autowiredService) {
            autowiredService.autowire(thiz);
        }
}

我們接著看下AutowiredService,這里會先在混存中查找是否有Test1Activity的輔助注入類,這里剛開始肯定是沒有的,所以需要去加載。輔助注入類就是Test1Activity$$ARouter$$Autowired,不用說,這個就是編譯期間生成的。

@Route(path = "/arouter/service/autowired")
public class AutowiredServiceImpl implements AutowiredService {
    private LruCache<String, ISyringe> classCache;
    private List<String> blackList;

    @Override
    public void init(Context context) {
        classCache = new LruCache<>(66);
        blackList = new ArrayList<>();
    }

    @Override
    public void autowire(Object instance) {
        String className = instance.getClass().getName();
        try {
            if (!blackList.contains(className)) {
                ISyringe autowiredHelper = classCache.get(className);
                if (null == autowiredHelper) {  // No cache.
                    autowiredHelper = (ISyringe) Class.forName(instance.getClass().getName() + SUFFIX_AUTOWIRED).getConstructor().newInstance();
                }
                autowiredHelper.inject(instance);
                classCache.put(className, autowiredHelper);
            }
        } catch (Exception ex) {
            blackList.add(className);    // This instance need not autowired.
        }
    }
}

通過反射得到輔助注入類的實例后就調用inject方法注入實例。我們來看下這個類的代碼。到這里就水落石出了,在跳轉時將需要傳遞的參數寫入postcard的bundle中,然后成功跳轉到目標頁面后就可以通過getIntent取出參數,然后分別給目標頁面的每個需要注入的成員變量賦值。

public class Test1Activity$$ARouter$$Autowired implements ISyringe {
    private SerializationService serializationService;

    @Override
    public void inject(Object target) {
        serializationService = ARouter.getInstance().navigation(SerializationService.class);
        ;
        Test1Activity substitute = (Test1Activity) target;
        substitute.name = substitute.getIntent().getStringExtra("name");
        substitute.age = substitute.getIntent().getIntExtra("age", 0);
        substitute.girl = substitute.getIntent().getBooleanExtra("boy", false);
        substitute.pac = substitute.getIntent().getParcelableExtra("pac");
        if (null != serializationService) {
            substitute.obj = serializationService.json2Object(substitute.getIntent().getStringExtra("obj"), TestObj.class);
        } else {
            Log.e("ARouter::", "You want automatic inject the field 'obj' in class 'Test1Activity' , then you should implement 'SerializationService' to support object auto inject!");
        }
        substitute.url = substitute.getIntent().getStringExtra("url");
        substitute.helloService = ARouter.getInstance().navigation(HelloService.class);
    }
}

我們再總結下整個注入的過程:

依賴注入.png

4.總結

今天我們分享了IoC的設計思想,也通過栗子說明了依賴注入和依賴查找,從上面的分析可以看出ARouter的依賴注入是在運行時生成輔助類,在運行時通過反射實例化輔助類并完成跳轉后參數的自動注入。這里面涉及到的技術還是比較多的,比如反射,注解,APT技術,IoC,依賴注入和依賴查找,代理,算是比較綜合的應用了,這些技術的我在之前的博客中都有分享過,小伙伴們可自行參考。

慣例,感謝@右傾傾的支持與理解!

后面會分享ARouter的攔截器相關的內容,感興趣的小伙伴歡迎關注。希望我的分享能對大家有點幫助,謝謝!

歡迎關注公眾號:JueCode

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容