Android Room联合AsyncListUtil实现RecyclerView分页加载ORM数据

简介: Android Room联合AsyncListUtil实现RecyclerView分页加载ORM数据我之前写了一系列关于AsyncListUtil实现RecyclerView和ListView的分页加载机制和技术路线,见附录文章4,5。
Android Room联合AsyncListUtil实现RecyclerView分页加载ORM数据

我之前写了一系列关于AsyncListUtil实现RecyclerView和ListView的分页加载机制和技术路线,见附录文章4,5。同时也写了一些列文章介绍Android官方推出的ORM数据库:Room技术,见附录文章1,2。现在结合Android分页加载框架AsyncListUtil,以及Android官方ORM数据库Room,实现数据库数据分页加载到RecyclerView里面。
先给出一个例子,实现一个简单功能,在Android Room数据库中增加一批数据,然后在RecyclerView滚动时候,触发分页加载逻辑,把数据库中的数据分页分段的加载出来,本例中存储的数据为User。
MainActivity.java:
package zhangphil.demo;

import android.arch.persistence.room.Room;
import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.util.AsyncListUtil;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;

import java.util.List;

public class MainActivity extends AppCompatActivity {
    private final String TAG = "输出";

    private RecyclerView.Adapter mAdapter;
    private LinearLayoutManager mLinearLayoutManager;
    private AsyncListUtil<User> mAsyncListUtil;

    private final int LIMIT = 10;

    private UserDatabase mUserDatabase;
    private UserDao mUserDao;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mUserDatabase = Room.databaseBuilder(getApplicationContext(), UserDatabase.class, "users").build();
        mUserDao = mUserDatabase.getUserDao();

        setContentView(R.layout.activity_main);

        RecyclerView mRecyclerView = findViewById(R.id.recycler_view);

        mLinearLayoutManager = new LinearLayoutManager(this);
        mLinearLayoutManager.setOrientation(LinearLayout.VERTICAL);
        mRecyclerView.setLayoutManager(mLinearLayoutManager);

        mAdapter = new MyAdapter();
        mRecyclerView.setAdapter(mAdapter);

        MyViewCallback mViewCallback = new MyViewCallback();
        MyDataCallback mDataCallback = new MyDataCallback();
        mAsyncListUtil = new AsyncListUtil<>(User.class, LIMIT, mDataCallback, mViewCallback);

        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                Log.d(TAG + "onScrollStateChanged", "onRangeChanged");
                mAsyncListUtil.onRangeChanged();
            }
        });

        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d(TAG + "刷新", "refresh");
                mAsyncListUtil.refresh();
            }
        });

        findViewById(R.id.add_data).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        writeDatabase();
                    }
                }).start();
            }
        });

        //主动刷新数据。
        new Thread(new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(500);
                mAsyncListUtil.refresh();
            }
        }).start();
    }

    private class MyDataCallback extends AsyncListUtil.DataCallback<User> {

        @Override
        public int refreshData() {
            //更新数据的元素个数。
            Log.d(TAG + "refreshData", Integer.MAX_VALUE + "");
            return Integer.MAX_VALUE;
        }

        /**
         * 在这里完成耗时的数据加载的耗时任务。
         *
         * @param data
         * @param startPosition
         * @param itemCount
         */
        @Override
        public void fillData(User[] data, int startPosition, int itemCount) {
            Log.d(TAG + "fillData", startPosition + " , " + itemCount);

            List<User> list = mUserDao.getUserWhereUserIdBigThan(startPosition, itemCount);
            for (int i = 0; i < list.size(); i++) {
                data[i] = list.get(i);
            }
        }
    }

    private class MyViewCallback extends AsyncListUtil.ViewCallback {

        @Override
        public void getItemRangeInto(int[] outRange) {
            outRange[0] = mLinearLayoutManager.findFirstVisibleItemPosition();
            outRange[1] = mLinearLayoutManager.findLastVisibleItemPosition();

            Log.d(TAG + "getItemRangeInto", outRange[0] + " ~ " + outRange[1]);
        }

        @Override
        public void onDataRefresh() {
            mAdapter.notifyItemRangeChanged(mLinearLayoutManager.findFirstVisibleItemPosition(), LIMIT);
            Log.d(TAG + "onDataRefresh", mLinearLayoutManager.findFirstVisibleItemPosition() + "," + mLinearLayoutManager.findLastVisibleItemPosition());
        }

        @Override
        public void onItemLoaded(int position) {
            mAdapter.notifyItemChanged(position);
            Log.d(TAG + "onItemLoaded", String.valueOf(position));
        }
    }

    private class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
        public MyAdapter() {
            super();
        }

        @Override
        public MyViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
            View view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.item_layout, viewGroup, false);
            MyViewHolder holder = new MyViewHolder(view);
            return holder;
        }

        @Override
        public void onBindViewHolder(MyViewHolder viewHolder, int i) {
            User u = mAsyncListUtil.getItem(i);
            viewHolder.setData(u);
        }

        @Override
        public int getItemCount() {
            return mAsyncListUtil.getItemCount();
        }
    }

    private class MyViewHolder extends RecyclerView.ViewHolder {
        public TextView userId;
        public TextView userName;
        public TextView userAge;

        public MyViewHolder(View itemView) {
            super(itemView);

            userId = itemView.findViewById(R.id.user_id);
            userName = itemView.findViewById(R.id.user_name);
            userAge = itemView.findViewById(R.id.user_age);
        }

        public void setData(User u) {
            if (u != null) {
                userId.setText("id:" + String.valueOf(u.userId));
                userName.setText("姓名:" + String.valueOf(u.name));
                userAge.setText("年龄:" + String.valueOf(u.age));
            }
        }
    }

    private void writeDatabase() {
        Log.d(TAG + "writeDatabase", "开始写入数据...");
        for (int i = 0; i < 100; i++) {
            User user = new User();
            user.name = "张" + i;
            user.age = (int) (Math.random() * 100);
            user.updateTime = System.currentTimeMillis();

            mUserDao.insertUser(user);
        }
        Log.d(TAG + "writeDatabase", "写入数据库完毕.");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mUserDatabase.close();
    }
}


