“终于懂了” 系列:Android组件化,全面掌握! _ 掘金技术征文-双节特别篇

//壳工程app module的build.gradle
dependencies {

//这里没有使用私有maven仓,而是发到JitPack仓,一样的意思~
// implementation ‘com.hfy.cart:cart:1.0.0’
implementation ‘com.github.hufeiyang:Cart:1.0.1’ //依赖购物车组件
implementation ‘com.github.hufeiyang:HomePage:1.0.2’ //依赖首页组件

//壳工程内 也需要依赖Common组件,因为需要初始化ARouter
implementation ‘com.github.hufeiyang:Common:1.0.0’
}

4.2.2 初始化

依赖完了,先要对ARouter初始化,需要在Application内完成:

public class MyApplication extends Application {

@Override
public void onCreate() {
super.onCreate();

// 这两行必须写在init之前,否则这些配置在init过程中将无效
if (BuildConfig.DEBUG) {
// 打印日志
ARouter.openLog();
// 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
ARouter.openDebug();
}
// 尽可能早,推荐在Application中初始化
ARouter.init(this);
}
}

4.2.3 路由跳转

好了,准备工作都完成了。并且知道 首页组件是没有依赖购物车组件的,下面就来实现前面提到的 首页组件 无依赖 跳转到 购物车组件页面

而使用ARouter进行简单路由跳转,只有两步:添加注解路径、通过路径路由跳转。

1、在支持路由的页面上添加注解@Route(path = “/xx/xx”),路径需要注意的是至少需要有两级,/xx/xx。这里就是购物车组件的CartActivity:

@Route(path = “/cart/cartActivity”)
public class CartActivity extends AppCompatActivity {

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

2、然后在首页组件的HomeActivity 发起路由操作—点击按钮跳转到购物车,调用ARouter.getInstance().build(“/xx/xx”).navigation()即可:

@Route(path = “/homepage/homeActivity”)
public class HomeActivity extends AppCompatActivity {

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

findViewById(R.id.btn_go_cart).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//通过路由跳转到 购物车组件的购物车页面(但没有依赖购物车组件)
ARouter.getInstance()
.build(“/cart/cartActivity”)
.withString(“key1”,“value1”)//携带参数1
.withString(“key2”,“value2”)//携带参数2
.navigation();
}
});
}
}

另外,注意在HomeActivity上添加了注解和路径,这是为了壳工程的启动页中直接打开首页。还看到路由跳转可以像startActivity一样待参数。

最后,壳工程的启动页中 通过路由打开首页(当然这里也可以用startActivity(),毕竟壳工程依赖了首页组件):

//启动页
public class SplashActivity extends AppCompatActivity {

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

//通过路由直接打开home组件的HomeActivity,
ARouter.getInstance().build(“/homepage/homeActivity”).navigation();
finish();
}
}

我们run壳工程 最后看下效果: 无依赖首页跳购物车 到这里,组件间页面跳转的问题也解决了。

五、组件间通信

组件间没有依赖,又如何进行通信呢?

例如,首页需要展示购物车中商品的数量,而查询购物车中商品数量 这个能力是购物车组件内部的,这咋办呢?

5.1 服务暴露组件

平时开发中 我们常用 接口 进行解耦,对接口的实现不用关心,避免接口调用与业务逻辑实现紧密关联。这里组件间的解耦也是相同的思路,仅依赖和调用服务接口,不会依赖接口的实现。

可能你会有疑问了:既然首页组件可以访问购物车组件接口了,那就需要依赖购物车组件啊,这俩组件还是耦合了啊,那咋办啊?答案是组件拆分出可暴露服务。见下图: 组件服务接口暴露 左侧是组件间可以调用对方服务 但是有依赖耦合。右侧,发现多了export_homeexport_cart,这是对应拆分出来的专门用于提供服务的暴露组件。操作说明如下:

  • 暴露组件 只存放 服务接口、服务接口相关的实体类、路由信息、便于服务调用的util等
  • 服务调用方 只依赖 服务提供方的 露组件,如module_home依赖export_cart,而不依赖module_cart
  • 组件 需要依赖 自己的暴露组件,并实现服务接口,如module_cart依赖export_cart 并实现其中的服务接口
  • 接口的实现注入 依然是由ARouter完成,和页面跳转一样使用路由信息

下面按照此方案 来实施 首页调用购物车服务 来获取商品数量,更好地说明和理解。

5.2 实施

