当前位置:网站首页>"NetEase Internship" Weekly Diary (3)

"NetEase Internship" Weekly Diary (3)

2022-08-02 01:56:00 Liknananana

『网易实习』周记(三)

本周知识清单:

  • kotlin基础
  • jar包和arr包的区别
  • 分析Google的MVVMThe case of architecture
  • LiveData与MutableLive的区别
  • kotlin lamda语法
  • kotlin的匿名内部类
  • kotlin函数式编程
  • RxJava

kotlin基础

A learning, of course, is to look at the official documentation,推荐阅读kotlin中文官方文档菜鸟教程 ,Me some time to read the document after the company took over the real project,And found a lot of advanced usage haven't read it,So again to catch up with the,关于这些I see the article recommended
从Java过渡到kotlin
"Kotlin"系列: 一、Kotlin入门
"Kotlin"系列: 二、Kotlin泛型
Lambda语法在Java中已经被广泛的运用,我们在开发Android中几乎上每一个项目也会在项目中接入Lambda插件,因为LambdaReally can reduce a lot of amount of code.无独有偶,在Kotlin中也是Lambda语法的,在这篇文章中就详细的为大家讲解Lambda语法的编写与使用
Late themselves out somekotlin的读书笔记,欢迎关注

jar包和arr包的区别

Recently learned componentization,Found it useless (no use) our company componentization projectARouter路由组件,我很好奇,Ask the project architecture predecessors,He asked me the company code is nothing two parts
在这里插入图片描述
java包和sdk包,javaIs the real business logic,Are not exposed to the outside world,sdkPackage is this module provides the interface,我们通过把sdk打包成jar包,Other packages to use introduced directly by the,It also can realize the page jump,为什么不用ARouter呢?前辈说:ARouterTo configure the routing path,Routing path is a string,If the configuration is wrong is hard to find,But if you call interface,The compiler can't through,One to reduce the error probability
**jar包与aarThe difference between package files **:
两者区别:
*.jar:只包含了class文件与清单文件,不包含资源文件,如图片等所有res中的文件.
*.aar:包含所有资源,class以及res资源文件全部包含
If you are just a simple class library with the generated.jar文件即可;如果你的是一个UI库,Contains some control layout file and write their own font resources such as file so can only use.aar文件.
推荐阅读:android studio library生成jar包和aar的方法总结

分析Google的MVVMThe case of architecture

案例地址:https://github.com/android/architecture-components-samples/tree/main/BasicSample
Due to the company before the project isMVVM的架构,To learn on their own to thenJetpack里面的ViewModel和DataBinding LiveDataHas never written and project,So on this is now a little knowledge,And then this week to have demand recently,The company is a big project,Then see dizziness later teacher let me to readgoogle的案例,Google's case is a list of goods,Point in is the product details page,这次分析Mainly from a data request to start,Analysis of a complete data request,特此记录一下
在这里插入图片描述
在这里插入图片描述
先看项目结构:
在这里插入图片描述

public class BasicApp extends Application {
    

    private AppExecutors mAppExecutors;

    @Override
    public void onCreate() {
    
        super.onCreate();
        // Create a thread to handle class
        mAppExecutors = new AppExecutors();
    }

    //Returns a database operation to do class
    public AppDatabase getDatabase() {
    
        return AppDatabase.getInstance(this, mAppExecutors);
    }

    // Return to a class of data storage warehouse
    public DataRepository getRepository() {
    
        return DataRepository.getInstance(getDatabase());
    }
}

//This is a page to load thelist的商品类,From the analysis data of loading request
public class ProductListFragment extends Fragment {
    

    public static final String TAG = "ProductListFragment";

    private ProductAdapter mProductAdapter;

    private ListFragmentBinding mBinding;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
            @Nullable Bundle savedInstanceState) {
    
        // 这就是获取DataBinding
        mBinding = DataBindingUtil.inflate(inflater, R.layout.list_fragment, container, false);
        //.This is for home pagerecycleview设置适配器
        mProductAdapter = new ProductAdapter(mProductClickCallback);
        mBinding.productsList.setAdapter(mProductAdapter);

        return mBinding.getRoot();
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    
        super.onViewCreated(view, savedInstanceState);
        // 获取页面的viewModel
        final ProductListViewModel viewModel =
                new ViewModelProvider(this).get(ProductListViewModel.class);
        // This is the query in the search box
        mBinding.productsSearchBtn.setOnClickListener(v -> {
    
            Editable query = mBinding.productsSearchBox.getText();
            viewModel.setQuery(query);
        });
        //1.调用subscribeUiShow the commodity data in,Focus on here so he went to get onProducts的
        subscribeUi(viewModel.getProducts());
    }

    private void subscribeUi(LiveData<List<ProductEntity>> liveData) {
    
        // Update the list when the data changes
        liveData.observe(getViewLifecycleOwner(), new Observer<List<ProductEntity>>() {
    
            @Override
            // 这里就是LiveDataThere are any changes of data is to look at the data
            public void onChanged(List<ProductEntity> myProducts) {
    
                if (myProducts != null) {
    
                    mBinding.setIsLoading(false);
                    mProductAdapter.setProductList(myProducts);
                } else {
    
                    mBinding.setIsLoading(true);
                }
                // espresso does not know how to wait for data binding's loop so we execute changes
                // sync.
                mBinding.executePendingBindings();
            }
        });
    }

    @Override
    public void onDestroyView() {
    
        mBinding = null;
        mProductAdapter = null;
        super.onDestroyView();
    }
    
    //This back off function is used to do when I click the specific pageitem数据的时候.Details to display the page
    private final ProductClickCallback mProductClickCallback = product -> {
    
        if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
    
            ((MainActivity) requireActivity()).show(product);
        }
    };
}

回到关注点1:我们进入了ProductListViewModel

public class ProductListViewModel extends AndroidViewModel {
    
    private static final String QUERY_KEY = "QUERY";

    private final SavedStateHandle mSavedStateHandler;
    private final DataRepository mRepository;
    private final LiveData<List<ProductEntity>> mProducts;

    public ProductListViewModel(@NonNull Application application,
            @NonNull SavedStateHandle savedStateHandle) {
    
        super(application);
        mSavedStateHandler = savedStateHandle;

        mRepository = ((BasicApp) application).getRepository();

        // Use the savedStateHandle.getLiveData() as the input to switchMap,
        // allowing us to recalculate what LiveData to get from the DataRepository
        // based on what query the user has entered
        // 3.Here you can see here ismProducts数据初始化的时候,We can see that the data is fromRepository类
        mProducts = Transformations.switchMap(
                savedStateHandle.getLiveData("QUERY", null),
                (Function<CharSequence, LiveData<List<ProductEntity>>>) query -> {
    
                    if (TextUtils.isEmpty(query)) {
    
                        return mRepository.getProducts();
                    }
                    return mRepository.searchProducts("*" + query + "*");
                });
    }

    public void setQuery(CharSequence query) {
    
        // Save the user's query into the SavedStateHandle.
        // This ensures that we retain the value across process death
        // and is used as the input into the Transformations.switchMap above
        mSavedStateHandler.set(QUERY_KEY, query);
    }

    /** * Expose the LiveData Products query so the UI can observe it. */
    // 2.This is the first concerns the method called,Then we can see the class constructor
    public LiveData<List<ProductEntity>> getProducts() {
    
        return mProducts;
    }
}

回到DataReppsitory类,

public class DataRepository {
    

    private static DataRepository sInstance;

    private final AppDatabase mDatabase;
    private MediatorLiveData<List<ProductEntity>> mObservableProducts;

    private DataRepository(final AppDatabase database) {
    
        mDatabase = database;
        mObservableProducts = new MediatorLiveData<>();
        // 5.这里可以知道,Data from againDatabase里面的productDao去调
        mObservableProducts.addSource(mDatabase.productDao().loadAllProducts(),
                productEntities -> {
    
                    if (mDatabase.getDatabaseCreated().getValue() != null) {
    
                        mObservableProducts.postValue(productEntities);
                    }
                });
    }

    public static DataRepository getInstance(final AppDatabase database) {
    
        if (sInstance == null) {
    
            synchronized (DataRepository.class) {
    
                if (sInstance == null) {
    
                    sInstance = new DataRepository(database);
                }
            }
        }
        return sInstance;
    }

    /** * Get the list of products from the database and get notified when the data changes. */
    // 4.Can know the focus of the data is from here
    public LiveData<List<ProductEntity>> getProducts() {
    
        return mObservableProducts;
    }

    public LiveData<ProductEntity> loadProduct(final int productId) {
    
        return mDatabase.productDao().loadProduct(productId);
    }

    public LiveData<List<CommentEntity>> loadComments(final int productId) {
    
        return mDatabase.commentDao().loadComments(productId);
    }

    public LiveData<List<ProductEntity>> searchProducts(String query) {
    
        return mDatabase.productDao().searchAllProducts(query);
    }
}

回到AppDatabase,这个类就是Room数据库的操作类,这个类是个抽象类,我们调用productDao(),会去
ProductDaoInterface to execute insidesql语句

@Database(entities = {
    ProductEntity.class, ProductFtsEntity.class, CommentEntity.class}, version = 2)
@TypeConverters(DateConverter.class)
public abstract class AppDatabase extends RoomDatabase {
    

    private static AppDatabase sInstance;

    @VisibleForTesting
    public static final String DATABASE_NAME = "basic-sample-db";

    public abstract ProductDao productDao();

    public abstract CommentDao commentDao();

    private final MutableLiveData<Boolean> mIsDatabaseCreated = new MutableLiveData<>();

    public static AppDatabase getInstance(final Context context, final AppExecutors executors) {
    
        if (sInstance == null) {
    
            synchronized (AppDatabase.class) {
    
                if (sInstance == null) {
    
                    sInstance = buildDatabase(context.getApplicationContext(), executors);
                    sInstance.updateDatabaseCreated(context.getApplicationContext());
                }
            }
        }
        return sInstance;
    }

    /** * Build the database. {@link Builder#build()} only sets up the database configuration and * creates a new instance of the database. * The SQLite database is only created when it's accessed for the first time. */
    private static AppDatabase buildDatabase(final Context appContext,
            final AppExecutors executors) {
    
        return Room.databaseBuilder(appContext, AppDatabase.class, DATABASE_NAME)
                .addCallback(new Callback() {
    
                    @Override
                    public void onCreate(@NonNull SupportSQLiteDatabase db) {
    
                        super.onCreate(db);
                        executors.diskIO().execute(new Runnable() {
    
                            @Override
                            public void run() {
    
                                // Add a delay to simulate a long-running operation
                                addDelay();
                                // Generate the data for pre-population
                                AppDatabase database = AppDatabase.getInstance(appContext, executors);
                                List<ProductEntity> products = DataGenerator.generateProducts();
                                List<CommentEntity> comments =
                                        DataGenerator.generateCommentsForProducts(products);

                                insertData(database, products, comments);
                                // notify that the database was created and it's ready to be used
                                database.setDatabaseCreated();
                            }
                        });
                    }
                })
            .addMigrations(MIGRATION_1_2)
            .build();
    }

    /** * Check whether the database already exists and expose it via {@link #getDatabaseCreated()} */
    private void updateDatabaseCreated(final Context context) {
    
        if (context.getDatabasePath(DATABASE_NAME).exists()) {
    
            setDatabaseCreated();
        }
    }

    private void setDatabaseCreated(){
    
        mIsDatabaseCreated.postValue(true);
    }

    private static void insertData(final AppDatabase database, final List<ProductEntity> products,
            final List<CommentEntity> comments) {
    
        database.runInTransaction(() -> {
    
            database.productDao().insertAll(products);
            database.commentDao().insertAll(comments);
        });
    }

    private static void addDelay() {
    
        try {
    
            Thread.sleep(4000);
        } catch (InterruptedException ignored) {
    
        }
    }

    public LiveData<Boolean> getDatabaseCreated() {
    
        return mIsDatabaseCreated;
    }

    private static final Migration MIGRATION_1_2 = new Migration(1, 2) {
    

        @Override
        public void migrate(@NonNull SupportSQLiteDatabase database) {
    
            database.execSQL("CREATE VIRTUAL TABLE IF NOT EXISTS `productsFts` USING FTS4("
                + "`name` TEXT, `description` TEXT, content=`products`)");
            database.execSQL("INSERT INTO productsFts (`rowid`, `name`, `description`) "
                + "SELECT `id`, `name`, `description` FROM products");

        }
    };
}

Obviously it is all explained,The data of experience:

Other details want to know about yourself to see oh,就不一一写出来了

LiveData与MutableLive的区别

LiveData

  • LiveDataIs one of the observer pattern data entity class,He can, and whether his registered observer callback data has been updated
  • LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如 Activity、Fragment 或 Service)的生命周期.这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者.

MutableLive与LiveData的区别
1.MutableLiveData的父类是LiveData
2.LiveData在实体类里可以通知指定某个字段的数据更新.
3.MutableLiveData则是完全是整个实体类或者数据类型变化后才通知.不会细节到某个字段
推荐阅读 :MutableLiveData详解

Kotlin:Lambda表达式

Recently took over the demand arekotlin开发,Alternative predecessors wrotekollin很多都是lambda表达式,I'm a beginner looks really bad,The recently took advantage of the weekendLambdaLearn about,特此记录一下.

Lambda的特征

  1. Lambda表达式总是被大括号括着
  2. 其参数(如果存在)在符号’->'之前声明(参数类型可以省略)
  3. 函数体(如果存在)在符号’->'后面.
  4. 简单来说,Lambda Is a can be passed as a parameter to the code,It can be used as the parameters of the function,返回值,At the same time can also be assigned to a variable
  5. Lambda Complete expression of the grammatical structure:{ 参数名1:参数类型,参数名2:参数类型 -> 函数体 }
  6. 很多时候,We will use simplified forms of syntax structure,Direct is a function of body:{函数体},这种情况是当 Lambda Expression of only one parameter in the parameter list,We can put the parameters to omit,默认会有个 it 参数
  7. Kotlin 中规定,当 Lambda Expression as a function of the last parameter,我们可以把 Lambda 表达式移到函数括号的外面
  8. Kotlin 中规定,当 Lambda The expression is the only parameter function,函数的括号可以省略

无参数:
val 函数名 = {函数体}
有参数:
val 函数名 : (参数1:类型, 参数2:类型, …) -> 返回值类型 = { 参数1, 参数2, … -> 函数体 }
val 函数名 = { 参数1:类型1, 参数2:类型2, … -> 函数体 }

只有一个参数的时候,返回值中的参数形参可以省略,引用时通过it进行引用

匿名函数:
val 函数名 = fun(参数1:类型1, 参数2:类型2, …): 返回值类型 { 函数体 }
高阶函数
KotlinEverything inside the function,Introduced the function type,Can also function as a type of,当作参数传递,那么使用LambdaExpressions will have a feeling that is used as the composite function of high school mathematics,f(g(x))There are many replacement

// 匿名函数
val sum = fun(a: Int, b: Int): Int {
    
    return a + b
}

// 具名函数
fun namedSum(a: Int, b: Int): Int {
    
    return a + b
}

// 高阶函数
fun highSum(a: Int, b: Int, f: (Int, Int) -> Int): Int {
    
    return f(a, b)
}

fun main(args: Array<String>) {
    
    // 通过()来执行匿名函数sum
    val add = sum(1, 2)
    println(add)
    // 通过lambda表达式来完成函数highSum
    val add2 = highSum(3, 4) {
     a, b -> a + b }
    println(add2)
    // 通过函数引用来完成函数highSum
    val add3 = highSum(5, 6, ::namedSum)
    println(add3)
    
    // forEach参数接收一个函数
    args.forEach({
     it: String -> println(it) })
    // 去掉返回值,自动推断
    args.forEach({
     it -> println(it) })
    // 只有一个参数的时候可以省略it
    args.forEach({
     println(it) })
    // lambda表达式在最后一个参数可以外移
    args.forEach() {
     println(it) }
    // 函数若无参数可以去掉()
    args.forEach {
     println(it) }
    // 引用函数
    args.forEach(::println)
}

函数类型与实例化

  1. :: 双冒号操作符表示对函数的引用
  2. lambda表达式
  3. 匿名函数
fun main(args: Array<String>) {
    
    // 引用函数
    println(test(1, 2, ::add))
    // 匿名函数
    val add = fun(a: Int, b: Int): Int {
    
        return a + b
    }
    println(test(3, 4, add))
    // lambda表达式
    println(test(5, 6, {
     a, b -> a + b }))// lambda作为最后一个参数可以提到括号外
    println(test(5, 6) {
     a, b -> a + b })
}

fun test(a: Int, b: Int, f: (Int, Int) -> Int): Int {
    
    return f(a, b)
}

fun add(a: Int, b: Int): Int {
    
    return a + b
}

推荐阅读:Kotlin修炼指南(二):lambda表达式的精髓Kotlin:Lambda表达式
Kotlin 的高阶函数、匿名函数和 Lambda 表达式

kotlin匿名内部类

Kotlin Write an anonymous inner class and Java 有一点区别,Kotlin Because in the abandoned new 关键字,改用 object 关键字就可以了

//java 中的匿名内部类
new Thread(new Runnable() {
    
     @Override
     public void run() {
    

     }
}).start();

//Kotlin 中可以这么写
Thread(object : Runnable{
    
    override fun run() {
    
            
    }
}).start()

/** * We went on to simplify Kotlin 中的写法 * 因为 Runnable 类中只有一个待实现方法,Even if there is no display rewritten run() 方法, * Kotlin Can understand the back of the Lambda 表达式就是要在 run() 方法中实现的内容 */
Thread(Runnable{
    
  
}).start()

//Because a single abstract method interface,We can be omitted the interface name
Thread({
    
  
}).start()

//当 Lambda Expression as a function of the last parameter,我们可以把 Lambda 表达式移到函数括号的外面
Thread(){
    
  
}.start()

//当 Lambda The expression is the only parameter function,函数的括号可以省略
Thread{
    
  
}.start()

推荐阅读:如何在 kotlin 优雅的封装匿名内部类(DSL、高阶函数)

kotlin的函数式编程

函数式编程(FP)Is based on the premise of a simple and meaningful:Only pure functions to build the program.The deeper meaning of this sentence is,We should function to build applications with no side effect.什么是副作用呢?Function with side effects in the process of call is not only a simple input and output behavior,It also do some other things.And these effects will influence the diffusion to the outside of the function,比如:

  • Modify an external variable(函数外定义)
  • 设置成员变量
  • 修改数据结构(List、Map、Set)
  • Throw an exception, or an error to stop
  • 读取、写入文件

RxJava学习

Recent project there are many are with theRxJava,You also need to update their knowledge base,由于时间原因,我只学了RxJava的如何使用,RxJavaMany of the source and the use of other,Have the time to come back to catch up with.

RxJava的简介

  • RxJava:Reactive Extensions for the JVM ,基于JVM的Rx
  • Reactive Extensions Is a Microsoft put forward a comprehensive induction and based on the event-driven programming library
  • RxJava的核心就是异步数据流和响应式编程:
    1·Put all the events(数据)As a river,He can be observed,Filtering or operation,Can also and another rivers converge into a new river
    2·Once the event or change,Can trigger to observe the role of these events(观察者/订阅者)做出响应

RxJavaThe advantage and the suitable scene

  • 具备响应式编程该有的特性
  • 为异步而生,无需手动创建线程,Have the ability to thread
  • 支持链式调用,保证代码的简洁性
  • 各种操作符,功能非常强大,满足各种业务需求
  • 简化了异常的处理

RxJava的适用场景:网络请求,数据库读写,文件读写,Timing task and other lengthy operations are oh ah done through asynchronous operations can be usedRxJava
RxJava There are three basic elements of the:

  1. 被观察者(Observable)
  2. 观察者(Observer)
  3. 订阅(subscribe)
// 导入依赖
implementation "io.reactivex.rxjava2:rxjava:2.2.21"
implementation "io.reactivex.rxjava2:rxandroid:2.0.2"

Events like a river
在这里插入图片描述

Observable.create(new ObservableOnSubscribe<Integer>() {
    
    //上游 Observable
    @Override
    public void subscribe(ObservableEmitter<Integer> e) throws Exception {
                //To add a waternext事件,Passed to the downstream has corresponding receivingnext事件的方法
        e.onNext(1);
        //In the water is acomplete事件,Passed to the downstream has corresponding receivingnext事件的方法
        e.onComplete(2);
    }
})
    .subscribe(
    //subscible Is the link,Connects upstream and downstream
    new Observer<Integer>() {
    
        //observer 表示 下游
        Disposable d;
        //DisposableSaid the link switch
        @Override
        public void onSubscribe(Disposable d) {
    
            this.d=d;
        }
        @Override
        public void onNext(Integer integer) {
    
            //To accept the upstreamnext事件
            Log.d(TAG, "onNext: "+integer);
        }
        @Override
        public void onError(Throwable e) {
    
            Log.d(TAG, "onError: "+e.getMessage());
        }
        @Override
        public void onComplete() {
    
            //To accept the upstreamcomplete事件`在这里插入代码片`
            Log.d(TAG, "onComplete ");
        }
    });

ObservableEmitter

ObservableEmitter: Emitter是发射器的意思,He is used to emit events,He can send out three types of events,通过调用emitter的onNext(T value)、onComplete()和onError(Throwable error)就可以分别发出next事件、complete事件和error事件.
Emit events need to follow certain rules

  • onNext():The upstream can make multipleonNextEvents can also received a number of downstreamonNext事件.
  • onComplete():Send a upstreamonComplete事件后,Can continue to send other events,But the downstream accept toonComplete事件以后,Just stop receive events.
  • onError():和onComplete相同,The upstreamonErrorAfter continue to send other events,And downstream to receiveonErrorAfter the event no longer receive events.
  • The upstream can not sendonComplete和onError事件
  • onComplete和onErrorMust be mutually exclusive and only,Don't can also make multipleonCompleteEvents also can not make multiple at the same timeonError事件,Also can not startonCompleteEvents againonError事件,反之亦然.

Disposable

He is equivalent to we mentioned the link,When the link was closed the calldisposable.dispose()方法,We won't be able to receive any events from the upstream downstream.But this does not affect the upstream continue to emit events.

public class test {
    
    public static void main(String[] args) {
    
        String TAG="SHOW";
        Observable.create(new ObservableOnSubscribe<Integer>() {
    
            //被观察者
            @Override
            public void subscribe(ObservableEmitter<Integer> e) throws Exception {
    
                e.onNext(1);
                System.out.println("subscribe: next1");
                e.onNext(2);
                System.out.println("subscribe: next2");
                //e.onComplete();
                //System.out.println("subscribe: complete");
                e.onNext(3);
                System.out.println("subscribe: next3");
                e.onNext(4);
                System.out.println("subscribe: next4");
            }
        }).subscribe(new Observer<Integer>() {
    
            //观察者
            Disposable d;
            @Override
            public void onSubscribe(Disposable d) {
    
                this.d=d;
            }
            @Override
            public void onNext(Integer integer) {
    
                //DisposableTo be able to cut the pipe
                if(integer>=2){
    
                    //当Integer大于2The close link between,Don't accept any downstream events,The upstream can continue to send
                    d.dispose();
                }
                System.out.println("onNext: "+integer);
            }
            @Override
            public void onError(Throwable e) {
    
                System.out.println("onError: "+e.getMessage());
            }
            @Override
            public void onComplete() {
    
                System.out.println("onComplete ");
            }
        });


    }
}

在这里插入图片描述

Schedulers

  1. Schedulers.newThread():A regular child thread
  2. AndroidSchedulers.mainThread():Activity所在的主线程
  3. Schedulers.io():适合IO密集型的操作
  4. Schedulers.computation():Suitable for computationally intensive operations

subscribeOn

  1. Used to specify the observed in the thread,一般来说,We in the interior of the observed perform network request orIO操作,So on the child thread to perform.
  2. 注意一点,subscribeOnMethods if the called many times,Will be subject to when the first call to the specified thread,看如下代码
  observable.subscribeOn(Schedulers.computation())
                       .subscribeOn(Schedulers.io())
                       .subscribeOn(Schedulers.newThread())

This last callsubscribeOn方法,最后ObservableWill be in accordance with the first call the specified thread running.

observerOn

It is used to specify the observer's own thread,As a result of observers accepted observed operation,May need to be updatedUI,所以大部分情况下,We will specify it in the main thread.
Also need to note,observerOnMethod is called many times,Will be subject to the specified thread one last time,即调用一次observerOn方法,线程便会切换一次.
推荐阅读:RxJava2 只看这一篇文章就够了


写在最后:Notes just record of learning,Probably will not be fully,But also for their knowledge of a output,Record of his own little by little,I am carrying a bag,Drifting in the outside of young, Bag with my dream

原网站

版权声明
本文为[Liknananana]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/214/202208020139440330.html