如何应对 Android 面试官 -> 玩转 Jetpack Paging

Android统一开发标准深度解析:从MVVM架构到工程化实践 在移动开发领域,架构模式是构建可维护、可扩展应用的核心基础。MVVM(Model-View-ViewModel)作为一种主流架构,通过数据绑定和生命周期感知机制,实现了UI逻辑与业务逻辑的清晰分离,其原理在于利用观察者模式确保数据状态与界面呈现的一致性。这种架构的技术价值在于显著提升了代码的可测试性和团队协作效率,是应对复杂业务场景的工程化基石。在实际应用场景中,结合Android Jetpack组件库(如ViewModel、LiveData/StateFlow)和统一的代码规范,能够有效解决项目碎片化与维 阅读详情

前言


在这里插入图片描述

本章讲解 Paging,依然是从基础使用和原理两个方向;

是什么?

Paging 是 Jetpack 提供的一个分页组件,可以更轻松地在应用程序中的RecyclerView逐步和优雅地加载数据;数据请求消耗的网络带宽更少,系统资源更少;即使在数据更新和刷新期间,应用程序仍会继续快速响应用户输入;不过多浪费,显示多少就用多少;

使用篇


Paging 的使用主要区分在 DataSource 的使用,定义 DataSource 一共有三种:

继承 PositionalDataSource

继承 PageKeyedDataSource

继承 ItemKeyedDataSource

使用起来相对来说一般,需要提供四个类:

  • DataSource(是数据源,包含了多种形式,例如:Room 来源,PositionalDataSource 来源,PageKeyedDataSource 来源,ItemKeyedDataSource 来源)
  • PagedList(是 UIModel 数据层,通过 Factory 的方式拿到数据源)
  • PagedAdapter(这里不再是之前使用 RecycleView 的那种适配器了,而是和 Paging 配套的 PagedListAdapter)
  • RecycleView(是之前用的 RecycleView,只不过 setAdapter 的时候,绑定的适配器是 PagedAdapter)

依赖

// Paging库依赖
implementation 'androidx.paging:paging-runtime:xxx'

数据源 PositionalDataSource 的基础使用

数据bean

用来展示列表数据,那么我们就先来定义一个数据 bean

public class Student {

    private String id;
    private String name;
    private String sex;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    // 比较的函数
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return id.equals(student.id) &&
                name.equals(student.name) &&
                sex.equals(student.sex);
    }

    // 比较的函数
    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    @Override
    public int hashCode() {
        return Objects.hash(id, name, sex);
    }
}

一定要重写 equals 方法,Paging 用来判断是否相等,从而决定要不要刷新;

数据源 DataSource

接下来我们来定义数据源,需要继承 Paging 中的 PositionalDataSource 来定义数据源

/**
 * 数据的来源(room,网络,...)
 *
 * 这里是数据源,获取数据目前是在这里完成的
 *
 * 官方文档上,继承的是 ItemKeyedDataSource, 而这里实现的是 PositionalDataSource
 *
 * PositionalDataSource<Student>: 适用于目标数据总数的固定,通过特别的位置加载数据(0-10)
 *  比如从数据库中的1200条开始加在20条数据。
 */
public class StudentDataSource extends PositionalDataSource<Student> {

    /**
     * 可以理解是加载第一页数据的时候,会执行此函数来完成
     * 加载初始化数据,可以这么来理解,加载的是第一页的数据。
     * 形象的说,当我们第一次打开页面,需要回调此方法来获取数据。
     * @param params
     * @param callback
     */
    @Override
    public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback<Student> callback) {
        // @1:数据源 @2:位置 @3:总大小
        new Thread(){
            public void run() {
                callback.onResult(getStudents(0, 20), 0, 1000);
            }
        }.start()
    }

    /**
     * 当有了初始化数据之后,滑动的时候如果需要加载数据的话,会调用此方法。
     * @param params
     * @param callback
     */
    @Override
    public void loadRange(@NonNull LoadRangeParams params, @NonNull LoadRangeCallback<Student> callback) {
        // @1:从哪里开始加载(位置内部算的)  @2:size(size 内部算的)
        callback.onResult(getStudents(params.startPosition, params.loadSize));
    }

    /**
     * 可以理解这里是数据源,数据的来源(数据库,文件,网络服务器响应等等)
     * @param startPosition
     * @param pageSize
     * @return
     */
    private List<Student> getStudents(int startPosition, int pageSize) {
        List<Student> list = new ArrayList<>();
        for (int i = startPosition; i < startPosition + pageSize; i++) {
            Student student = new Student();
            student.setId("ID号是:" + i);
            student.setName("我名称:" + i);
            student.setSex("我性别:" + i);
            list.add(student);
        }
        return list;
    }
}

这个 loadInitial方法可以理解为:加载第一页数据的时候,会执行此函数来完成;

这个 loadRange方法可以理解为:加载更多数据的时候,会执行此函数来完成;

常规业务开发,我们就是使用这个

数据工厂 DataSource.Factory

数据工厂需要继承 DataSource.Factory 来实现;

/**
 * 数据的工厂
 */
public class StudentDataSourceFactory extends DataSource.Factory<Integer, Student> {

    @NonNull
    @Override
    public DataSource<Integer, Student> create() {
        StudentDataSource studentDataSource = new StudentDataSource();
        return studentDataSource;
    }
}

主要用来创建我们的 DataSource,也就是 StudentDataSource

适配器 PagedListAdapter

接下来我们来定义 Adapter,使用 Paging 需要继承 PagedListAdapter,而不是继承 RecyclerView.Adapter