5.2.1 新建export_cart

首先,在购物车工程中新建module即export_cart,在其中新建接口类ICartService并定义获取购物车商品数量方法,注意接口必须继承IProvider,是为了使用ARouter的实现注入:

/**

  • 购物车组件对外暴露的服务
  • 必须继承IProvider
  • @author hufeiyang
    */
    public interface ICartService extends IProvider {

/**

  • 获取购物车中商品数量
  • @return
    */
    CartInfo getProductCountInCart();
    }

CartInfo是购物车信息,包含商品数量:

/**

  • 购物车信息

  • @author hufeiyang
    */
    public class CartInfo {

/**

  • 商品数量
    */
    public int productCount;
    }

接着,创建路由表信息,存放购物车组件对外提供跳转的页面、服务的路由地址:

/**

  • 购物车组件路由表
  • 即 购物车组件中 所有可以从外部跳转的页面 的路由信息
  • @author hufeiyang
    */
    public interface CartRouterTable {

/**

  • 购物车页面
    */
    String PATH_PAGE_CART = “/cart/cartActivity”;

/**

  • 购物车服务
    */
    String PATH_SERVICE_CART = “/cart/service”;

}

前面说页面跳转时是直接使用 路径字符串 进行路由跳转,这里是和服务路由都放在这里统一管理。

然后,为了外部组件使用方便新建CartServiceUtil:

/**

  • 购物车组件服务工具类
  • 其他组件直接使用此类即可:页面跳转、获取服务。
  • @author hufeiyang
    */
    public class CartServiceUtil {

/**

  • 跳转到购物车页面
  • @param param1
  • @param param2
    */
    public static void navigateCartPage(String param1, String param2){
    ARouter.getInstance()
    .build(CartRouterTable.PATH_PAGE_CART)
    .withString(“key1”,param1)
    .withString(“key2”,param2)
    .navigation();
    }

/**

  • 获取服务
  • @return
    */
    public static ICartService getService(){
    //return ARouter.getInstance().navigation(ICartService.class);//如果只有一个实现,这种方式也可以
    return (ICartService) ARouter.getInstance().build(CartRouterTable.PATH_SERVICE_CART).navigation();
    }

/**

  • 获取购物车中商品数量
  • @return
    */
    public static CartInfo getCartProductCount(){
    return getService().getProductCountInCart();
    }
    }

注意到,这里使用静态方法 分别提供了页面跳转、服务获取、服务具体方法获取。 其中服务获取 和页面跳转 同样是使用路由,并且服务接口实现类 也是需要添加@Route注解指定路径的。

到这里,export_cart就已经准备完毕,我们同样发布一个export_cart的ARR(“com.github.hufeiyang.Cart:export_cart:xxx”)。

再来看看module_cart对服务接口的实现。

5.2.2 module_cart的实现

首先,module_cart需要依赖export_cart:

//module_cart的Build.gradle
dependencies {

annotationProcessor ‘com.alibaba:arouter-compiler:1.2.1’
implementation ‘com.github.hufeiyang:Common:1.0.0’

//依赖export_cart
implementation ‘com.github.hufeiyang.Cart:export_cart:1.0.5’
}

点击sync后,接着CartActivity的path改为路由表提供:

@Route(path = CartRouterTable.PATH_PAGE_CART)
public class CartActivity extends AppCompatActivity {

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

然后,新建服务接口的实现类来实现ICartService,添加@Route注解指定CartRouterTable中定义的服务路由

/**

  • 购物车组件服务的实现
  • 需要@Route注解、指定CartRouterTable中定义的服务路由
  • @author hufeiyang
    */
    @Route(path = CartRouterTable.PATH_SERVICE_CART)
    public class CartServiceImpl implements ICartService {

@Override
public CartInfo getProductCountInCart() {
//这里实际项目中 应该是 请求接口 或查询数据库
CartInfo cartInfo = new CartInfo();
cartInfo.productCount = 666;
return cartInfo;
}

@Override
public void init(Context context) {
//初始化工作,服务注入时会调用,可忽略
}
}

这里的实现是直接实例化了CartInfo,数量赋值666。然后发布一个ARR(“com.github.hufeiyang.Cart:module_cart:xxx”)。

5.2.3 module_home中的使用和调试

module_home需要依赖export_cart:

//module_home的Build.gradle
dependencies {

annotationProcessor ‘com.alibaba:arouter-compiler:1.2.1’
implementation ‘com.github.hufeiyang:Common:1.0.0’

//注意这里只依赖export_cart(module_cart由壳工程引入)
implementation ‘com.github.hufeiyang.Cart:export_cart:1.0.5’
}

在HomeActivity中新增TextView,调用CartServiceUtil获取并展示购物车商品数量:

@Route(path = “/homepage/homeActivity”)
public class HomeActivity extends AppCompatActivity {

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

//跳转到购物车页面
findViewById(R.id.btn_go_cart).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//通过路由跳转到 购物车组件的购物车页面(但没有依赖购物车组件)
// ARouter.getInstance()
// .build(“/cart/cartActivity”)
// .withString(“key1”,“param1”)//携带参数1
// .withString(“key2”,“param2”)//携带参数2
// .navigation();

CartServiceUtil.navigateCartPage(“param1”, “param1”);
}
});

//调用购物车组件服务:获取购物车商品数量
TextView tvCartProductCount = findViewById(R.id.tv_cart_product_count);
tvCartProductCount.setText(“购物车商品数量:”+ CartServiceUtil.getCartProductCount().productCount);
}
}

看到 使用CartServiceUtil.getCartProductCount()获取购物车信息并展示,跳转页面也改为了CartServiceUtil.navigateCartPage()方法。

到这里home组件的就可以独立调试了:页面跳转和服务调用,独立调试ok后 再集成到壳工程。 先让HomePage工程的app模块依赖Common组件、module_cart 以及本地的module_home

//HomePage工程,app模块的Build.gradle
dependencies {

//引入本地Common组件、module_cart、module_home,在app module中独立调试使用
implementation ‘com.github.hufeiyang:Common:1.0.0’
implementation ‘com.github.hufeiyang.Cart:module_cart:1.0.6’

implementation project(path: ‘:module_home’)
}

然后新建MyApplication初始化ARouter、在app的MainActivity中使用ARouter.getInstance().build(“/homepage/homeActivity”).navigation()打开首页,这样就可以调试了。

调试ok后接着就是集成到壳工程。

5.2.4 集成到壳工程

壳工程中的操作和独立调试类似,区别是对首页组件引入的是ARR:

dependencies {

//这里没有使用私有maven仓,而是发到JitPack仓,一样的意思~
// implementation ‘com.hfy.cart:cart:1.0.0’
implementation ‘com.github.hufeiyang.Cart:module_cart:1.0.6’
implementation ‘com.github.hufeiyang:HomePage:1.0.4’

//壳工程内 也需要依赖Common组件,因为需要初始化ARouter
implementation ‘com.github.hufeiyang:Common:1.0.0’
}

最后run壳工程来看下效果: 获取数量666、跳转也ok 获取数量是666、跳转页面成功。

另外,除了export_xxx这种方式,还可以添加一个 ComponentBase 组件,这个组件被所有的Common组件依赖,在这个组件中分别添加定义了业务组件可以对外提供访问自身数据的抽象方法的 Service。相当于把各业务组件的export整合到ComponentBase中,这样就只添加了一个组件而已。但是这样就不好管理了,每个组件对外能力的变更都要改ComponentBase。

另外,除了组件间方法调用,使用EventBus在组件间传递信息也是ok的(注意Event实体类要定义在export_xxx中)。

好了,到这里组件间通信问题也解决了。

六、fragment实例获取

上面介绍了Activity 的跳转,我们也会经常使用 Fragment。例如常见的应用主页HomeActivity 中包含了多个属于不同组件的 Fragment、或者有一个Fragment多个组件都需要用到。通常我们直接访问具体 Fragment 类来new一个Fragment 实例,但这里组件间没有直接依赖,那咋办呢?答案依然是ARouter

先在module_cart中创建CartFragment:

//添加注解@Route,指定路径
@Route(path = CartRouterTable.PATH_FRAGMENT_CART)
public class CartFragment extends Fragment {

public CartFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//显示“cart_fragment"
return inflater.inflate(R.layout.fragment_cart, container, false);
}
}

同时是fragment添加注解@Route,指定路由路径,路由还是定义在export_cart的CartRouterTable中,所以export_cart需要先发一个ARR,module_cart来依赖,然后module_cart发布ARR。

然后再module_home中依赖export_cart,使用ARouter获取Fragment实例:

@Route(path = “/homepage/homeActivity”)
public class HomeActivity extends AppCompatActivity {

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

FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction= manager.beginTransaction();

//使用ARouter获取Fragment实例 并添加
Fragment userFragment = (Fragment) ARouter.getInstance().build(CartRouterTable.PATH_FRAGMENT_CART).navigation();
transaction.add(R.id.fl_test_fragment, userFragment, “tag”);
transaction.commit();
}
}

可以先独立调试,然后集成到壳工程——依赖最新的module_cart 、HomePage,结果如下:

在这里插入图片描述

绿色部分就是引用自cart组件的fragment。

七、Application生命周期分发

我们通常会在Application的onCreate中做一些初始化任务,例如前面提到的ARouter初始化。而业务组件有时也需要获取应用的Application,也要在应用启动时进行一些初始化任务。

你可能会说,直接在壳工程Application的onCreate操作就可以啊。但是这样做会带来问题:因为我们希望壳工程和业务组件 代码隔离(虽然有依赖),并且 我们希望组件内部的任务要在业务组件内部完成。

那么如何做到 各业务组件 无侵入地获取 Application生命周期 呢?——答案是 使用AppLifeCycle插件,它专门用于在Android组件化开发中,Application生命周期主动分发到组件。具体使用如下:

  1. common组件依赖 applifecycle-api

首先,common组件通过 api 添加 applifecycle-api 依赖 并发布ARR:

//common组件 build.gradle
dependencies {

//AppLifecycle
api ‘com.github.hufeiyang.Android-AppLifecycleMgr:applifecycle-api:1.0.4’
}

  1. 业务组件依赖applifecycle-compiler、实现接口+注解

各业务组件都要 依赖最新common组件,并添加 applifecycle-compiler 的依赖:

//业务组件 build.gradle

//这里Common:1.0.2内依赖了applifecycle-api
implementation ‘com.github.hufeiyang:Common:1.0.2’
annotationProcessor ‘com.github.hufeiyang.Android-AppLifecycleMgr:applifecycle-compiler:1.0.4’

sync后,新建类来实现接口IApplicationLifecycleCallbacks用于接收Application生命周期,且添加@AppLifecycle注解。

例如 Cart组件的实现:

/**

  • 组件的AppLifecycle
  • 1、@AppLifecycle
  • 2、实现IApplicationLifecycleCallbacks
  • @author hufeiyang
    */
    @AppLifecycle
    public class CartApplication implements IApplicationLifecycleCallbacks {

public Context context;

/**

  • 用于设置优先级,即多个组件onCreate方法调用的优先顺序
  • @return
    */
    @Override
    public int getPriority() {
    return NORM_PRIORITY;
    }

@Override
public void onCreate(Context context) {
//可在此处做初始化任务,相当于Application的onCreate方法
this.context = context;

Log.i(“CartApplication”, “onCreate”);
}

@Override
public void onTerminate() {
}

@Override
public void onLowMemory() {
}

@Override
public void onTrimMemory(int level) {
}
}

实现的方法 有onCreate、onTerminate、onLowMemory、onTrimMemory。最重要的就是onCreate方法了,相当于Application的onCreate方法,可在此处做初始化任务。 并且还可以通过getPriority()方法设置回调 多个组件onCreate方法调用的优先顺序,无特殊要求设置NORM_PRIORITY即可。

  1. 壳工程引入AppLifecycle插件、触发回调

壳工程引入新的common组件、业务组件,以及 引入AppLifecycle插件:

//壳工程根目录的 build.gradle

buildscript {

repositories {
google()
jcenter()

//applifecycle插件仓也是jitpack
maven { url ‘https://jitpack.io’ }
}
dependencies {
classpath ‘com.android.tools.build:gradle:3.6.1’

//加载插件applifecycle
classpath ‘com.github.hufeiyang.Android-AppLifecycleMgr:applifecycle-plugin:1.0.3’
}
}

//app module 的build.gradle

apply plugin: ‘com.android.application’
//使用插件applifecycle
apply plugin: ‘com.hm.plugin.lifecycle’

dependencies {

//这里没有使用私有maven仓,而是发到JitPack仓,一样的意思~
// implementation ‘com.hfy.cart:cart:1.0.0’
implementation ‘com.github.hufeiyang.Cart:module_cart:1.0.11’
implementation ‘com.github.hufeiyang:HomePage:1.0.5’

//壳工程内 也需要依赖Common组件,因为要 触发生命周期分发
implementation ‘com.github.hufeiyang:Common:1.0.2’
}

最后需要在Application中触发生命周期的分发:

//壳工程 MyApplication
public class MyApplication extends Application {

@Override
public void onCreate() {
super.onCreate();

ApplicationLifecycleManager.init();
ApplicationLifecycleManager.onCreate(this);
}

@Override
public void onTerminate() {
super.onTerminate();

ApplicationLifecycleManager.onTerminate();
}

@Override
public void onLowMemory() {
super.onLowMemory();

ApplicationLifecycleManager.onLowMemory();
}

@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);

ApplicationLifecycleManager.onTrimMemory(level);
}
}

