Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Android的IoC(四)——Hilt实践(3) #35

Open
soapgu opened this issue May 4, 2021 · 0 comments
Open

Android的IoC(四)——Hilt实践(3) #35

soapgu opened this issue May 4, 2021 · 0 comments
Labels
Demo Demo IoC New feature or request 安卓 安卓

Comments

@soapgu
Copy link
Owner

soapgu commented May 4, 2021

  • 引言

光说不练假把式,经过前面两篇的学习。需要一个应用的实战用例来检验学习成果。正好一个公告项目有一部分代码块的结构不太理想,正好小试牛刀一把。

  • 用户需求

正好衔接深入RxJava之多维转换及平铺收敛这篇的子流程
图片
设计思路

  • 需要抽象提取发布流程

  • 发布流程分解一:获取发布源,源代表数据(视频文件,图片地址,浏览器地址都是源),获取是一个异步的过程

  • 发布流程分解二:获取发布界面,所有的界面最终都是Fragment

  • 当然这数据面向对象的常规设计,并不是一定要用Hilt不可。如果不用框架使用反射或者自己写工厂方法,一样可以达到重构优化代码的目的。Hilt是我们的好帮手,并不神话它。

  • 公告接口及三种实现类

public interface INotice {

    Fragment setFragmentConnect(String connect);

    Single<String> getConnectUrl(DeviceDetial device);
}

抽象公告接口,定义两个方法分别是

  • 获取相关公告的UI(Fragment )
  • 获取公告发布源
public class VideoNotice implements INotice {

    private DownloadService downloadService;

    public VideoNotice( DownloadService downloadService) {
        this.downloadService = downloadService;
    }

    @Override
    public Fragment setFragmentConnect(String connect) {
        File file = new File(connect);
        if (file.exists()) {
            return VideoViewFragment.newInstance(file.getPath(), null);
        }
        return null;
    }

    @Override
    public Single<String> getConnectUrl(DeviceDetial device) {
        String video = device.getAttributes().getVideo();
        File root = new File(Environment.getExternalStorageDirectory(), "Space365/media");
        String filePath = root + File.separator + video.substring(video.lastIndexOf("/") + 1);
        File file = new File(filePath);
        if( file.exists() )
            return Single.just(filePath);
        else {
            return downloadService.downloadVideo(video, filePath)
                    .flatMap( success -> {
                        Logger.i( "download result:%s",success );
                        if(success){
                            return Single.just(filePath);
                        }
                        return Single.error( new Exception("download error") );
                    } );
        }
    }
}

视频公告实现,源需要下载相关文件,完成后再返回结果

public class PictureNotice implements INotice {
    @Override
    public Fragment setFragmentConnect(String connect) {
        return PictureFragment.newInstance(connect);
    }

    @Override
    public Single<String> getConnectUrl(DeviceDetial device) {
        String picture = device.getAttributes().getPictures();
        String pic = picture.split(",")[0];
        String baseUrl = App.context().getResources().getString(R.string.baseUrl);
        return Single.just( String.format("%s/api/v1%s", baseUrl, pic) );
    }
}

图片公告是最简单的实现,虽然获取发布源是非耗时任务,但是为了统一接口只能委屈一下了

public class WebNotice implements INotice {

    DownloadService downloadService;

    public WebNotice( DownloadService downloadService ) {
        this.downloadService = downloadService;
    }

    @Override
    public Fragment setFragmentConnect(String connect) {
        String url = App.context().getResources().getString(R.string.baseUrl) + "/app/roompad/?" + connect;
        return WebFragment.newInstance(url);
    }

    @Override
    public Single<String> getConnectUrl(DeviceDetial device) {
        if (device.getSpaces() != null && !device.getSpaces().isEmpty()) {
            GeneralEntity space = device.getSpaces().get(0);
            return downloadService.GetBuildingCode(device.getCompany().getId(), space.getCode(), space.getId())
                    .map( code -> String.format( "code=%s&company=%s",code,device.getCompany().getId() ));
        }
        return null;
    }
}

浏览器公告获取发布源需要获取楼的编码耗时任务

  • Hilt 的 Module

