问题
Here's what I've been doing all this while to get data from my database - my fragment will call a dbhelper which will run the query and pass the resulting data in an object.
I should have used a loader so that the querying is not done in the UI thread, but previously I didn't bother since my app's database is very small. Moving forward, this was a bad idea since my app has grown larger and so do the database. So now I'm reading all I can about Loaders including CursorLoader and custom loaders, but I still have problem implementing it in my app.
So here's my current code (simplified, just to show the relevant parts):
public class CategoryEdit extends Fragment{
private DbControl dbc;
private ObjectCategory objCategory;
private int categoryID = 3;
private EditText etName;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
dbc = new DbControl(getActivity());
try{
dbc.open();
objCategory = new ObjectCategory();
objCategory = dbc.getCategoryData(categoryID);
dbc.close();
}catch (SQLException e){
e.printStackTrace();
}catch (Exception e){
e.printStackTrace();
}
etName.setText(objCategory.name);
}
}
public class DbControl {
public static final String FILE_NAME = "DbControl.java";
private SQLiteDatabase database;
private MySQLiteHelper dbHelper;
public DbControl(Context context) {
dbHelper = new MySQLiteHelper(context);
}
public void open() throws SQLException {
database = dbHelper.getWritableDatabase();
}
public void close() {
dbHelper.close();
}
public ObjectCategory getCategoryData(int itemID) throws Exception{
ObjectCategory objCategory = new ObjectCategory();
Cursor cursor = database.query(MySQLiteHelper.TABLE_CATEGORY, new String[] {"name"},
"id = " + itemID, null, null, null, null);
if (cursor!=null && cursor.getCount()>0 && cursor.moveToFirst()) {
objCategory.name = cursor.getString(cursor.getColumnIndex("name"));
}
return objCategory;
}
}
Can anyone point me in the right direction, in implementing Loader the right way in my case here? If possible, I don't want to alter the codes inside class DbControl too much - especially the return data type of the functions inside it.
Btw amongst the tutorials about loaders that I've read is:
- http://www.recursiverobot.com/post/60331340133/very-simple-example-of-a-loader-and-loadermanager - but this was written more than 3 years ago and it uses Runnable to execute getSupportLoaderManager, and I've not seen other examples/tutorials that does this, so I'm afraid this article is outdated. Furthermore it uses CursorLoader, which from my understanding can only return cursor.
- http://www.grokkingandroid.com/using-loaders-in-android/ - this seems to deal with CursorLoader too
Anyway here's what I've done so far:
public class CategoryEdit extends Fragment implements LoaderManager.LoaderCallbacks<ObjectCategory> {
@Override
public Loader<ObjectCategory> onCreateLoader(int id, Bundle args){
try{
dbc.open();
objCategory = new ObjectCategory();
objCategory = dbc.getCategoryData(categoryID);
dbc.close();
}catch (SQLException e){
e.printStackTrace();
}catch (Exception e){
e.printStackTrace();
}
return objCategory; //this returns error, it says required Loader<ObjectCategory> instead of objCategory, but I'm not sure how to do that here
}
@Override
public void onLoadFinished(Loader<ObjectCategory> cursorLoader, ObjectCategory cursor) {
//Since onCreateLoader has error above, I'm not sure how to implement this part
}
@Override
public void onLoaderReset(Loader<ObjectCategory> cursorLoader) {
//Can I just leave this empty?
}
}
回答1:
Can anyone point me in the right direction, in implementing Loader the right way in my case here?
You have to use an AsyncTaskLoader like this:
public class CategoryEdit extends Fragment implements LoaderManager.LoaderCallbacks<ObjectCategory> {
@Override
public Loader<ObjectCategory> onCreateLoader(int id, Bundle args) {
return new AsyncTaskLoader<ObjectCategory>(getActivity()) {
@Override
public ObjectCategory loadInBackground() {
try {
dbc.open();
objCategory = new ObjectCategory();
objCategory = dbc.getCategoryData(categoryID);
dbc.close();
} catch (SQLException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return objCategory;
}
@Override
protected void onStartLoading() {
forceLoad();
}
};
}
@Override
public void onLoadFinished(Loader<ObjectCategory> loader, ObjectCategory data) {
//do some stuff here
}
@Override
public void onLoaderReset(Loader<ObjectCategory> loader) {
//Yes, you can leave it empty
}
}
回答2:
I would say - don't use Loaders
. I used CursorLoader
in my app when I was new to Android, and it turned out to be a bad decision.
If CursorLoader
is sufficient for your needs, then you're alright (though your Activities
and Fragments
will become polluted with DB related logic), but once you try to implement your own Loader
, that should encapsulate DB related logic and just return a constructed object, you'll inevitably bump into a million issues with LoaderManager
framework. @CommonsWare wrote a nice article about it - read it before you integrate Loaders
into your app.
Nowadays I usually use "managers" for loading data from SQLite or internet. The general form of my "manager" is like this (please note that these "managers" are not Singletons):
public class TutorialManager {
interface TutorialManagerListener {
void onDataFetchCompleted(SomeData data);
}
private BackgroundThreadPoster mBackgroundThreadPoster;
private MainThreadPoster mMainThreadPoster;
private Set<TutorialManagerListener> mListeners = new HashSet<>(1);
public TutorialManager(@NonNull BackgroundThreadPoster backgroundThreadPoster,
@NonNull MainThreadPoster mainThreadPoster) {
mBackgroundThreadPoster = backgroundThreadPoster;
mMainThreadPoster = mainThreadPoster;
}
@UiThread
public void registerListener(@NonNull TutorialManagerListener listener) {
mListeners.add(listener);
}
@UiThread
public void unregisterListener(@NonNull TutorialManagerListener listener) {
mListeners.remove(listener);
}
@UiThread
private void notifyFetchCompleted(SomeData data) {
for (TutorialManagerListener listener : mListeners) {
listener.onDataFetchCompleted(data);
}
}
/**
* Call to this method will fetch data on background thread and then notify all registered
* listeners about new data on UI thread.
*/
public void fetchData() {
mBackgroundThreadPoster.post(new Runnable() {
@Override
public void run() {
// logic that loads the data on background thread goes here
// final SomeData data = ... ;
mMainThreadPoster.post(new Runnable() {
@Override
public void run() {
notifyFetchCompleted(data);
}
});
}
});
}
}
Implementation of MainThreadPoster
:
/**
* This class should be used in order to execute {@link Runnable} objects on UI thread
*/
public class MainThreadPoster {
private final Handler mMainHandler;
public MainThreadPoster() {
mMainHandler = new Handler(Looper.getMainLooper());
}
/**
* Post {@link Runnable} for execution on main thread
*/
public void post(Runnable runnable) {
mMainHandler.post(runnable);
}
}
Implementation of BackgroundThreadPoster
is up to you.I usually use the following simple implementation (unless some fine tuning is required):
/**
* This class should be used in order to execute {@link Runnable} objects on background threads
*/
public class BackgroundThreadPoster {
private final ExecutorService mBackgroundExecutor;
public BackgroundThreadPoster() {
mBackgroundExecutor = Executors.newCachedThreadPool();
}
/**
* Post {@link Runnable} for execution on a random background thread
*/
public void post(Runnable runnable) {
mBackgroundExecutor.execute(runnable);
}
}
I inject MainThreadPoster
and BackgroundThreadPoster
objects into "managers" in order to:
- Make "managers" unit-testable
- Perform centralized management and optimization of background threading across entire application
来源:https://stackoverflow.com/questions/40365548/using-loaders-with-sqlite-query-that-returns-object