欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

详细讲解Android中使用LoaderManager加载数据的方法

程序员文章站 2024-03-01 16:43:34
android的设计之中,任何耗时的操作都不能放在ui主线程之中。所以类似于网络操作等等耗时的操作都需要使用异步的实现。而在contentprovider之中,也有可能存在...

android的设计之中,任何耗时的操作都不能放在ui主线程之中。所以类似于网络操作等等耗时的操作都需要使用异步的实现。而在contentprovider之中,也有可能存在耗时的操作(当查询的数据量很大的时候),这个时候我们也需要使用异步的调用来完成数据的查询。

当使用异步的query的时候,我们就需要使用loadermanager了。使用loadermanager就可以在不阻塞ui主线程的情况下完成数据的加载。

(1)获取loadermanger:activity.getloadermanager()

(2)loadermanager的事件回调接口, loadermanager.loadercallbacks<d>

下面是一个demo,从contentprovider中query数据添加到listview中,是异步执行的。

mysqliteopenheleper.java:

package com.app.loadermanager;
import android.content.context;
import android.database.sqlite.sqlitedatabase;
import android.database.sqlite.sqlitedatabase.cursorfactory;
import android.database.sqlite.sqliteopenhelper;
 
public class mysqliteopenhelper extends sqliteopenhelper {
 
  public static final string db_name = "test.db3";
  public static final int version = 1;
 
  public mysqliteopenhelper(context context) {
    super(context, db_name, null, version);
  }
 
  @override
  public void oncreate(sqlitedatabase db) {
 
    string create_sql = "create table tb_student(_id integer primary key autoincrement,name varchar(20),age integer)";
    db.execsql(create_sql);
  }
 
  @override
  public void onupgrade(sqlitedatabase db, int oldversion, int newversion) {
 
  }
 
}

mycontentprovider.java

package com.app.loadermanager;
 
import android.content.contentprovider;
import android.content.contenturis;
import android.content.contentvalues;
import android.content.urimatcher;
import android.database.cursor;
import android.database.sqlite.sqlitedatabase;
import android.net.uri;
 
public class mycontentprovider extends contentprovider {
 
  private mysqliteopenhelper helper = null;
  private static final urimatcher matcher = new urimatcher(
      urimatcher.no_match);
  private static final int students = 1;
  static {
    matcher.adduri("com.app.contentprovider", "tb_student", students);
  }
 
  @override
  public int delete(uri arg0, string arg1, string[] arg2) {
    return 0;
  }
 
  @override
  public string gettype(uri arg0) {
    return null;
  }
 
  @override
  public uri insert(uri uri, contentvalues values) {
    sqlitedatabase db = helper.getwritabledatabase();
    int flag = matcher.match(uri);
    switch (flag) {
    case students:
      long id = db.insert("tb_student", null, values);
      return contenturis.withappendedid(uri, id);
    }
    return null;
  }
 
  @override
  public boolean oncreate() {
    helper = new mysqliteopenhelper(this.getcontext());
    return true;
  }
 
  @override
  public cursor query(uri uri, string[] projection, string selection,
      string[] selectionargs, string sortorder) {
    sqlitedatabase db = helper.getwritabledatabase();
    cursor cursor=db.query("tb_student", projection, selection, selectionargs, null, null, null);
    return cursor;
  }
 
  @override
  public int update(uri uri, contentvalues values, string selection,
      string[] selectionargs) {
    return 0;
  }
 
}

mainactivity.java:

package com.app.loadermanager;
 
import java.util.arraylist;
 
import android.annotation.suppresslint;
import android.app.activity;
import android.app.loadermanager;
import android.content.cursorloader;
import android.content.loader;
import android.database.cursor;
import android.net.uri;
import android.os.bundle;
import android.widget.arrayadapter;
import android.widget.listview;
 
@suppresslint("newapi")
public class mainactivity extends activity implements
    loadermanager.loadercallbacks<cursor> {
 
  loadermanager manager = null;
  listview listview = null;
 
  @suppresslint("newapi")
  @override
  protected void oncreate(bundle savedinstancestate) {
    super.oncreate(savedinstancestate);
    setcontentview(r.layout.activity_main);
    listview = (listview) this.findviewbyid(r.id.listview);
    manager = this.getloadermanager();
    manager.initloader(1000, null, this);
  }
 
  @override
  public loader<cursor> oncreateloader(int id, bundle bundle) {
    cursorloader loader = new cursorloader(this,
        uri.parse("content://com.app.contentprovider"), null, null,
        null, null);
    return loader;
  }
 
  @override
  public void onloadfinished(loader<cursor> loader, cursor cursor) {
    arraylist<string> al = new arraylist<string>();
    while (cursor.movetonext()) {
      string name = cursor.getstring(cursor.getcolumnindex("name"));
      al.add(name);
    }
    arrayadapter adapter=new arrayadapter(this,android.r.layout.simple_list_item_1,al);
    listview.setadapter(adapter);
    adapter.notifydatasetchanged();
     
  }
 
  @override
  public void onloaderreset(loader<cursor> loader) {
 
  }
}

