Explain in detail the method of loading data using LoaderManager in Android

  • 2021-07-06 11:45:19
  • OfStack

In the design of Android, no time-consuming operation can be placed in the main thread of UI. Therefore, time-consuming operations like network operations need to be implemented asynchronously. In ContentProvider, there may be time-consuming operations (when the amount of data queried is very large), and we also need to use asynchronous calls to complete the data query at this time.

When using asynchronous query, we need to use LoaderManager. Using LoaderManager, you can load data without blocking the main thread of UI.

(1) Get loaderManger: activity. getLoaderManager ()

(2) Event callback interface for loaderManager, LoaderManager. LoaderCallbacks < D >

The following is an demo, which was added to listview from contentprovider query data and executed asynchronously.

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 and Life Cycle
Activity and Fragment both have the method of getLoaderManager. In fact, getLoaderManager of Fragment obtains one of many LoaderManager managed by Activity.

1. Who tag
Let's first look at the getLoaderManager method of Activity under 1:


   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 holds all LoaderManager owned by an Activity, and its key is an who variable of type String. If getLoaderManager is called from Activity, the who label of the resulting LoaderManager is (root):


   public LoaderManager getLoaderManager() {
     if (mLoaderManager != null) {
       return mLoaderManager;
     }
     mCheckedForLoaderManager = true;
     mLoaderManager = getLoaderManager("(root)", mLoadersStarted, true);
     return mLoaderManager;
   }

If getLoaderManager is used from Fragment, the who tag of the resulting LoaderManager will be different according to the level of Fragment, and the who tag of the top Fragment in Activity is:

android:fragment:X
X is the serial number.

The live LoaderManager who tag of Fragment belonging to Fragment becomes:

android:fragment:X:Y
Even greater depth.

2. LoaderManager state quantity mLoadersStarted
At the beginning of the analysis, it is analyzed that an mStarted state is saved inside LoaderManager, and many operations will be handled differently according to this state. From the above analysis, it can be seen that LoaderManager obtained in Fragment is finally obtained through Activity. When LoaderManager is constructed, a state quantity mLoadersStarted is passed in, and this state quantity is handed over to LoaderManager for self-management.

Both Fragment and Activity have a state quantity, mLoadersStarted, which is true after onStart life cycle and false after onStop. So doing initLoader after the onStart lifecycle causes Loader1 to be initialized and run.

3. Lifecycle of Fragment and Activity
The lifecycle of Fragment and Activity that can affect LoaderManager is the same.

onStart

The system will obtain LoaderManager in onStart stage, and if it is successfully obtained, it will call doStart () of LoaderManager. It should be specially noted here that although all LoaderManager are stored in Activity, it will only obtain its own (root) tag LoaderManager in the life cycle of Activity, instead of traversing all Manager stored in mAllLoaderManagers once.

onStop (performStop)

Is in the onStop lifecycle, but is called internally through performStop. Here, you will also get your own LoaderManager. If Activity starts from onStop (rotating screen) due to configuration change, you will call doRetain () interface of LoaderManager, and if not, you will call doStop () interface of LoaderManager.

onDestroy(performDestroy)

Call the doDestroy () interface of LoaderManager to destroy LoaderManager.

4. Life cycle of LoaderManager
Because the life cycles of LoaderManager and Fragment/Activity are closely linked, if you want to make good use of LoaderManager, you must understand its own life cycle, so that you can grasp the complete change law of data.

Normal from birth to destruction:

doStart() - > doReportStart() - > doStop() - > doDestroy()
Activity configuration changes:

doStart() - > doRetain() - > finishRetain() - > doReportStart() - > doStart() - > doStop() - > doDestroy()
Fragment also executes doReportNextStart () of LoaderManager after onDestroyView (), that is:

doStart() - > doRetain() - > doReportNextStart() - > finishRetain() - > doReportStart() - > doStart() - > doStop() - > doDestroy()
doStart () starts all Loader saved in LoaderManager. Eventually, the onStartLoading () method runs for every Loader. Any Loader that has been used through initLoader will be recorded in mLoaders of LoaderManager, so here's the problem:

How to remove a used Loader from an LoaderManager without destroying Fragment/Activity?
The answer is to use the interface of LoaderManager to remove Loader that specifies ID:

public void destroyLoader(int id)
This will be removed from the mLoaders, and the next onStart will have nothing to do with this Loader.

doReportStart (). If Fragment is destroyed and redone once, and the data is valid, it will actively report the data here, and finally go to onLoadFinished of callback.
doStop () stops all Loader saved by mLoaders. Eventually, the onStopLoading () method runs for every Loader.
doDestroy () empties all valid and invalid Loader, and no Loader exists in LoaderManager.
doRetain () sets the mRetaining state of LoaderManager to true and saves the mStarted state of LoaderInfo when retain
finishRetain () If the previously saved mStarted is not the same as the current one and the new state is stopped, stop the Loader. Otherwise, if data is available and not to be reported next time (no call doReportNextStart), it will be reported to onLoadFinished of callback.
doReportNextStart (), under Article 6, is understood. When Fragment executes to onDestroyView lifecycle, send a request to your LoaderManager: Do not report data even if it is available now, and give it back to me when I redo it to onStart lifecycle.


Related articles: