• Android--Loaders


    前言

      Loaders,装载机,适用于Android3.0以及更高的版本,它提供了一套在UI的主线程中异步加载数据的框架。使用Loaders可以非常简单的在Activity或者Fragment中异步加载数据,一般适用于大量的数据查询,或者需要经常修改并及时展示的数据显示到UI上,这样可以避免查询数据的时候,造成UI主线程的卡顿。

      Loaders有以下特点:

    • 可以适用于Activity和Fragment。
    • 可以提供异步的方式加载数据。
    • 监听数据源,当数据改变的时候,将新的数据发布到UI上。
    • Loaders使用Cursor加载数据,在更改Cursor的时候,会自动重新连接到最后配置的Cursor中读取数据,因此不需要重新查询数据。

      在Android中使用Loaders机制,需要多个类和接口的配合,以下是它们大致的关系图,之后的内容会对这几个类或接口进行详细讲解:

    LoaderManager

      LoaderManager,装载机管理器。用于在Activity或者Fragment中管理一个或多个Loader实例。在Activity或者Fragment中,可以通过getLoaderManager()方法获取LoaderManager对象,它是一个单例模式。

      介绍几个LoaderManager提供的方法,用于管理Loader:

    • Loader<D> initLoader(int id,Bundle bundle,LoaderCallbacks<D> callback):初始化一个Loader,并注册回调事件。
    • Loader<D> restartLoader(int id,Bundle bundle,LoaderCallbacks<D> callback):重新启动或创建一个Loader,并注册回调事件。
    • Loader<D> getLoader(int id):返回给定Id的Loader,如果没有找到则返回Null。
    • void destroyLoader(int id):根据指定Id,停止和删除Loader。

       通过上面几个方法的参数可以看到,都有一个id参数,这个Id是Loader的标识,因为LoaderManager可以管理一个或多个Loader,所以必须通过这个Id参数来唯一确定一个Loader。而InitLoader()、restartLoader()中的bundle参数,传递一个Bundle对象给LoaderCallbacks中的onCreateLoader()去获取,下面介绍LoaderCallbacks。

    LoaderManager.LoaderCallbacks

      LoaderCallbacks是LoaderManager和Loader之间的回调接口。它是一个回调接口,所以我们需要实现其定义的三个方法:

    • Loader<D> onCreateLoader(int id,Bundle bundle):根据指定Id,初始化一个新的Loader。
    • void onLoadFinished(Loader<D> loader,D data):当Loader被加载完毕后被调用,在其中处理Loader获取的Cursor数据。
    • void onLoaderReset(Loader<D> loader):当Loader被销毁的时候被调用,在其中可以使Loader的数据不可用。

      从LoaderCallbacks的声明的几个方法中可以看到,它是一个泛型的接口,需要指定Loader数据的类型。如果是数据源是从一个ContentProvider中获取的,一般直接使用它的子类CursorLoader,下面介绍CursorLoader。

    Loader

      Loader,一个抽象的类,用于执行异步加载数据,这个Loader对象可以监视数据源的改变和在内容改变后,以新数据的内容改变UI的展示。它是一个Loader的抽象接口,所有需要实现的Loader功能的类都需要实现这个接口,但是如果需要自己开发一个装载机的话,一般并不推荐继承Loader接口,而是继承它的子类AsyncTaskLoader,这是一个以AsyncTask框架执行的异步加载。

      Android中还提供了一个CursorLoader类,它是AsyncTaskLoader的子类,一个异步的加载数据的类,通过ContentResolver的标准查询并返回一个Cursor。这个类实现了Loader的协议,以一种标准的方式查询Cursor。

      CursorLoader类有两个构造函数,推荐使用第二个,因为使用第一个构造函数,需要还需要通过CursorLoader提供的一些了getXxx()方法设置对应的属性:

    • CursorLoader(Context context)
    • CursorLoader(Context context,Uri uri,String[] projection,String selection ,String[] selectionArgs,String sortOrder)

    SimpleCursorAdapter

      在Android中,数据的展示都需要使用一个Adapter适配器,而Loader一般返回的就是一个Cursor的数据,可以使用BaseAdapter的一个子类SimpleCursorAdapter,它可以使用XML资源文件自定义一个布局在展示数据。它有两个构造函数,但是有一个构造函数在API Level11之后就不推荐使用。下面是构造函数的签名:

      SimpleCursorAdapter(Context context,int layout,Cursor c,String[] from,int[] to,int flags).

      最后一个参数flags是一个标识,标识当数据改变调用onContentChanged()的时候,是否通知ContentProvider数据的改变,如果无需监听ContentProvider的改变,则可以传0。对于SimpleCursorAdapter适配器的Cursor的改变,可以使用SimpleCursorAdapter.swapCursor(Cursor)方法,它会与旧的Cursor互换,并且返回旧的Cursor。

    Demo

      下面通过一个Demo来讲解一下Loaders的使用。在这个Demo中,数据使用SQLite数据库保存,而使用ContentProvider进行数据的请求与访问。在SQLite数据库中,存在一个Student表,它近有两个字段:_id,name。在Demo中,使用一个ListView展示数据,使用LoaderManager管理一个Loader,并通过这个Loader的回调接口进行刷新ListView的数据显示。进行对SQLite数据库中的数据进行增加与删除。下面不提供SQLiteOpenHelper和ContentProvider相关实现类的代码,如有需要可以下载源码查看,对于SQLite和ContentProvider的内容,不清楚的朋友可以参见博客:数据持久化之SQLiteContentProvider

      实现代码:

      1 package com.example.loadermanagerdemo;
      2 
      3 import android.net.Uri;
      4 import android.os.Bundle;
      5 import android.app.Activity;
      6 import android.app.AlertDialog;
      7 import android.app.LoaderManager;
      8 import android.app.LoaderManager.LoaderCallbacks;
      9 import android.content.ContentResolver;
     10 import android.content.ContentValues;
     11 import android.content.CursorLoader;
     12 import android.content.Loader;
     13 import android.database.Cursor;
     14 import android.util.Log;
     15 import android.view.ContextMenu;
     16 import android.view.LayoutInflater;
     17 import android.view.Menu;
     18 import android.view.MenuInflater;
     19 import android.view.MenuItem;
     20 import android.view.View;
     21 import android.view.ContextMenu.ContextMenuInfo;
     22 import android.widget.AdapterView.AdapterContextMenuInfo;
     23 import android.widget.Button;
     24 import android.widget.EditText;
     25 import android.widget.ListView;
     26 import android.widget.SimpleCursorAdapter;
     27 import android.widget.TextView;
     28 
     29 public class MainActivity extends Activity {
     30     private LoaderManager manager;
     31     private ListView listview;
     32     private AlertDialog alertDialog;
     33     private SimpleCursorAdapter mAdapter;
     34     private final String TAG="main";
     35 
     36     @Override
     37     protected void onCreate(Bundle savedInstanceState) {
     38         super.onCreate(savedInstanceState);
     39         setContentView(R.layout.activity_main);
     40         listview = (ListView) findViewById(R.id.listView1);
     41         //使用一个SimpleCursorAdapter,布局使用android自带的布局资源simple_list_item_1, android.R.id.text1 为simple_list_item_1中TextView的Id
     42         mAdapter = new SimpleCursorAdapter(MainActivity.this,
     43                 android.R.layout.simple_list_item_1, null,
     44                 new String[] { "name" }, new int[] { android.R.id.text1 },0);
     45         
     46         // 获取Loader管理器。
     47         manager = getLoaderManager();
     48         // 初始化并启动一个Loader,设定标识为1000,并制定一个回调函数。
     49         manager.initLoader(1000, null, callbacks);
     50 
     51         // 为ListView注册一个上下文菜单
     52         registerForContextMenu(listview);
     53     }
     54 
     55     @Override
     56     public void onCreateContextMenu(ContextMenu menu, View v,
     57             ContextMenuInfo menuInfo) {
     58         super.onCreateContextMenu(menu, v, menuInfo);
     59         // 声明一个上下文菜单,contentmenu中声明了两个菜单,添加和删除
     60         MenuInflater inflater = getMenuInflater();
     61         inflater.inflate(R.menu.contentmenu, menu);
     62     }
     63 
     64     @Override
     65     public boolean onContextItemSelected(MenuItem item) {
     66         
     67         switch (item.getItemId()) {
     68         case R.id.menu_add:
     69             // 声明一个对话框
     70             AlertDialog.Builder builder = new AlertDialog.Builder(
     71                     MainActivity.this);
     72             // 加载一个自定义布局,add_name中有一个EditText和Button控件。
     73             final View view = LayoutInflater.from(MainActivity.this).inflate(
     74                     R.layout.add_name, null);
     75             Button btnAdd = (Button) view.findViewById(R.id.btnAdd);
     76             btnAdd.setOnClickListener(new View.OnClickListener() {
     77 
     78                 @Override
     79                 public void onClick(View v) {
     80                     EditText etAdd = (EditText) view
     81                             .findViewById(R.id.username);
     82                     String name = etAdd.getText().toString();
     83                     // 使用ContentResolver进行删除操作,根据name字段。
     84                     ContentResolver contentResolver = getContentResolver();
     85                     ContentValues contentValues = new ContentValues();
     86                     contentValues.put("name", name);
     87                     Uri uri = Uri
     88                             .parse("content://com.example.loadermanagerdemo.StudentContentProvider/student");
     89                     Uri result = contentResolver.insert(uri, contentValues);
     90                     if (result != null) {
     91                         //result不为空证明删除成功,重新启动Loader,注意标识需要和之前init的标识一致。
     92                         manager.restartLoader(1000, null, callbacks);
     93                     }
     94                     // 关闭对话框
     95                     alertDialog.dismiss();
     96                     
     97                     Log.i(TAG, "添加数据成功,name="+name);
     98                 }
     99             });
    100             builder.setView(view);
    101             alertDialog = builder.show();
    102             return true;
    103         case R.id.menu_delete:
    104             // 获取菜单选项的信息
    105             AdapterContextMenuInfo info = (AdapterContextMenuInfo) item
    106                     .getMenuInfo();
    107             // 获取到选项的TextView控件,并得到选中项的那么
    108             TextView tv = (TextView) info.targetView;
    109             String name = tv.getText().toString();
    110             // 使用ContentResolver进行删除操作
    111             Uri url = Uri
    112                     .parse("content://com.example.loadermanagerdemo.StudentContentProvider/student");
    113             ContentResolver contentResolver = getContentResolver();
    114             String where = "name=?";
    115             String[] selectionArgs = { name };
    116             int count = contentResolver.delete(url, where, selectionArgs);
    117             if (count == 1) {
    118                 //这个操作仅删除单挑记录,如果删除行为1 ,则重新启动Loader
    119                 manager.restartLoader(1000, null, callbacks);
    120             }
    121             Log.i(TAG, "删除数据成功,name="+name);
    122             return true;
    123         default:
    124             return super.onContextItemSelected(item);
    125         }
    126 
    127     }
    128 
    129     // Loader的回调接口
    130     private LoaderManager.LoaderCallbacks<Cursor> callbacks = new LoaderCallbacks<Cursor>() {
    131 
    132         @Override
    133         public Loader<Cursor> onCreateLoader(int id, Bundle bundle) {
    134             // 在Loader创建的时候被调用,这里使用一个ContentProvider获取数据,所以使用CursorLoader返回数据
    135             Uri uri = Uri
    136                     .parse("content://com.example.loadermanagerdemo.StudentContentProvider/student");
    137             CursorLoader loader = new CursorLoader(MainActivity.this, uri,
    138                     null, null, null, null);
    139             Log.i(TAG, "onCreateLoader被执行。");
    140             return loader;
    141         }
    142 
    143         @Override
    144         public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
    145             //刷新SimpleCursorAdapter的数据
    146             mAdapter.swapCursor(cursor);
    147             // 重新设定适配器
    148             listview.setAdapter(mAdapter);
    149             Log.i(TAG, "onLoadFinished被执行。");
    150         }
    151 
    152         @Override
    153         public void onLoaderReset(Loader<Cursor> loader) {
    154             // 当Loader被从LoaderManager中移除的时候,被执行,清空SimpleCursorAdapter适配器的Cursor
    155             mAdapter.swapCursor(null);
    156             Log.i(TAG, "onLoaderReset被执行。");
    157         }
    158     };
    159 
    160     @Override
    161     public boolean onCreateOptionsMenu(Menu menu) {
    162         getMenuInflater().inflate(R.menu.main, menu);
    163         return true;
    164     }
    165 
    166 }

      效果展示:

      源码下载

     

  • 相关阅读:
    hdu1828线段树(两次扫描+离散化)
    hdu1542线段树(扫描线+离散化)
    二分思想
    hdu2871线段树区间更新
    git初体验
    python笔记-模块
    python笔记-动态类型
    AWS上创建EKS(K8S)集群
    Akamai CDN刷新(通过Akamai cli 自动刷新)
    创建Akamai cdn api授权
  • 原文地址:https://www.cnblogs.com/plokmju/p/android_Loaders.html
Copyright © 2020-2023  润新知