首先在inCreate方法中调用 ApplicationLifecycleManager的init()方法,用于收集组件内实现了IApplicationLifecycleCallbacks且添加了@AppLifecycle注解的类。然后在各生命周期方法内调用对应的ApplicationLifecycleManager的方法,来分发到所有组件。

这样 组件 就能接收到Application的生命周期了。 新增组件的话,只需要 实现IApplicationLifecycleCallbacks并添加了@AppLifecycle注解 即可,无需修改壳工程,也不用关心

AppLifecycle插件是使用了 APT技术、gradle插件技术+ASM动态生成字节码,在编译阶段就已经完成了大部分工作,无性能问题、且使用方便。

到这里,组件化开发的5个问题点 都已经解决了。 下面来看看针对老项目如何实现组件化改造。

八、 老项目组件化

通常情况 我们去做组件化,都是为了改造 已有老项目。可能老项目内部的模块之间耦合严重,没有严格的业务模块划分,并且组件化改造是大工作量的事情,且要全量回归测试,总体来说,是需要全员参与、有较大难度的事情。

8.1 方案

8.1.1 组件划分

根据前面介绍的组件化架构图,组件分为 基础组件、业务基础组件、业务组件。

  • 基础组件,不用多说,就是基础功能,例如网络请求、日志框架、图片加载,这些与业务毫无关联,可用于公司所有项目,是底层最稳定的组件。这里就比较容易识别和拆分。
  • 业务基础组件,主要是供业务组件依赖使用,例如 分享、支付组件,通常是一个完整的功能,是较为最稳定的组件。这部分通常也是比较容易识别的。
  • 业务组件,完整的业务块,例如前面提到京东的 “首页”、“分类”、“发现”、“购物车”、“我的”。业务组件是日常需求开发的主战场。

8.1.2 组件拆分:基础组件、Common组件

基础组件最容易拆分,它依赖最少,功能单一纯粹。把基础组件依赖的东西,从老工程中抽取出来,放在单独的工程,做成单独的组件,发布ARR到公司maven仓。注意不能存在任何业务相关代码。

新建Common组件,使用 “api” 依赖 所有基础组件,这样依赖 Common组件的组件 就能使用所有基础组件的功能了。接着,就是前面提到的 ARouter、AppLifeCycle、以及其他第三方库的依赖。

另外,Common组件,还有一个重要部分:提供BaseActivity、BaseFragment,这里Base需要完成基础能力的添加,例如页面进入、退出的埋点上报、统一页面标题样式、打开关闭EventBus等等。

8.1.3 组件拆分:业务基础组件、业务组件

业务基础组件 基本上只依赖common,功能也是单一纯粹。同样是把依赖的东西抽取出来,放在单独的工程,做成单独的组件,发布ARR到公司maven仓。

业务组件,首先要识别组件的边界,可以按照页面入口和出口作为判断。然后,需要识别对 业务基础组件的依赖;以及 最重要的,对其他 业务组件的依赖。 可以先把代码抽离到单独的工程,然后依赖common组件、需要的业务基础组件,此时依然报错的地方就是 对其他 业务组件的依赖了。这时就可以给对应组件负责人提需求,在export_xxx中提供跳转和服务。 然后你只需要依赖export_xxx使用即可

老项目组件化改造需要循序渐进,除非有专门的时间。一般是需求开发和改造并行。要先完成一个组件,之后有了经验,后面其他业务组件陆续实施,这样就会比较简单。

最后

在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

人提需求,在export_xxx中提供跳转和服务。 然后你只需要依赖export_xxx使用即可**。

老项目组件化改造需要循序渐进,除非有专门的时间。一般是需求开发和改造并行。要先完成一个组件,之后有了经验,后面其他业务组件陆续实施,这样就会比较简单。

最后

在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
[外链图片转存中…(img-OBa95CJH-1714525817642)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值