6

我对Android开发很陌生。我已经研究了文档并阅读了大量关于如何做这个和那个的教程,但是 1 个问题仍然困扰着我。

我的项目有 3 个实体:作者、小说和章节。

我有一个片段,其中包含在某个服务器上发布的最新章节列表,当它滚动到底部时会动态加载更多项目(使用 LoaderManager)。机制是这样的:fragment 向 SQLite 数据库查询有限的记录,负责 uri 的内容提供者调用服务(我自己的)下载更多的章节并将它们保存到数据库中。然后通知该特定 uri 的内容观察者并刷新片段列表。

每次我在列表中向下滚动太快(我猜应用程序在尝试从数据库加载新数据时正在下载和插入新内容)我都会收到此错误:

ERROR/AndroidRuntime(689): FATAL EXCEPTION: IntentService[DatabaseService]
    java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase: /data/data/cz.muni.fi.WebNovelReader/databases/Live
    at android.database.sqlite.SQLiteClosable.acquireReference(SQLiteClosable.java:55)
    at android.database.sqlite.SQLiteDatabase.beginTransaction(SQLiteDatabase.java:503)
    at android.database.sqlite.SQLiteDatabase.beginTransaction(SQLiteDatabase.java:416)
    at cz.muni.fi.WebNovelReader.Downloader.LiveProvider.bulkInsertOrUpdate(LiveProvider.java:2040)
    at cz.muni.fi.WebNovelReader.Downloader.LiveProvider.bulkInsert(LiveProvider.java:1804)
    at android.content.ContentProvider$Transport.bulkInsert(ContentProvider.java:207)
    at android.content.ContentResolver.bulkInsert(ContentResolver.java:925)
    at cz.muni.fi.WebNovelReader.Downloader.DatabaseService.onHandleIntent(DatabaseService.java:90)
    at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:65)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:137)
    at android.os.HandlerThread.run(HandlerThread.java:60)

第 2040 行标记在下面的源代码中。当我在加载(下载和插入)更多数据时尝试向上滚动时出现另一个错误,我收到另一个错误:

ERROR/AndroidRuntime(619): FATAL EXCEPTION: ModernAsyncTask #4
    java.lang.RuntimeException: An error occured while executing doInBackground()
    at android.support.v4.content.ModernAsyncTask$3.done(ModernAsyncTask.java:137)
    at java.util.concurrent.FutureTask$Sync.innerSetException(FutureTask.java:273)
    at java.util.concurrent.FutureTask.setException(FutureTask.java:124)
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:307)
    at java.util.concurrent.FutureTask.run(FutureTask.java:137)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569)
    at java.lang.Thread.run(Thread.java:856)
    Caused by: java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed.
    at android.database.sqlite.SQLiteConnectionPool.throwIfClosedLocked(SQLiteConnectionPool.java:962)
    at android.database.sqlite.SQLiteConnectionPool.waitForConnection(SQLiteConnectionPool.java:599)
    at android.database.sqlite.SQLiteConnectionPool.acquireConnection(SQLiteConnectionPool.java:348)
    at android.database.sqlite.SQLiteSession.acquireConnection(SQLiteSession.java:894)
    at android.database.sqlite.SQLiteSession.executeForCursorWindow(SQLiteSession.java:834)
    at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:62)
    at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:143)
    at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:133)
    at android.content.ContentResolver.query(ContentResolver.java:388)
    at android.content.ContentResolver.query(ContentResolver.java:313)
    at android.support.v4.content.CursorLoader.loadInBackground(CursorLoader.java:49)
    at android.support.v4.content.CursorLoader.loadInBackground(CursorLoader.java:35)
    at android.support.v4.content.AsyncTaskLoader.onLoadInBackground(AsyncTaskLoader.java:240)
    at android.support.v4.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:51)
    at android.support.v4.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:40)
    at android.support.v4.content.ModernAsyncTask$2.call(ModernAsyncTask.java:123)
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
    ... 4 more

我从错误消息中了解到,内容提供者的查询方法是关闭可写数据库。我想这与内容提供者的线程安全有关。作为线程安全问题的临时解决方法,我创建了一个服务(DatabaseService),它只负责数据库写入操作(按顺序执行它们)。我想我了解在 java 中使用多线程的基础知识,但是对我来说将这些知识应用到这个特定的问题上实在是太过分了,所以我来这里寻求帮助。

这是带有列表的frament的完整来源

package cz.muni.fi.WebNovelReader.Fragments;

import android.content.ContentUris;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.SimpleCursorAdapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ListView;
import com.actionbarsherlock.app.SherlockListFragment;
import cz.muni.fi.WebNovelReader.Library.ReaderActivity;
import cz.muni.fi.WebNovelReader.R;
import cz.muni.fi.WebNovelReader.Tools.Provider;

public class LiveNewestChaptersFragment extends SherlockListFragment implements LoaderManager.LoaderCallbacks<Cursor> {
    Uri uri;
    ListView list;
    SimpleCursorAdapter adapter;
    View footer = null;

    int offset;
    int lastEverVisible = 0;
    int count = 0;
    boolean loadingMore = false;

    String orderBy = Provider.Columns.CHAPTER_PUBLISHED;
    String sortOrder = " DESC";

    /* **********************
    *   Fragment Lifecycle  *
    ************************/
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.live_servers_fragment, container, false);
        list = (ListView)view.findViewById(android.R.id.list);
        list.setOnScrollListener(new AbsListView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView absListView, int i) {
                //To change body of implemented methods use File | Settings | File Templates.
            }

            @Override
            public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                //what is the bottom item that is visible
                int lastInScreen = firstVisibleItem + visibleItemCount;

                if (lastEverVisible < lastInScreen)
                    lastEverVisible = lastInScreen;

                //is the bottom item visible & not loading more already ? Load more !
                if ((totalItemCount >= 10) && (lastInScreen == totalItemCount) && (lastEverVisible == lastInScreen) && !(loadingMore)){
                    if (footer != null)
                        footer.setVisibility(View.VISIBLE);

                    offset += 10;
                    loadingMore = true;
                    count = totalItemCount;
                    getLoaderManager().restartLoader(0, null,         LiveNewestChaptersFragment.this);
                }
            }
        });

        footer = inflater.inflate(R.layout.loading_footer, null, false);
        footer.setVisibility(View.GONE);
        list.addFooterView(footer);
        return view;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        uri = getActivity().getIntent().getData();

        offset = 0;
        getLoaderManager().initLoader(0, null, this);

        adapter = new SimpleCursorAdapter(
                getActivity(),
                R.layout.live_newest_chapters_item,
                null,
                new String[] {Provider.Columns.CHAPTER_INDEX, Provider.Columns.CHAPTER_TITLE, Provider.Columns.NOVEL_TITLE, Provider.Columns.AUTHOR_NICKNAME},
                new int[] {R.id.chapter_index, R.id.chapter_title, R.id.novel_title, R.id.author_nickname},
                0
        );
        setListAdapter(adapter);

        getSherlockActivity().getSupportActionBar().setDisplayShowTitleEnabled(true);
        getSherlockActivity().getSupportActionBar().setDisplayShowHomeEnabled(true);
    }

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        Intent i = new Intent(getActivity(), ReaderActivity.class);
        i.setData(ContentUris.appendId(
                Provider.BaseUris.LIVE.buildUpon().path(Provider.UriPaths.CHAPTERS),
                id
        ).build());
        startActivity(i);
    }

    /* **********************************
    *   LoaderManager.LoaderCallbacks   *
    ************************************/

    @Override
    public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
        getSherlockActivity().setSupportProgressBarIndeterminateVisibility(true);

        return new CursorLoader(getActivity(),
                uri,
                new String[] {Provider.Columns.BASE_ID, Provider.Columns.CHAPTER_TITLE, Provider.Columns.CHAPTER_INDEX, Provider.Columns.NOVEL_TITLE, Provider.Columns.AUTHOR_NICKNAME},
                null,
                new String[] {String.valueOf(offset + 10)},
                orderBy + " COLLATE LOCALIZED" + sortOrder
        );
    }

    @Override
    public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
        if (cursor.getCount() > count) {
            if (footer != null)
                footer.setVisibility(View.GONE);
            adapter.swapCursor(cursor);
            loadingMore = false;
        }
        getSherlockActivity().setSupportProgressBarIndeterminateVisibility(false);
    }

    @Override
    public void onLoaderReset(Loader loader) {
        adapter.swapCursor(null);
        if (loader.isStarted())
            loader.reset();
    }
}