@InstallIn(SingletonComponent.class)
@Module
public class NetworkModule {
    @Singleton
    @Provides
    public DownloadService provideRetrofit() {
        String baseUrl = App.context().getResources().getString(R.string.baseUrl);
        Api api = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
                .build()
                .create(Api.class);
        return new DownloadService(api);
    }
}

定义一个Singleton的Provide,提供DownloadService 。作为单例全局实现

敲黑板重要知识点:

  • 耦合的耦合要怎么实现?这个世界是复杂的,不存在纯粹的耦合提供方,它同时它也需要别人提供耦合,打个比方我是发送机的厂商,但是我的钢材也非自供,我也一样有供应商。在这个例子中,WebNotice和VideoNotice 就需要DownloadService 的耦合。

  • 耦合的耦合可以通过Components统一供货,怎么把耦合的需求暴露出来那,可以通过构造函数,通过Components帮助导入耦合

  • 曾经遭遇挫折,WebNotice和VideoNotice声明@Inject来求注入,结果失败(小声BB~~~)

@Module
@InstallIn(SingletonComponent.class)
public class NoticeModule {
    @Provides
    @IntoMap
    @StringKey("video")
    public static INotice provideVideoNotice(DownloadService downloadService){
        return new VideoNotice(downloadService);
    }

    @Provides
    @IntoMap
    @StringKey("picture")
    public static INotice providePictureNotice(){
        return new PictureNotice();
    }

    @Provides
    @IntoMap
    @StringKey("web")
    public static INotice provideWebNotice(DownloadService downloadService){
        return new WebNotice(downloadService);
    }
}

最关键的NoticeModule

  • 分别提供"video","picture","web"作为关键字注入INotice 字典

  • provideVideoNotice,provideWebNotice声明了DownloadService 参数,最终通过构造参数传入,参数就是耦合的耦合的组装方式,只要列出清单就行,会有圣诞老人(组件)帮忙实现的。

  • 依赖注入端实现

@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {
    @Inject
    DownloadService downloadService;

    @Inject
    Map<String, INotice> noticeMap;
    .....
   private void ReloadDisplay(DeviceDetial device) {
        Logger.d("Device--:%s", device.getAttributes());
        Logger.d("Device Space--:%s", device.getSpaces().get(0).getName());
        Logger.d("Device Company--:%s", device.getCompany().getName());
        if (device.getAttributes() == null) {
            Logger.d("GetAttributes NULL");
        }

        String picture = device.getAttributes().getPictures();
        String video = device.getAttributes().getVideo();

        String noticeKey;
        if (!video.isEmpty()) {
            noticeKey = "video";
            report();
        } else if (!picture.isEmpty()) {
            noticeKey = "picture";
            report();
        } else {
            noticeKey = "web";
        }
        INotice notice = noticeMap.get(noticeKey);
        assert notice != null;
        disposables.add( notice.getConnectUrl(device)
                .map(notice::setFragmentConnect)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(this::setFragment,
                           error-> Logger.e(error, "INotice error!" ) ) );
    }
}

篇幅有限,只截取最关键的逻辑。

  • 前面判断是否需要呈现刷新公告的逻辑略

  • 根据DeviceDetial的对象信息来设置noticeKey决定实例化INotice 接口

  • 最后使用Rx的链式魔法

    • 先调用getConnectUrl获取发布源
    • 再调用setFragmentConnect获取Fragment
    • 把观察现场切换到Fragment
    • 调用setFragment把Fragment加载到FrameLayout
  • 原来非主干逻辑整理到各个Notice实现类完成

  • 总结

Hilt确实是我们解耦的好助手,很多脏活累活我们都不用做了。使用轻巧方便,不用为了配合组件做特别多的事情。当然还有很多有价值的功能待进一步开发。

@soapgu soapgu added Demo Demo IoC New feature or request 安卓 安卓 labels May 4, 2021
@soapgu soapgu changed the title Android的IOC(四)——Hilt实践(3) Android的IoC(四)——Hilt实践(3) Jul 13, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Demo Demo IoC New feature or request 安卓 安卓
Projects
None yet
Development

No branches or pull requests

1 participant