MainActivity需要的布局文件activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="更新" />

    <Button
        android:id="@+id/add_data"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="添加数据" />

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>


item_layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="20dp">

    <TextView
        android:id="@+id/user_id"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_red_light" />

    <TextView
        android:id="@+id/user_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="@android:color/holo_orange_light" />

    <TextView
        android:id="@+id/user_age"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="@android:color/holo_blue_light" />

    <View
        android:layout_width="match_parent"
        android:layout_height="1px"
        android:background="@android:color/darker_gray" />
</LinearLayout>


涉及到Android Room的Model,表,Dao。
User.java:
package zhangphil.demo;

import android.arch.persistence.room.ColumnInfo;
import android.arch.persistence.room.Entity;
import android.arch.persistence.room.PrimaryKey;

/**
 * Created by Phil on 2017/11/22.
 */

@Entity(tableName = "user_table")
public class User {
    @PrimaryKey(autoGenerate = true)
    public int userId;

    @ColumnInfo(name = "userName")
    public String name;

    @ColumnInfo(name = "userAge")
    public int age = -1;

    @ColumnInfo(name = "updateTime")
    public long updateTime = -1;
}

UserDao.java:
package zhangphil.demo;

import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Delete;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.OnConflictStrategy;
import android.arch.persistence.room.Query;
import android.arch.persistence.room.Update;

import java.util.List;

/**
 * Created by Phil on 2017/11/22.
 */

@Dao
public interface UserDao {
    @Query("SELECT * FROM user_table")
    public List<User> getAllUsers();

    @Query("SELECT * FROM user_table WHERE userId >:uid ORDER BY userId ASC LIMIT :limit")
    public List<User> getUserWhereUserIdBigThan(int uid, int limit);

    @Query("SELECT * FROM user_table WHERE userId =:uid")
    public List<User> getUserWhereUserIdEqual(int uid);

    @Query("SELECT * FROM user_table WHERE userId BETWEEN :minId AND :maxId  ORDER BY userId ASC")
    public List<User> getUserIdBetween(int minId, int maxId);

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    public void insertUser(User... users);

    @Update
    public void updateUser(User... users);

    @Delete
    public void deleteUser(User... users);
}

UserDatabase.java:
package zhangphil.demo;

import android.arch.persistence.room.Database;
import android.arch.persistence.room.RoomDatabase;

/**
 * Created by Phil on 2017/11/22.
 */