public class RecyclerPagingAdapter extends PagedListAdapter<Student, RecyclerPagingAdapter.MyRecyclerViewHolder> {

    // TODO 比较的行为
    private static DiffUtil.ItemCallback<Student> DIFF_STUDENT = new
            DiffUtil.ItemCallback<Student>() {

                @Override
                public boolean areItemsTheSame(@NonNull Student oldItem, @NonNull Student newItem) {
                    return oldItem.getId().equals(newItem.getId());
                }

                // 对象本身的比较
                @Override
                public boolean areContentsTheSame(@NonNull Student oldItem, @NonNull Student newItem) {
                    return oldItem.equals(newItem);
                }
            };

    protected RecyclerPagingAdapter() {
        // Remind
        super(DIFF_STUDENT);
    }

    @NonNull
    @Override
    public MyRecyclerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, null);
        return new MyRecyclerViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull MyRecyclerViewHolder holder, int position) {
        Student student = getItem(position);

        // itemView 出来了, 分页库还在加载数据中,就显示Id加载中
        if (null == student) {
            holder.tvId.setText("Id加载中");
            holder.tvName.setText("Name加载中");
            holder.tvSex.setText("Sex加载中");
        } else {
            holder.tvId.setText(student.getId());
            holder.tvName.setText(student.getName());
            holder.tvSex.setText(student.getSex());
        }
    }

    // Item 优化的 ViewHolder
    public static class MyRecyclerViewHolder extends RecyclerView.ViewHolder {

        TextView tvId;
        TextView tvName;
        TextView tvSex;

        public MyRecyclerViewHolder(View itemView) {
            super(itemView);
            tvId = itemView.findViewById(R.id.tv_id); // ID
            tvName = itemView.findViewById(R.id.tv_name); // 名称
            tvSex = itemView.findViewById(R.id.tv_sex); // 性别
        }
    }
}

需要注意的地方就是构造方法中,需要传入 DiffUtil.ItemCallback 从而让 PagedListAdapter 来判断变化的 item,从而只刷新需要改变的 item;

组合使用

接下来,我们来定义承载 RecyclerView 的 Activity,以及在 Activity 中使用 Factory,官方文档是建议我们放在 ViewModel 中来使用,我们来定一个 ViewModel;

ViewModel
/**
 * PagedList: 数据源获取的数据最终靠 PagedList 来承载。
 * 对于 PagedList, 我们可以这样来理解,它就是一页数据的集合。
 * 每请求一页,就是新的一个 PagedList 对象。
 */
public class StudentViewModel extends ViewModel {

    private final LiveData<PagedList<Student>> listLiveData;

    public StudentViewModel() {
        StudentDataSourceFactory factory = new StudentDataSourceFactory();
        // 初始化 ViewModel
        this.listLiveData = new LivePagedListBuilder<Integer, Student>(factory, 20).build();
    }

    // 暴露数据出去
    public LiveData<PagedList<Student>> getListLiveData() {
        return listLiveData;
    }
}

构造方法中,直接创建 StudentDataSourceFactory 并交给 LivePagedListBuilder 来构建 LiveData

不想用 ViewModel 的可以直接拿 new LivePagedListBuilder<Integer, Student>(factory, 20).build();

RecyclerView
public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    RecyclerPagingAdapter recyclerPagingAdapter;
    StudentViewModel viewModel;

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

        recyclerView =  findViewById(R.id.recycle_view);
        recyclerPagingAdapter = new RecyclerPagingAdapter();

        // 初始化 ViewModel
        viewModel = new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory())
                .get(StudentViewModel.class);

        // LiveData 观察者感应更新
        viewModel.getListLiveData().observe(this, new Observer<PagedList<Student>>() {
            @Override
            public void onChanged(PagedList<Student> students) {
                // 再这里更新适配器数据
                recyclerPagingAdapter.submitList(students);
            }
        });

        recyclerView.setAdapter(recyclerPagingAdapter);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
    }
}

Activity 中就是很基础的使用方式,adapter 设置数据不再是自定义 setData 而是通过 submitList方法来实现;

数据源 ItemKeyedDataSource 的基础使用

需要我们继承 ItemKeyedDataSource 类;

/**
 * ItemKeyedDataSource<Key, Value>:适用于目标数据的加载依赖特定 item 的信息,
 * 即 Key 字段包含的是 Item 中的信息,比如需要根据第 N 项的信息加载第 N+1 项的数据,传参中需要传入第N 项的 ID,
 * 该场景多出现于论坛类应用评论信息的请求。
 */
public class CustomItemDataSource extends ItemKeyedDataSource<Integer, Person> {

    private DataRepository dataRepository;

    CustomItemDataSource(DataRepository dataRepository) {
        this.dataRepository = dataRepository;
    }

    // loadInitial 初始加载数据
    @Override
    public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Person> callback) {
        List<Person> dataList = dataRepository.initData(params.requestedLoadSize);
        callback.onResult(dataList);
    }

    @NonNull
    @Override
    public Integer getKey(@NonNull Person item) {
        return (int) System.currentTimeMillis();
    }

    // loadBefore 向前分页加载数据
    @Override
    public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Person> callback) {
        List<Person> dataList = dataRepository.loadPageData(params.key, params.requestedLoadSize);
        if (dataList != null) {
            callback.onResult(dataList);
        }
    }

    // loadAfter 向后分页加载数据
    @Override
    public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Person> callback) {
        List<Person> dataList = dataRepository.loadPageData(params.key, params.requestedLoadSize);
        if (dataList != null) {
            callback.onResult(dataList);
        }
    }
}