loadermanager与生命周期
activity和fragment都拥有getloadermanager的方法,其实fragment的getloadermanager去获取的就是activity所管理的众多loadermanager之一。

1.who标签
先来看一下activity的getloadermanager方法:

   loadermanagerimpl getloadermanager(string who, boolean started, boolean create) {
     if (mallloadermanagers == null) {
       mallloadermanagers = new arraymap<string, loadermanagerimpl>();
     }
     loadermanagerimpl lm = mallloadermanagers.get(who);
     if (lm == null) {
       if (create) {
         lm = new loadermanagerimpl(who, this, started);
         mallloadermanagers.put(who, lm);
       }
     } else {
       lm.updateactivity(this);
     }
     return lm;
   }

mallloadermanagers保存着一个activity所拥有的所有loadermanager,其key为string类型的who变量。若从activity调用getloadermanager,那么所得loadermanager的who标签为(root):

   public loadermanager getloadermanager() {
     if (mloadermanager != null) {
       return mloadermanager;
     }
     mcheckedforloadermanager = true;
     mloadermanager = getloadermanager("(root)", mloadersstarted, true);
     return mloadermanager;
   }

若从fragment中使用getloadermanager,则所得loadermanager的who标签会根据fragment的层级不同而不同,在activity中处于最*的fragment的who标签为:

android:fragment:x
x为序号。

而属于fragment的fragment所活的的loadermanager who标签就成为了:

android:fragment:x:y
甚至更大的深度。

2.loadermanager状态量mloadersstarted
在开篇分析的时候分析过loadermanager内部保存了一个mstarted状态,很多操作会根据这个状态做不同处理。通过上边的分析也能看出,fragment中获取loadermanager最终是通过activity获取的,在loadermanager构造时,就将一个状态量mloadersstarted传递了进去,这个状态量交给loadermanager自行管理。

而无论是fragment还是activity,都有mloadersstarted这样一个状态量,在onstart生命周期后为true,onstop后为false。所以在onstart生命周期后做initloader操作就会使loader一经初始化就开始运行了。

3.fragment和activity的生命周期
fragment和activity能够对loadermanager产生影响的生命周期是一样的。

onstart

系统在onstart阶段会获取loadermanager,如果成功获取,则调用loadermanager的dostart(),这里需要特别说明的是,虽然所有loadermanager都是保存在activity中,但是在activity的onstart生命周期其也只是会获取属于自己的(root)标签loadermanager,而并不是将所有保存在mallloadermanagers里的manager全部遍历一遍。

onstop(performstop)

处于onstop生命周期,但是系统内部是通过performstop调用的。在这里,同样会获取属于自己的loadermanager,如果activity是因为配置改变出发的onstop(旋转屏幕),则调用loadermanager的doretain()接口,如果不是,就调用loadermanager的dostop()接口。

ondestroy(performdestroy)

调用loadermanager的dodestroy()接口销毁loadermanager。

4.loadermanager的生命周期
因为loadermanager与fragment/activity的生命周期紧密相连,所以想要用好loadermanager就必须了解其自身的生命周期,这样就能把握数据的完整变化规律了。

正常的从出生到销毁:

dostart() -> doreportstart() -> dostop() -> dodestroy()
activity配置发生变化:

dostart() -> doretain() -> finishretain() -> doreportstart() -> dostart() -> dostop() -> dodestroy()
fragment在ondestroyview()之后还会执行loadermanager的doreportnextstart(), 即:

dostart() -> doretain() -> doreportnextstart() -> finishretain() -> doreportstart() -> dostart() -> dostop() -> dodestroy()
dostart()会将loadermanager中保存的所有loader都启动。最终是运行每一个loader的onstartloading()方法。只要是通过initloader使用过的loader都会记录在loadermanager的mloaders中,那么问题来了:

怎样在fragment/activity不销毁的前提下从loadermanager中移除某个使用过的loader呢?
答案就是使用loadermanager的接口去除指定id的loader:

public void destroyloader(int id)
这样就能在mloaders中移除掉了,下次onstart的时候就没有这个loader什么事了。

doreportstart()。如果fragment上一次在销毁并重做,而且数据有效的话会在这里主动上报数据,最终走到callback的onloadfinished中。
dostop()会停止mloaders保存的所有loader。最终是运行每一个loader的onstoploading()方法。
dodestroy()会清空所有有效和无效loader,loadermanager中不再存在任何loader。
doretain()会将loadermanager的mretaining状态置位true,并且保存retain时loaderinfo的mstarted状态
finishretain()如果之前所保存的mstarted与现在的不一样而且新的状态是停止的话,就停止掉这个loader。否则若有数据并且不是要下次再上报(没有call doreportnextstart)的话就上报给callback的onloadfinished。
doreportnextstart(),根据第6条,已经能够理解了。当fragment执行到ondestroyview生命周期时,对自己的loadermanager发出请求:即使现在有数据也不要进行上报,等我重做再到onstart生命周期时再给我。