@Database(entities = {User.class}, version = 1, exportSchema = false)
public abstract  class UserDatabase  extends RoomDatabase {
    public abstract UserDao getUserDao();
}


代码运行结果,初始化:




当点击“添加数据”添加完数据库数据后,点击“更新”按钮后:



小结:
(一)在本例中,使用Android Room获得分页数据时候,和之前的UserDao相比,增加了SQL查询约束LIMIT,关于LIMIT,详情见附录文章3。从SQL取数据时,就事先作为分页,把数据分块,而不像以前那样一次性无脑取出数据库表中的全部数据。从而提高了性能。
(二)AsyncListUtil需要延迟的主动refresh(),才能再初始化后且无滚动RecyclerView时加载出来数据。本例中在MainActivity的onCreate最后,开启一个线程,该线程故意延迟一定时间,然后才启动AsyncListUtil的refresh()。之所以这么做,有部分原因是因为AsyncListUtil和RecyclerView之间,UI绘制和数据的更新没有同步,导致第一次初始化加载后,无法刷出来数据。明显的现象就是:如果数据库中有数据,初次打开整个程序,RecyclerView加载出来的每个item是空的,但是明明此时数据库中有数据,按照道理应该初始化加载出来,但是没有。追踪代码就会发现,导致这一现象发生,极大程度上是AsyncListUtil的回调先于RecyclerView完成初始化,导致onDataRefresh和getItemRangeInto
捕捉和使用的第一个和最后一个RecyclerView可见item position均为-1引起。


附录:
1,《Android官方ORM数据库Room技术解决方案:@Embedded内嵌对象(二)》链接:http://blog.csdn.net/zhangphil/article/details/78621009 
2,《Android官方ORM数据库Room技术解决方案简介(一)》链接:http://blog.csdn.net/zhangphil/article/details/78611632 
3,《SQL数据库查询LIMIT 数据分页》链接:http://blog.csdn.net/zhangphil/article/details/78653677 
4,《基于Android官方AsyncListUtil优化改进RecyclerView分页加载机制(一)》链接:http://blog.csdn.net/zhangphil/article/details/78603499 
5,《基于Android官方AsyncListUtil优化经典ListView分页加载机制(二)》链接:http://blog.csdn.net/zhangphil/article/details/78645089 

相关文章
|
8天前
|
Android开发 开发者
Android网络和数据交互: 请解释Android中的AsyncTask的作用。
Android&#39;s AsyncTask simplifies asynchronous tasks for brief background work, bridging UI and worker threads. It involves execute() for starting tasks, doInBackground() for background execution, publishProgress() for progress updates, and onPostExecute() for returning results to the main thread.
9 0
|
8天前
|
网络协议 安全 API
Android网络和数据交互: 什么是HTTP和HTTPS?在Android中如何进行网络请求?
HTTP和HTTPS是网络数据传输协议,HTTP基于TCP/IP,简单快速,HTTPS则是加密的HTTP,确保数据安全。在Android中,过去常用HttpURLConnection和HttpClient,但HttpClient自Android 6.0起被移除。现在推荐使用支持TLS、流式上传下载、超时配置等特性的HttpsURLConnection进行网络请求。
9 0
|
22天前
|
XML Java Android开发
Android每点击一次按钮就添加一条数据
Android每点击一次按钮就添加一条数据
23 1
|
1月前
|
存储 Android开发 C++
【Android 从入门到出门】第五章:使用DataStore存储数据和测试
【Android 从入门到出门】第五章:使用DataStore存储数据和测试
34 3
|
1月前
|
存储 Android开发
【Android 从入门到出门】第八章:分页入门指南
【Android 从入门到出门】第八章:分页入门指南
21 3
|
2月前
|
JavaScript Java 数据安全/隐私保护
安卓逆向 -- POST数据解密
安卓逆向 -- POST数据解密
25 2
|
3月前
|
编解码 测试技术 开发工具
如何实现Android视音频数据对接到GB28181平台(SmartGBD)
如何实现Android视音频数据对接到GB28181平台(SmartGBD)
|
3月前
|
数据采集 编解码 图形学
Android平台Unity下如何通过WebCamTexture采集摄像头数据并推送至RTMP服务器或轻量级RTSP服务
Android平台Unity下如何通过WebCamTexture采集摄像头数据并推送至RTMP服务器或轻量级RTSP服务
102 0