简化查询法

public Cursor query(Uri uri, String[] proj, String sel, String[] selectionArgs, String orderBy) {
    String selection = sel;

    String[] projection;
    if ((proj == null) || (proj.length == 0)) {
        projection = new String[] {"*"};
    }else{
        projection = new String[proj.length];
        System.arraycopy(proj, 0, projection, 0, proj.length);
    }
    String table;
    String groupBy = null;
    String limit = null;
    StringBuilder temp;

    boolean distinct = false;
    switch (uriMatcher.match(uri)) {
        // initialize variables needed to make the query
    }
    SQLiteDatabase db = dbHelper.getReadableDatabase();
    Cursor result = null;
    try{
        result = db.query(distinct, table, projection, selection, SelectionArgs, groupBy, null, orderBy, limit);
        if (result != null) {
            result.setNotificationUri(getContext().getContentResolver(), uri);

            if ((uriMatcher.match(uri) == URI_MATCHER_CHAPTERS_BY_SERVER_ID) && (ContentUris.parseId(uri) == 0)) {
                int offset = Integer.valueOf(limit);
                if (lastLiveLimit < offset) {
                    lastLiveLimit = offset;
                    DownloaderItemContent download = new DownloaderItemContent(url, insertUri);
                    Intent i = new Intent(getContext(), DownloaderService.class);
                    i.putExtra(DownloaderService.EXTRA_DOWNLOAD_ITEM, download);
                    getContext().startService(i);
                }
            }
        }
    }catch (SQLiteException e) {
        Log.e("WebNovel Reader", "LibraryProvider: query(): SQLiteException thrown:\n" + e.getMessage());
    } catch (MalformedURLException e) {
        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
    }

    return result;
}

简化
的 bulkInsert 方法 来源说明:我使用 bulkInsert 方法插入数据。我需要所有 3 个实体一起运行检查,因此我决定构建 ContentValues 数组以在单个方法调用中获取所有 3 个实体。其结构为:作者内容值依次是作者小说内容值、小说章节内容值。下一个内容值是另一个作者的小说的值,依此类推(模式 [author1, novel1, chapter1, chapter2, novel2, chapter1, chapter2, author2, novel1, chapter1])。它类似于通过 DFS 对树图进行序列化,然后将它们一个接一个地放置。