这里主要的区别是 loadBefore和 loadAfter方法,向前分页加载数据和向后分页加载数据;

params.key 以及 params.requestedLoadSize 是由 Paging 框架帮我们算好并返回的;

这个通常是 一次加一条数据

就像下面这样,每次都加载一条数据进来;

在这里插入图片描述

数据源 PageKeyedDataSource 的基础使用

需要我们继承 PageKeyedDataSource 类

/**
 * PageKeyedDataSource<Key, Value>:适用于目标数据根据页信息请求数据的场景,
 * 即 Key 字段是页相关的信息。比如请求的数据的参数中包含类似next / pervious页数的信息。
 */
public class CustomPageDataSource extends PageKeyedDataSource<Integer, Person> {

    private DataRepository dataRepository;

    CustomPageDataSource(DataRepository dataRepository) {
        this.dataRepository = dataRepository;
    }

    // loadInitial 初始加载数据
    @Override
    public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Integer, Person> callback) {
        List<Person> dataList = dataRepository.initData(params.requestedLoadSize);
        callback.onResult(dataList, 0, 2);
    }

    // loadBefore 向前分页加载数据
    @Override
    public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, Person> callback) {
        List<Person> dataList = dataRepository.loadPageData(params.key, params.requestedLoadSize);
        if (dataList != null) {
            callback.onResult(dataList, params.key - 1);
        }
    }

    // loadAfter 向后分页加载数据
    @Override
    public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, Person> callback) {
        List<Person> dataList = dataRepository.loadPageData(params.key, params.requestedLoadSize);
        if (dataList != null) {
            callback.onResult(dataList, params.key + 1);
        }
    }
}

区别在于 onResult 传递的参数不一样了,每个方法的 onResult 中都额外添加了参数,用来表示要加载的目标页面数据;

这个通常是 一次加一页数据

一次加载一页,这个有点类似下面这样,点击1 加载1页面内容,点击2 加载2页面内容

在这里插入图片描述

结合 Room 使用

Room 天然的支持了 Paging,也就是 Dao 中支持了返回 DataSource.Factory

@Dao
public interface StudentDao {
    @Insert
    void insertStudents(Student... students);

    @Query("DELETE FROM student_table")
    void deleteAllStudents();

    @Query("SELECT * FROM student_table ORDER BY id")
    DataSource.Factory<Integer, Student> getAllStudents();
}

所以,我们在构建 LivePagedListBuilder 的时候,可以直接传入

studentsDatabase = StudentsDatabase.getInstance(this);
studentDao = studentsDatabase.getStudentDao();
// 直接通过 Room 获取返回的 Factory 然后传给 LivePagedListBuilder
allStudentsLivePaged = new LivePagedListBuilder<>(studentDao.getAllStudents(), 2)
        .build();
allStudentsLivePaged.observe(this, new Observer<PagedList<Student>>() {
    @Override
    public void onChanged(final PagedList<Student> students) {
        pagedAdapter.submitList(students);
    }
});

直接通过 Room 获取返回的 Factory 然后传给 LivePagedListBuilder;

原理篇


我们就从数据的初始化作为入口来分析,我们进入 new LivePagedListBuilder<>(studentDao.getAllStudents(), 2).build()的 build 方法看下:

private static <Key, Value> LiveData<PagedList<Value>> create(
        @Nullable final Key initialLoadKey,
        @NonNull final PagedList.Config config,
        @Nullable final PagedList.BoundaryCallback boundaryCallback,
        @NonNull final DataSource.Factory<Key, Value> dataSourceFactory,
        @NonNull final Executor notifyExecutor,
        @NonNull final Executor fetchExecutor) {
    return new ComputableLiveData<PagedList<Value>>(fetchExecutor) {
        @Nullable
        private PagedList<Value> mList;
        @Nullable
        private DataSource<Key, Value> mDataSource;

        private final DataSource.InvalidatedCallback mCallback =
                new DataSource.InvalidatedCallback() {
                    @Override
                    public void onInvalidated() {
                        invalidate();
                    }
                };

        @SuppressWarnings("unchecked") // for casting getLastKey to Key
        @Override
        protected PagedList<Value> compute() {
            @Nullable Key initializeKey = initialLoadKey;
            if (mList != null) {
                initializeKey = (Key) mList.getLastKey();
            }

            do {
                if (mDataSource != null) {
                    mDataSource.removeInvalidatedCallback(mCallback);
                }

                mDataSource = dataSourceFactory.create();
                mDataSource.addInvalidatedCallback(mCallback);

                mList = new PagedList.Builder<>(mDataSource, config)
                        .setNotifyExecutor(notifyExecutor)
                        .setFetchExecutor(fetchExecutor)
                        .setBoundaryCallback(boundaryCallback)
                        .setInitialKey(initializeKey)
                        .build();
            } while (mList.isDetached());
            return mList;
        }
    }.getLiveData();
}

这里直接 new 了一个 ComputableLiveData 我们进入这个 ComputableLiveData 的构造方法看下:

public ComputableLiveData(@NonNull Executor executor) {
    mExecutor = executor;
    mLiveData = new LiveData<T>() {
        @Override
        protected void onActive() {
            mExecutor.execute(mRefreshRunnable);
        }
    };
}

这里直接创建了 LiveData 通过前面我们学习 LiveData 可以知道,当有观察者订阅的时候,这个 onActive会执行,我们进入这个 mRefreshRunnable 看下:

final Runnable mRefreshRunnable = new Runnable() {
    @WorkerThread
    @Override
    public void run() {
        boolean computed;
        do {
            computed = false;
            if (mComputing.compareAndSet(false, true)) {
                try {
                    T value = null;
                    while (mInvalid.compareAndSet(true, false)) {
                        computed = true;
                        value = compute();
                    }
                    if (computed) {
                        mLiveData.postValue(value);
                    }
                } finally {
                    mComputing.set(false);
                }
            }
        } while (computed && mInvalid.get());
    }
};

最终会通过 mLiveData.postValue(value)将我们的数据发送出去,这样我们就在 observe回调方法中接收到这个数据了,那么这个数据是怎么构建的呢?通过上面的 value = compute()可以知道,最终是调用的我们重写的 compute 方法,也就是我们创建 ComputableLiveData 的时候重写的 compute 方法;

我们进入这个方法看下:

protected PagedList<Value> compute() {
    @Nullable Key initializeKey = initialLoadKey;
    .... 省略部分代码

    do {
        .... 省略部分代码
        // 核心逻辑1 创建 DataSource
        mDataSource = dataSourceFactory.create();
        
        .... 省略部分代码
        
        // 核心逻辑2 构建 PagedList
        mList = new PagedList.Builder<>(mDataSource, config)
                .setNotifyExecutor(notifyExecutor)
                .setFetchExecutor(fetchExecutor)
                .setBoundaryCallback(boundaryCallback)
                .setInitialKey(initializeKey)
                .build();
    } while (mList.isDetached());
    return mList;
}

核心逻辑1,最终调用的是我们自己创建的 DataSource 的 create 方法,也就是 StudentDataSourceFactory 中的 create 方法,获取 StudentDataSource;

核心逻辑2:构建 PagedList,我们进入这个 build方法看下:

public PagedList<Value> build() {
    return PagedList.create(
            mDataSource,
            mNotifyExecutor,
            mFetchExecutor,
            mBoundaryCallback,
            mConfig,
            mInitialKey);
}

这里直接调用了 PagedList 的 create方法,我们进入这个方法看下:

static <K, T> PagedList<T> create(@NonNull DataSource<K, T> dataSource,
        @NonNull Executor notifyExecutor,
        @NonNull Executor fetchExecutor,
        @Nullable BoundaryCallback<T> boundaryCallback,
        @NonNull Config config,
        @Nullable K key) {
    if (dataSource.isContiguous() || !config.enablePlaceholders) {
    
        .... 省略部分代码
        
        ContiguousDataSource<K, T> contigDataSource = (ContiguousDataSource<K, T>) dataSource;
        return new ContiguousPagedList<>(contigDataSource,
                notifyExecutor,
                fetchExecutor,
                boundaryCallback,
                config,
                key,
                lastLoad);
    } else {
        return new TiledPagedList<>((PositionalDataSource<T>) dataSource,
                notifyExecutor,
                fetchExecutor,
                boundaryCallback,
                config,
                (key != null) ? (Integer) key : 0);
    }
}

这个方法返回了两种 PagedList,一种是 ContiguousPagedList 一种是 TiledPagedList

我们通过查看 PagedList 的子类,其实可以看到

在这里插入图片描述

通过复写 isContiguous这方法,可以知道 ContiguousPagedList 直接返回了 true,TiledPagedList 直接返回了 false;

我们进入 ContiguousPagedList 的构造方法看下:

ContiguousPagedList(
        @NonNull ContiguousDataSource<K, V> dataSource,
        @NonNull Executor mainThreadExecutor,
        @NonNull Executor backgroundThreadExecutor,
        @Nullable BoundaryCallback<V> boundaryCallback,
        @NonNull Config config,
        final @Nullable K key,
        int lastLoad) {
    super(new PagedStorage<V>(), mainThreadExecutor, backgroundThreadExecutor,
            boundaryCallback, config);
    mDataSource = dataSource;
    mLastLoad = lastLoad;

    if (mDataSource.isInvalid()) {
        detach();
    } else {
        // 核心逻辑
        mDataSource.dispatchLoadInitial(key,
                mConfig.initialLoadSizeHint,
                mConfig.pageSize,
                mConfig.enablePlaceholders,
                mMainThreadExecutor,
                mReceiver);
    }
    mShouldTrim = mDataSource.supportsPageDropping()
            && mConfig.maxSize != Config.MAX_SIZE_UNBOUNDED;
}

这里调用了 dispatchLoadInitial方法,我们进入这个方法看下:
在这里插入图片描述

这个方法也是提供了三个实现,我们需要进入的是 ContiguousWithoutPlaceholdersWrapper 进入之后,可以看到,它其实是 PositionalDataSource 的一个内部类,而 PositionalDataSource 又是我们前面使用的 DataSource 之一;

我们进入这个 PositionalDataSource 的 dispatchLoadInitial方法看下:

void dispatchLoadInitial(@Nullable Integer position, int initialLoadSize, int pageSize,
        boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
        @NonNull PageResult.Receiver<Value> receiver) {
    final int convertPosition = position == null ? 0 : position;
    // 核心逻辑
    mSource.dispatchLoadInitial(false, convertPosition, initialLoadSize,
            pageSize, mainThreadExecutor, receiver);
}

这里也是直接调用了 dispatchLoadInitial方法,我们进入看下:

final void dispatchLoadInitial(boolean acceptCount,
        int requestedStartPosition, int requestedLoadSize, int pageSize,
        @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) {
    // 核心逻辑    
    LoadInitialCallbackImpl<T> callback =
            new LoadInitialCallbackImpl<>(this, acceptCount, pageSize, receiver);
    LoadInitialParams params = new LoadInitialParams(
            requestedStartPosition, requestedLoadSize, pageSize, acceptCount);
    // 核心逻辑        
    loadInitial(params, callback);
    callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
}

