Android實現模組 api 化

我與bug不得不說的故事發表於2019-04-15

最近看了微信Android模組化架構重構實踐這篇文章,剛好自己又正在搭建新專案的框架,用到元件化開發;感覺文章裡面的一些技巧很有用,就跟著實現了一下,寫一下自己的看法

模組間的互動

首先是解決模組之前的依賴問題,模組間肯定是不能相互依賴的,那如何進行資料互動就是一個問題了;比如使用者模組和其他模組,其他模組如何在不依賴使用者模組的情況下獲取到使用者資訊;

使用EventBus

想要獲取使用者資訊,那User類肯定是要引用的,肯定是要提取出User類放到公共模組裡面,然後獲取User可以通過EventBus來獲取資料

公共模組將EventBus傳送的Event定義為介面

public interface UserCallback {

    /**
     * 獲取使用者資料
     *
     * @param user
     */
    void getUser(User user);
}
複製程式碼

然後在使用者模組訂閱事件,返回使用者資訊

    @Subscribe
    public void getUser(UserCallback callback){
        callback.getUser(new com.dhht.baselibrary.User());
    }
複製程式碼

在其他模組就可以通過EventBus來傳送事件獲取到使用者資訊

EventBus.getDefault().post(new UserCallback() {
    @Override
    public void getUser(User user) {
        mUser = user;
    }
});
複製程式碼

但是講道理EventBus還是少用的好,業務多了會生成很多Event類,感覺是有點難受的,而且程式碼閱讀起來非常難;

SPI機制

SPI全稱Service Provider Interface,是Java提供的一套用來被第三方實現或者擴充套件的API,它可以用來啟用框架擴充套件和替換元件。

整體機制圖如下:

Picture
Picture

具體的實現(可以略過)

首先也是把User放在公共模組裡面,獲取使用者資訊的介面也放在公共模組裡面

package com.dhht.baselibrary;
public interface UserService {
    /**
     * 獲取user
     *
     * @return
     */
    User getUser();
}
複製程式碼

然後在使用者模組裡面實現介面

package com.dhht.user;

public class UserImpl implements UserService {
    @Override
    public User getUser() {
        return new User("UserImpl");
    }
}
複製程式碼

需要在user/src/main/resources/META-INF.services/目錄下面新建檔名為com.dhht.baselibrary.UserService的檔案,檔案內容就是實現類的路徑

com.dhht.user.UserImpl
複製程式碼

這個時候再其他模組使用這個實現類就可以通過SPI機制來獲取

        ServiceLoader<UserService> userServices = ServiceLoader.load(UserService.class);
        Iterator<UserService> iterator = userServices.iterator();
        while (iterator.hasNext()) {
            UserService userService = iterator.next();
            ToastUtil.showShort(userService.getUser().getName());
        }
複製程式碼

不懂的可以加群問下博主:點選連結加入群聊【Android技術交流】:jq.qq.com/?_wv=1027&a… ##ARouter 上面的過程稍微有點複雜,也沒必要去實現,這個是一種思想,很多路由框架都是藉助了這種思想,而且使用非常方便,比如阿里的ARouter框架;使用者類不變,介面需要實現IProvider介面

public interface UserService extends IProvider {
    UserInfo getUser();
}
複製程式碼

然後在使用者模組實現介面,並且新增@Route註解

@Route(path = "/user/UserService")
public class UserServiceImpl implements UserService {
    @Override
    public UserInfo getUser() {
        return new UserInfo("Tyhj");
    }

    @Override
    public void init(Context context) {

    }
}
複製程式碼

然後在其他模組通過ARouter註解獲取例項

    @Autowired//(name = "/user/UserService")
    UserService mUserService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ARouter.getInstance().inject(this);
        ...
複製程式碼

方法比較簡單,相對於正常的程式碼只是新增了一個註解而已,ARouter的最新版本如下,每個模組都需要新增註解外掛(第二行),庫(第一行)只需要在公共模組新增就好了;

//arouter
api 'com.alibaba:arouter-api:1.4.1'
annotationProcessor 'com.alibaba:arouter-compiler:1.2.2'
複製程式碼

使用ARouter還需要在每個模組的build.gradle的defaultConfig節點下新增如下程式碼

 javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
複製程式碼