private int bulkInsertOrUpdate(Uri uri, ContentValues[] values) {
    //basic checking
    .
    .
    .
    //loop through authors
    while (i < values.length) {
        int offset = 0;
        List<ContentValues> novels_with_children = new ArrayList<ContentValues>();
        List<ContentValues[]> chapters = new ArrayList<ContentValues[]>();
        int max1 = 0;

        // check if content values of author or identifier of child count are incorrect. If so, skip to the next author

        // shift 1 array entry to novel content values
        offset++;
        // loop through author's novels
        for (int j = 0; j < max1; j++) {

            int novelIndex = i + offset;
            int max2;
             // check if content values of novel or identifier of child count are incorrect. If so, skip to the next novel

            // shift  1 array entry to chapter content values
            offset++;
            ContentValues[] temp = new ContentValues[max2];
            for (int a = 0; a < max2; a++) {
                temp[a] = null;
            }

            boolean flag_empty = true;
            // loop through chapters
            for (int k = 0; k < max2; k++) {
                // check if content values of chapter or identifier of child count are incorrect. If so, skip to the next chapter


                // at least 1 chapter was added, novel is not without chapters
                flag_empty = false;
            }

            // if novel has no chapters skip it

            offset += max2;

            // if novel is OK, add it for SQLite insert
            novels_with_children.add(values[novelIndex]);
            // add novel chapters for SQLite insert
            chapters.add(temp);
        }

        // if author has no novels skip him

        /****************
         *  ACTIVE SQL  *
         ****************/

         boolean authorInserted = false;
         boolean novelInserted = false;
         boolean chapterInserted = false;

         int count_author_instance = 0;
         int count_novel_instance = 0;

         serverID = values[i].getAsLong(Columns.AUTHOR_SERVER_ID);

         picturesToDownload.clear();

         String authorPictureUrl = values[i].getAsString(Columns.AUTHOR_PICTURE);
         if (values[i].containsKey(Columns.AUTHOR_PICTURE))
             values[i].remove(Columns.AUTHOR_PICTURE);

         SQLiteDatabase dbwrite = dbHelper.getWritableDatabase();
         SQLiteStatement insertGenresChaptersConnector = dbwrite.compileStatement ("INSERT OR IGNORE INTO " + Tables.GENRES_CHAPTERS_CONNECTOR + "(" + Columns.CHAPTER_ID + ", " + Columns.GENRE_ID + ", " + Columns.NOVEL_ID + ") VALUES (?, ?, ?)");

         //transaction author start
(2040)  dbwrite.beginTransaction();
        try{
            // serach for author
             Cursor authorIDcursor = dbwrite.query(Tables.AUTHORS, new String[] {Columns.BASE_ID}, "(" + Columns.AUTHOR_NICKNAME + " = ?) AND (" + Columns.AUTHOR_SERVER_ID + " = ?)", new String[]{values[i].getAsString(Columns.AUTHOR_NICKNAME), Integer.toString(values[i].getAsInteger(Columns.AUTHOR_SERVER_ID))}, null, null, null);
            long authorID;
            boolean flag_transaction2_successful = false;

            if (authorIDcursor.moveToFirst()) {
                // if found, get his id
                authorID = authorIDcursor.getInt(0);
            }else{
                // create if not found
                authorID = dbwrite.insert(Tables.AUTHORS, null, values[i]);
            }
            authorIDcursor.close();

            // for every author's novel
            for (int j = 0; j < novels_with_children.size(); j++) {
                ContentValues cvn = novels_with_children.get(j);
                ContentValues[] cvc = chapters.get(j);
                boolean flag_transaction3_successful = false;

                //transaction novel start
                dbwrite.beginTransaction();
                try {
                    // search for novel
                    Cursor novelIDcursor = dbwrite.query(Tables.NOVELS, new String[]{Columns.BASE_ID}, "(" + Columns.NOVEL_TITLE + " = ?)", new String[]{cvn.getAsString(Columns.NOVEL_TITLE)}, null, null, null);
                    long novelID;

                    if (novelIDcursor.moveToFirst()) {
                        // if found get its id
                        novelID = novelIDcursor.getInt(0);
                    }else{
                        //create if not found
                        novelID = dbwrite.insert(Tables.NOVELS, null, cvn);
                    }
                    novelIDcursor.close();

                    // for every chapter
                    for (ContentValues chapterCV : cvc) {
                        //skip if chapter did not pass through initial control tests
                        if (chapterCV == null) {
                            continue;
                        }

                        //find previous chapter if exists
                        Cursor previous = dbwrite.query(Tables.CHAPTERS, new String[] {Columns.BASE_ID}, "(" + Columns.NOVEL_ID + " = ?) AND (" + Columns.CHAPTER_INDEX + " = ?)", new String[] {String.valueOf(novelID), String.valueOf(chapterIndex - 1)}, null, null, null);
                        if ((previous != null) && (previous.moveToFirst()))
                            chapterCV.put(Columns.CHAPTER_ID_PREVIOUS, previous.getLong(previous.getColumnIndex(Columns.BASE_ID)));
                        previous.close();

                        //find next chapter if exists
                        Cursor next = dbwrite.query(Tables.CHAPTERS, new String[] {Columns.BASE_ID}, "(" + Columns.NOVEL_ID + " = ?) AND (" + Columns.CHAPTER_INDEX + " = ?)", new String[] {String.valueOf(novelID), String.valueOf(chapterIndex + 1)}, null, null, null);
                        if ((next != null) && (next.moveToFirst()))
                            chapterCV.put(Columns.CHAPTER_ID_NEXT, next.getLong(next.getColumnIndex(Columns.BASE_ID)));
                        next.close();

                        //transaction chapter start
                        dbwrite.beginTransaction();

                        try {
                            // search for chapter
                            Cursor chapterIDcursor = dbwrite.query(Tables.CHAPTERS, new String[]{Columns.BASE_ID}, "(" + Columns.NOVEL_ID + " = ?) AND (" + Columns.CHAPTER_INDEX + " = ?)", new String[]{Long.toString(novelID), chapterCV.getAsInteger(Columns.CHAPTER_INDEX).toString()}, null, null, null);
                            long chapterID;

                            if (chapterIDcursor.moveToFirst()) {
                                // if found get its id
                                chapterID = chapterIDcursor.getInt(0);
                            } else {
                                // create if not found
                                chapterID = dbwrite.insert(Tables.CHAPTERS, null, chapterCV);
                            }
                            chapterIDcursor.close();

                            // use pre-compiled statement to create connection between genre and chapter (insert entry into connector table)
                            for (byte genreID : genres) {
                                insertGenresChaptersConnector.bindLong(1, chapterID);
                                insertGenresChaptersConnector.bindLong(2, genreID);
                                insertGenresChaptersConnector.bindLong(3, novelID);
                                insertGenresChaptersConnector.executeInsert();
                            }

                            //tie consequent chapters together
                            //write this chapter as NEXT in previous chapter if exests
                            ContentValues cv = new ContentValues();
                            cv.put(Columns.CHAPTER_ID_PREVIOUS, chapterID);
                            dbwrite.update(Tables.CHAPTERS, cv, "(" + Columns.NOVEL_ID + " = ?) AND (" + Columns.CHAPTER_INDEX + " = ?)", new String[] {String.valueOf(novelID), String.valueOf(chapterIndex + 1)});//Tables.CHAPTERS, new String[]{Columns.BASE_ID}, "(" + Columns.NOVEL_TITLE + " = ?)", new String[]{novelValues.getAsString(Columns.AUTHOR_NICKNAME)}, null, null, null);

                            //write this chapter as PREVIOUS in next chapter if exests
                            cv.clear();
                            cv.put(Columns.CHAPTER_ID_NEXT, chapterID);
                            dbwrite.update(Tables.CHAPTERS, cv, "(" + Columns.NOVEL_ID + " = ?) AND (" + Columns.CHAPTER_INDEX + " = ?)", new String[] {String.valueOf(novelID), String.valueOf(chapterIndex - 1)});



                            //if chapter and connector entries added successfully
                            dbwrite.setTransactionSuccessful();
                            flag_transaction3_successful = true;
                            count_novel_instance++;
                        } catch (SQLiteException e) {
                            //TODO Log
                        } finally {
                            //transaction chapter end
                            dbwrite.endTransaction();
                        }
                    }

                    // if at least one chapter of this novel was successfully added
                    if (flag_transaction3_successful) {
                        dbwrite.setTransactionSuccessful();
                        flag_transaction2_successful = true;
                        count_novel_instance++;
                    }else{
                        count_novel_instance = 0;
                    }
                }catch(SQLiteException e) {
                    count_novel_instance = 0;
                    //TODO Log
                }finally {
                    //transaction novel end
                    dbwrite.endTransaction();
                }
            }

            count_author_instance += count_novel_instance;

            // if at least one novel of this author was successfully added
            if (flag_transaction2_successful) {
                dbwrite.setTransactionSuccessful();
                count_author_instance++;
            }else{
                count_author_instance = 0;
            }

        }catch (SQLiteException e) {
            count_author_instance = 0;
            //log
        }finally {
            //transaction1 end
            dbwrite.endTransaction();
            dbwrite.close();
        }

        count += count_author_instance;

        i += offset;
    }

    // notify all uris to be notified

    return count;
}

先感谢您。如果需要任何其他信息,请告诉我。

4

0 回答 0