可以看到,这个 loadInitial是一个抽象方法,由我们具体实现的 DataSource 来实现,也就是我们的 StudentDataSource 来实现;

最终,我们的初始化数据,通过 onResult 回传回去;

@Override
public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback<Student> callback) {
    // @1:数据源 @2:位置 @3:总大小
    callback.onResult(getStudents(0, Flag.SIZE), 0, 1000);
}

我们接下来看下,这个 callback 是如何将数据回调回去的,由 dispatchLoadInitial方法可以知道,LoadInitialCallback 有一个实现类 LoadInitialCallbackImpl

LoadInitialCallbackImpl<T> callback =
            new LoadInitialCallbackImpl<>(this, acceptCount, pageSize, receiver);

我们进入实现类的 onResult 方法看下:

public void onResult(@NonNull List<T> data, int position, int totalCount) {
    if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
        LoadCallbackHelper.validateInitialLoadParams(data, position, totalCount);
        // 核心逻辑,if else 中最终都是调用的 dispatchResultToReceiver
        if (mCountingEnabled) {
            int trailingUnloadedCount = totalCount - position - data.size();
            mCallbackHelper.dispatchResultToReceiver(
                    new PageResult<>(data, position, trailingUnloadedCount, 0));
        } else {
            mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, position));
        }
    }
}

if else 中最终都是调用的 dispatchResultToReceiver方法,我们进入这个方法看下:

void dispatchResultToReceiver(final @NonNull PageResult<T> result) {
    Executor executor;
    synchronized (mSignalLock) {
        mHasSignalled = true;
        executor = mPostExecutor;
    }

    if (executor != null) {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                mReceiver.onPageResult(mResultType, result);
            }
        });
    } else {
        mReceiver.onPageResult(mResultType, result);
    }
}

最终都是调用的 onPageResult方法,只不过一个是子线程,一个是主线程,我们进入这个方法看下:

PageResult.Receiver<V> mReceiver = new PageResult.Receiver<V>() {
    @AnyThread
    public void onPageResult(int resultType, @NonNull PageResult<V> pageResult) {
        if (pageResult.isInvalid()) {
            ContiguousPagedList.this.detach();
        } else if (!ContiguousPagedList.this.isDetached()) {
            List<V> page = pageResult.page;
            if (resultType == 0) {
                // 核心逻辑1
                ContiguousPagedList.this.mStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls, pageResult.positionOffset, ContiguousPagedList.this);
                .... 省略部分代码
            } else {
                .... 省略部分代码
                if (resultType == 1) {
                    if (skipNewPage && !trimFromFront) {
                        .... 省略部分代码
                    } else {
                        // 核心逻辑2
                        ContiguousPagedList.this.mStorage.appendPage(page, ContiguousPagedList.this);
                    }
                } else {
                    .... 省略部分代码
                    if (skipNewPage && trimFromFront) {
                        .... 省略部分代码
                    } else {
                        // 核心逻辑3
                        ContiguousPagedList.this.mStorage.prependPage(page, ContiguousPagedList.this);
                    }
                }
                .... 省略部分代码
            }
            .... 省略部分代码
        }
    }
};

三个比较核心的调用 初始化数据:this.mStorage.init ,向下翻页:this.mStorage.appendPage ,向上翻页:this.mStorage.prependPage

我们主要来看下 init方法

void init(int leadingNulls, @NonNull List<T> page, int trailingNulls, int positionOffset,
        @NonNull Callback callback) {
    init(leadingNulls, page, trailingNulls, positionOffset);
    // 核心逻辑
    callback.onInitialized(size());
}

我们接着看下 onInitialized的具体实现:

public void onInitialized(int count) {
    // 核心逻辑
    notifyInserted(0, count);
    mReplacePagesWithNulls =
            mStorage.getLeadingNullCount() > 0 || mStorage.getTrailingNullCount() > 0;
}

这里调用了 notifyInserted方法,我们进入看下:

void notifyInserted(int position, int count) {
    if (count != 0) {
        for (int i = mCallbacks.size() - 1; i >= 0; i--) {
            final Callback callback = mCallbacks.get(i).get();
            if (callback != null) {
                // 核心逻辑
                callback.onInserted(position, count);
            }
        }
    }
}

最终调用的是 onInserted方法,我们进入这个方法看下:

public AsyncPagedListDiffer(@NonNull ListUpdateCallback listUpdateCallback, @NonNull AsyncDifferConfig<T> config) {
    class NamelessClass_1 extends PagedList.Callback {
        public void onInserted(int position, int count) {
            // 核心逻辑
            AsyncPagedListDiffer.this.mUpdateCallback.onInserted(position, count);
        }
        .... 省略部分代码
    }
    .... 省略部分代码
}

我们继续进入这个 onInserted方法看下

在这里插入图片描述

可以看到,这个 onInserted 方法有多个实现,最终指向的是 AdapterListUpdateCallback 中的 onInserted方法;

public void onInserted(int position, int count) {
    mAdapter.notifyItemRangeInserted(position, count);
}

最终调用到了 RecyclerView 的 Adapter 的 notifyItemRangeInserted插入数据的方法,来将初始化数据插入到对应的位置;

这是使用 Paging 来完成数据到 RecyclerView 的填充,当我们结合 Room 使用的时候,是通过

studentsDatabase = StudentsDatabase.getInstance(this);
studentDao = studentsDatabase.getStudentDao();
allStudentsLivePaged = new LivePagedListBuilder<>(studentDao.getAllStudents(), 2)
        .build();
allStudentsLivePaged.observe(this, new Observer<PagedList<Student>>() {
    @Override
    public void onChanged(final PagedList<Student> students) {
        pagedAdapter.submitList(students);
    }
});

我们直接构建的 LivePagedListBuilder,然后在 onChanged中调用 submitList来实现数据到 RecyclerView 的填充;

submitList最终也是会调用到 onInserted 方法;

mUpdateCallback.onInserted(0, pagedList.size());

总结:本质上就是用 DataSource 来对接开发者的不同需求,然后用不同的 PagedList 来封装不同的数据,不管封装的是哪一种数据,最终通过 AsyncPagedListDiffer (这个同步机制) 来和 RecyclerView 的 API 对接;

好了,Paging 就讲到这里吧~

欢迎三连


来都来了,点个关注,点个赞吧,你的支持是我最大的动力~

DGX Spark上PyTorch GPU环境配置实战:CUDA 13.0与ARM架构全流程 在深度学习中,CUDA作为GPU加速计算的底层基础,其版本与深度学习框架(如PyTorch)的匹配直接影响模型训练的效率与稳定性。GPU环境配置不仅涉及驱动、CUDA工具链与框架运行时的协同,还依赖于硬件架构与预构建包的兼容性。在AI开发机Ubuntu系统上,正确对齐CUDA版本、计算能力架构及安装源,是避免环境故障、发挥硬件算力的关键。无论是面向个人AI超算还是通用GPU服务器,掌握版本对齐、路径隔离与验证方法,都能大幅提升开发效率。本文以DGX Spark为例,详解从环境摸底到PyTorch GPU版本 阅读详情

相关推荐

HBuilderx 坑 之前没问题 改了几行代码 小程序上传报分包大小超过限制

Error: 分包大小超过限制,main package source size 4199KB exceed max limit 2MB [20221213 10:08:06][wx432abdb518bdc1eb]下面的我提示是都没超过2M,却提示我超过限制,代码改了不到20行吧,之前是正常上传的.....,比如这类,要压缩的,前提是没压缩过的,但是我肯定提交的时候是压缩的,没勾选此项的要注意,如果本文解决了你的困扰 来波666不为过吧!

爱就码上行动,奔赴热爱,追光而行。愿你带着从容与热爱,拥抱每一天的惊喜与美好 5219

Android架构之Paging组件(一)

Paging组件的意义 分页加载是在应用程序开发过程中十分常见的需求。我们经常需要以列表的形式加载大量的数据,一次性加载所有的数据,必然会消耗大量的时间和数据流量。然而用户只需要部分数据。这时候就有了分页加载。分页加载是对数据进行按需加载。 分页加载有两种模式: 传统的上拉加载更多 无限滚动分页效果 Paging组件就是基于无限滚动模式而设计的。 比如说:京东的App就是就是基于无限滚动模式进行分页加载的。当我们滑动到一定量的数据的时候,会自动请求加载下一页的数据。这让用户会感觉到,所有的数据都是一次

qq_43404873的博客 3579

直流电动机的建模仿真(1)他励,串励和并励电动机建模仿真

本文根据不同励磁方式的直流电动机的接线原理图,建立了相应的数学模型,然后根据与数学模型对应的等效电路以及模拟机械运动方程的等效电路,用Matlab/simulink中的SpecializedPower system库中的电路元件,建立了他励,并励和串励直流电动机的全等效电路仿真模型,经过与Matlab自带的直流电动机仿真模型比较以及仿真实例验证,所建立的仿真模型时完全正确的。

weixin_45670943的博客 7516

JetPack--Paging2

Paging是一个用于分页加载的组件,对于一些列表数据,以前大家都使用过的一种方式是定义一个page,当达到加载更多条件时,page加一再去请求数据,为此我们要写很多重复的代码,Paging就对分页进行了一个封装 Paging由分为三个模块 1.DataSource:数据从该模块中获取,数据可以来源于网络、本地数据库等 2.PagedList:负责具体获取数据的逻辑,何时获取、加载下一页、预加载等...

aruba的博客 439

OpenWebUI外部模型配置异常排查与修复指南

本文详细解析了OpenWebUI外部模型配置异常的常见问题及解决方案,包括隐藏字符、数据库锁死等典型现象。通过错误日志分析和数据库修复实战指南,帮助开发者快速定位并解决配置问题,确保OpenWebUI与第三方大模型的稳定连接。

weixin_29205061的博客 310

labview汽车电子通用测试程序架构,CAN通讯上位机

结合做过的汽车电子测试项目,车身控制器测试,CAN网络模块测试,电机控制器功能测试,汽车仪表盘视觉及功能测试、电气盒功能测试、EOL下线检测、车用空调控制器功能测试等项目,结合实际项目,整理编写的一个汽车电子,通用测试架构,包含项目的讲解等。6、数据保存 、数据处理、 曲线显示 、报警处理等。7、CAN总线故障信息监控及记录保存。2、参数设置(CAN端口及参数)5、CAN通讯+数据解析。

ali2805486883的博客 2753

反思|Android 列表分页组件Paging的设计与实现:系统概述

本文将对Paging分页组件的设计和实现进行一个系统整体的概述,强烈建议 读者将本文作为学习Paging 阅读优先级最高的文章,所有其它的Paging中文博客阅读优先级都应该靠后。

却把清梅嗅的博客 2435

使用Qt编译Qt WebEngine