##提取出api模組 如果每次有一個模組要使用另一個模組的介面都把介面和相關檔案放到公共模組裡面,那麼公共模組會越來越大,而且每個模組都依賴了公共模組,都依賴了一大堆可能不需要的東西;

所以我們可以提取出每個模組提供api的檔案放到各種單獨的模組裡面;比如user模組,我們把公共模組裡面的User和UserInfoService放到新的user-api模組裡面,這樣其他模組使用的時候可以單獨依賴於這個專門提供介面的模組,以此解決公共模組膨脹的問題

##自動生成Library 為了寫程式碼方便,我們可以在寫程式碼的時候,每個模組的東西都寫在一起,比如User提供的介面我們也正常寫在使用者模組裡面,在編譯的時候,再使用gradle來自動生成各個api模組,這樣會方便很多

原理是這樣的,我們把需要單獨生成api模組的.java檔案改為另一種檔案型別比如把UserInfo.java改為UserInfo.api,在設定/Editor/File Type中找到Java型別,新增*.api,然後就可以和Java檔案一樣使用了;

在專案的setting.gradle檔案裡面新增方法includeWithApi("module名字"),用這個方法來代替include ":module名字",這個方法會從這個module裡面找到以.api結尾的檔案,複製到新的module裡面並重新命名,當然也會複製gradle檔案和AndroidManifest檔案,以此生成新的api模組

##具體實現 setting.gradle檔案的實現

def includeWithApi(String moduleName) {
    //先正常載入這個模組
    include(moduleName)
    //找到這個模組的路徑
    String originDir = project(moduleName).projectDir
    //這個是新的路徑
    String targetDir = "${originDir}-api"
    //新模組的名字
    def sdkName = "${project(moduleName).name}-api"
    
    //這個是公共模組的位置,我放了一個 新建的api.gradle 檔案進去
    String apiGradle = project(":commonlibrary").projectDir

    // 每次編譯刪除之前的檔案
    deleteDir(targetDir)

    //複製.api檔案到新的路徑
    copy() {
        from originDir
        into targetDir
        exclude '**/build/'
        exclude '**/res/'
        include '**/*.api'
    }

    //複製 gradle和AndroidManifest檔案到新的路徑,作為該模組的檔案
    copy() {
        from apiGradle
        into targetDir
        include 'api.gradle'
        include '**/AndroidManifest.xml'
    }

    //重新命名一下gradle
    def build = new File(targetDir + "/api.gradle")
    if (build.exists()) {
        build.renameTo(new File(targetDir + "/build.gradle"))
    }

    // 重新命名.api檔案,生成正常的.java檔案
    renameApiFiles(targetDir, '.api', '.java')

    //正常載入新的模組
    include ":$sdkName"
}


private void deleteDir(String targetDir) {
    FileTree targetFiles = fileTree(targetDir)
    targetFiles.exclude "*.iml"
    targetFiles.each { File file ->
        file.delete()
    }
}

/**
 * rename api files(java, kotlin...)
 */
private def renameApiFiles(root_dir, String suffix, String replace) {
    FileTree files = fileTree(root_dir).include("**/*$suffix")
    files.each {
        File file ->
            file.renameTo(new File(file.absolutePath.replace(suffix, replace)))
    }
}

//這些還是之前的寫法
include ':app', ':commonlibrary', ':expresscheck', ':main'
//需要生成新的模組的使用這個方法
includeWithApi(":user")
複製程式碼

其實講的還是比較清楚了,我首先複製.api檔案去生成Java檔案,想要生成新的api模組,得有gradle和AndroidManifest檔案才行,而這個api模組顯然不需要過多的配置,於是我自己先生成一個簡單的gradle檔案,就是其他模組複製過來的,基礎配置而已,然後複製到新的api模組搞定,對於AndroidManifest檔案,基礎模組肯定是沒有什麼配置的,複製過來使用完事兒;其實複製過來的AndroidManifest裡面的package路徑不對,沒關係反正這個檔案是用不到的;

####我這裡建立的是Android Library,其實建立Java Library也是一樣的,只是我感覺Android Library更好一點;可能感覺稍微有點複雜,其實只需要編寫一個通用的setting.gradle檔案然後改改.java檔名而已,這個也是微信重構的一個技巧,我覺得還是挺好的

知識思維導圖.jpg

相關文章