这就是使用Qt编译Qt WebEngine模块的基本步骤。通过按照上述步骤,您应该能够成功编译和使用Qt WebEngine来嵌入Web内容到您的Qt应用程序中。Qt WebEngine是Qt框架的一个模块,它提供了一个现代的Web浏览器引擎,可以在Qt应用程序中嵌入Web内容。首先,需要获取Qt的源代码。如果尚未安装,可以从Qt官方网站下载并安装Qt。完成Qt的安装后,您可以构建并运行Qt WebEngine示例来验证编译是否成功。这将把编译好的Qt库文件和头文件安装到之前指定的安装目录中。

DevForge的博客 3016

Jetpack插件化学习之AndroidX Paging 大数据列表加载库一

Jetpack插件化学习之AndroidX Paging 大数据列表加载库一 本文根据以下思路进行Paging的学习: Paging介绍 Paing核心组件 总结梳理 Paging介绍 分页库使您可以更轻松地在应用程序中逐步和优雅地加载数据 RecyclerView。 许多应用程序使用包含大量项目的数据源中的数据,但一次只显示一小部分。 分页库可帮助您的应用观察并显示此数据的合理子集。此功能有...

边城晨的专栏 1339

企业级 Multi-Agent 实施指南:从需求拆解到上线验收的全流程方法论

本文基于12个不同行业(金融、制造、零售、互联网)的百万级Multi-Agent投产项目经验,提炼出一套可复用的全流程实施SOP,覆盖需求对齐与拆解、架构设计、开发实现、测试优化、上线部署、验收迭代6个核心阶段,包含标准化工具、量化指标、踩坑指南、可直接运行的代码示例,帮你避开90%的落地坑。需求阶段是项目成败的核心,必须对齐业务方的量化指标,明确系统边界架构设计要兼顾性能、安全、可扩展性,不要为了炫技选择复杂的框架工程化是投产的关键,测试、灰度、监控、降级机制缺一不可。

AI云原生与云计算技术学院 223

Android 10.0 kenel中修改rom系统内部存储的大小

在10.0的系统rom产品开发定制中,在对一些产品开发中的配置需求方面,由于在产品后续订单中,有些产品是出口的,但是硬件方面已经定板,时间比较仓促,所以 就需要软件方面在rom内部存储的大小方面作假,修改rom真实的大小容量,所以就需要在kenel驱动部分来修改这部分的值最好了,接下来分析下计算rom容量的 相关代码,然后做出修改

安卓兼职framework和app工程师的博客 1404

Jetpack Architecture系列教程之(六)——Paging分页库

Paging分页库可用于一次性数据和块数据显示的加载优化,按需加载部分数据减少网络带宽和系统资源的使用。它可以优化Sql数据的加载、也可以优化Network数据的加载。实际应用中,通常是结合RecyclerView使用。

彩蛋在每篇文章的最后名片 579

Hermes Agent 安装部署完全指南

Hermes Agent安装部署指南提供了两种安装方式:一键脚本安装和手动分步安装。该AI助手支持多平台运行(Linux/macOS/WSL2),核心功能仅需Git作为前置依赖,其他依赖如Python、Node.js等均由安装脚本自动处理。文档详细介绍了系统要求、依赖管理、配置目录结构、环境变量设置以及10个手动安装步骤,并提供了功能模块化选项,用户可根据需求选择安装语音交互、浏览器自动化等扩展功能。安装完成后可通过hermes doctor命令验证环境,并通过交互式向导完成初始化配置。

简简单单Onlinezuozuo 2618

Android jetpack最佳总结和实践,字节跳动面试官

2)类图Lifecycle组件成员Lifecycle被定义成了抽象类,LifecycleOwner、LifecycleObserver被定义成了接口。组件(Activity、Fragment)实现了LifecycleOwner接口,该只有一个返回Lifecycle对象的方getLifecyle(): LifecycleRegistry。Lifecycle的内部类State标明状态、Event表示事件。

2301_79056696的博客 583

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

面试官:作为一名Android开发者,连jetpack都不懂?你还是改行吧!

JetPack自2018年面世,迎来了它的第三个年头。但是在国内的受欢迎程度远远及不上国外,国内开发者一听到别人说JetPack,脑海中蹦出来的词就是“不成熟”、“不好用”、“调试有点难”,但是事实真的是如此嘛? 我们首先来看看JetPack在发布的时候的雄心壮志: 以上来自谷歌语录,总结起来就是: JetPack是一整套组件库 它能帮我们写出优质应用 它能让我们不需要去写毫无营养的样板代码 4. 它能规范我们的开发行为 接下来来看看JetPack组件库到底包含了哪些组件: 从上图我们

Android578的博客 418

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

不会吧!做Android开发还不会Jetpack架构组件?快来看看这篇文章吧!

1.Hilt - Jetpack 推荐的依赖注入类库2.Paging3 - 逐步加载和显示数据3.App Startup - 在应用启动时初始化组件5.更简单的动画 — core-animation 和 SeekableAnimatedVectorDrawable6.使用 Database Inspector 调试数据库。

2401_84545046的博客 597

Android Jetpack从入门到精通,干货满满

11.1 Hilt - Jetpack 推荐的依赖注入类库11.2 Paging3 - 逐步加载和显示数据11.3 App Startup - 在应用启动时初始化组件11.5 更简单的动画 — core-animation 和 SeekableAnimatedVectorDrawable。

erhtre的博客 1452

阿里5次面试碰壁,100天深造-Jetpack架构组件从入门到精通,险获腾讯年薪45w+offer!

Jetpack是一套库、工具和指南,可以帮助开发者更轻松地编写优质应用。这些组件可以帮助开发者遵循最佳做法、让开发者摆脱编写样板代码的工作并简化复杂任务,以便开发者将精力集中放在所需的代码上。我见过很多技术leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了7、8年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。问到这些人的职业规划时,他们也没有太多想法。

2401_84132204的博客 1005

面试官:“会不会熟练使用Jetpack” 我:“......”

前言 金九银十的招聘旺季已经快接近尾声,相信有不少读者正在抓紧机会面试。 Android 市场已今非昔比。在过去,迫于招人的压力,应试者只需了解四大组件、视图、网络请求,即可谋得一份满意的工作。 现如今面向标准化开发已成现实,Jetpack 架构组件及标准化开发模式的确立,意味着Android开发已步入成熟阶段。熟练使用Jetpack也成为了招聘要求中必不可少的一项: 如何快速且熟练的使用Jetpack呢? Jetpack,它是一套组件、工具和指导;是谷歌正式推出的一整套用来构建APP开发的组件。 对于产

datian1234的博客 1976

如何快速上手Jetpack?Jetpack入门到精通再到(网易云)强化实战,Github已标星80K+

前言 金九银十的招聘旺季已经快接近尾声,相信有不少读者正在抓紧机会面试。 Android 市场已今非昔比。在过去,迫于招人的压力,应试者只需了解四大组件、视图、网络请求,即可谋得一份满意的工作。 现如今面向标准化开发已成现实,Jetpack 架构组件及标准化开发模式的确立,意味着Android开发已步入成熟阶段。熟练使用Jetpack也成为了招聘要求中必不可少的一项: 为什么要用Jetpack? 许多样板代码不再需要开发者手写,而是可以通过模版工具自动生成,在取缔繁杂耗时的重复工作的同时,避免因人工操作的

datian1234的博客 927

Android Jetpack 架构组件最佳实践之“网抑云”APP,2021吊打面试官系列

一、初识ConstraintLayout之实现登录页面 1.创建项目 2.沉浸式的布局 3.富文本 4.属性动画 二、Navigation实践之实现APP主框架以及Navigation的相关介绍 1.搭建 Bottom Navigation Activity 2.导航界面跳转 3.Navigation传值 4.Navigation跳转动画 5.导航文件拆分 6.Deeplink导航 三、使用 Coroutines, Retrofit, Moshi实现网络数据请求 1.kotlin - Corout

m0_66145114的博客 347

爱了爱了!Android开发者值得深入思考的几个问题,送大厂面经一份!

前段时间有人问我:「你是怎么成为一名软件架构师的?」我们就此探讨了必备技能、经验,以及储备相关知识所需的时间和精力。除此之外,我也回顾了自己走过的路、使用或尝试过的技术,以及我从那些五花八门的工作中学到的东西。 Android Jetpack组件的作用是什么? Navigation:一个用于管理Fragment切换的工具类,可视化、可绑定控件、支持动画等是其优点。 Data Binding:不用说,都知道,加速MVVM的创建。 Lifecycle:他是我们能够处理Activity和Fragme

Sunbuyi的博客 113

Android Jetpack 架构组件最佳实践之“网抑云”APP

上面这些公司都是时下最受欢迎的互联网大厂,他们的职级、薪资、福利也都讲的差不多了,相信大家都是有梦想和野心的人,心里多少应该都有些想法。也相信很多人也都在为即将到来的金九银十做准备,也有不少人的目标都是这些公司。我这边有不少朋友都在这些厂工作,其中也有很多人担任过面试官,上面的资料也差不多都是从朋友那边打探来的。除了上面的信息,我这边还有这些大厂近年来的面试真题及解析,以及一些朋友出于兴趣和热爱一起整理的Android时下热门知识点的学习资料。

2401_83916204的博客 691

全世界都在问Android开发凉了吗?建议收藏

开头 面试时间:2021.2.9 1~3面、2021.2.13 4~6面、2021.2.26 HR面 面试部门 + 岗位:商业化 - 高级 Android 开发工程师 面试感想:整体面得比较累,基础面、交叉面、Boss面,前前后后对接了 6 个面试官 (离当初给我说的 3面+HR面 貌似差得有点远¬_¬) 。算法到 Boss 面都还在写,不过庆幸的是面试官没有为难我 (老实交代了算法没怎么准备,哎…),算法都不太难。整体项目比基础问得多。 面试建议:算法、基础是敲门砖,项目是试金石,良好的面试形象是加分项

m0_56222134的博客 101

这些面试官常问的开发面试题你都掌握好了吗?安卓系列学习进阶视频

导语 又到了面试季,自己也不得不参与到这场战役中来,其实是从去年底就开始看,Android的好机会确实不太多,但也还好,3年+的android开发经历还是有一些面试机会的,不过确实不像几年前门槛那么低了,总的体会就是小的创业公司比较注重你的项目经历是否和自己的贴合,直接能过来独当一面。 大厂除了看中项目经历外,还比较注重你知识面的广度,是广度、深度和解决方案等多方面的考察,平时够工作要好好积累临时刷题只聊点皮毛估计是过不了关的。下面就总结一些大厂面试遇到必定会问的知识点,我把网络上讲解的最好的视频给大家整理

m0_52308677的博客 104
上一篇: 如何应对 Android 面试官 -> 玩转 Jetpack WorkManager
下一篇: 如何应对 Android 面试官 -> 运用 Jetpack 写一个音乐播放器(一)基础搭建
老A说
博客等级 码龄14年 1181粉丝 85原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值