2014-01-05 92 views
13

所以我已經到了需要爲我的應用程序實現SQLite數據庫的地步。 繼「繁忙的編碼器的Android開發指南」之後,我創建了一個擴展SQLiteOpenHelper的DatabaseHelper類。Android:碎片,SQLite和加載器

我的一個用例是對數據庫運行查詢,並在ListViewFragment(我使用支持庫中的片段)中顯示結果。

據我瞭解,使用​​是不是真的合適,即使它是不建議由於一些封裝的這種方法裏面的邏輯實際上是在主線程上執行的事實,特別是reQuery()其中我的理解是在Activity重新啓動時執行的。

所以我一直在試圖獲得與Loader類的第一次相識,只看到這一點:

"The only supplied concrete implementation of a Loader is CursorLoader, and that is only for use with a ContentProvider"

我最初的想法是要實現自己的內容提供商,也許防止其他從獲得訪問它的應用程序,然後我讀通過developer.android.com的ContentProvider文檔中的以下內容:

"You don't need a provider to use an SQLite database if the use is entirely within your own application."

我也一直在打這個:

https://github.com/commonsguy/cwac-loaderex

但我不熟悉這個項目,不知道是否可以在生產環境中使用。

所以,現在我所能想到的是在Fragment中創建一堆AsyncTask實例並適當地管理它們的生命週期,確保它們在需要和不需要時被取消。

還有其他的選擇嗎?

+0

我會使用CommonsWare的SQLiteCursorLoader,它似乎正是你想要的。我沒有看到使用它的生產應用程序的任何問題。 –

+0

你需要顯示數據庫中的數據還是需要對數據進行操作? – NickF

+0

@NickF在這種情況下,我想要的只是檢索數據。 – cdroid

回答

6

您可以擴展Loader類以執行其他異步工作,例如從數據庫直接加載。

Here


編輯的示例:加入裝載機使用的一個更好的例子。

最後設法找到了幫助我理解事情如何工作的教程。

通過擴展裝載機類,你能避免與內容觀察員和其非常容易實現(最後) 需要在代碼中採取的修改

  • 添加實施LoaderManager.LoaderCallbacks<D>,其中搞亂D是您的數據列表(片斷1)
  • 創建您的裝載機類,複製代碼段2,從DB
  • 添加數據的加載最後調用裝載機1調用init的,然後刷新重啓電話。 (片斷2 & 3)

片段1:如何 「鏈接」 裝載機與片段:

public static class AppListFragment extends ListFragment implements 
     LoaderManager.LoaderCallbacks<List<SampleItem>> { 

    public Loader<List<SampleItem>> onCreateLoader(int id, Bundle args) { 
    //... 
    return new SampleLoader (getActivity()); 
    } 

    public void onLoadFinished(Loader<List<SampleItem>> loader, List<SampleItem> data) { 
    // ... 
    mAdapter.setData(data); 

    if (isResumed()) { 
     setListShown(true); 
    } else { 
     setListShownNoAnimation(true); 
    } 
    // ... 
} 

    public void onLoaderReset(Loader<List<SampleItem>> loader) { 
    // ... 
    mAdapter.setData(null); 
    // ... 
    } 

    /* ... */ 
} 

片段2:您的自定義裝載機的架構: (我已經加倍評論觀察者的事情,因爲我認爲從一開始就很難實現它,並且您可以簡單地調用加載程序而不妨礙自動刷新)

public class SampleLoader extends AsyncTaskLoader<List<SampleItem>> { 

    // We hold a reference to the Loader’s data here. 
    private List<SampleItem> mData; 

    public SampleLoader(Context ctx) { 
    // Loaders may be used across multiple Activitys (assuming they aren't 
    // bound to the LoaderManager), so NEVER hold a reference to the context 
    // directly. Doing so will cause you to leak an entire Activity's context. 
    // The superclass constructor will store a reference to the Application 
    // Context instead, and can be retrieved with a call to getContext(). 
    super(ctx); 
    } 

    /****************************************************/ 
    /** (1) A task that performs the asynchronous load **/ 
    /****************************************************/ 

    @Override 
    public List<SampleItem> loadInBackground() { 
    // This method is called on a background thread and should generate a 
    // new set of data to be delivered back to the client. 
    List<SampleItem> data = new ArrayList<SampleItem>(); 

    // TODO: Perform the query here and add the results to 'data'. 

    return data; 
    } 

    /********************************************************/ 
    /** (2) Deliver the results to the registered listener **/ 
    /********************************************************/ 

    @Override 
    public void deliverResult(List<SampleItem> data) { 
    if (isReset()) { 
     // The Loader has been reset; ignore the result and invalidate the data. 
     releaseResources(data); 
     return; 
    } 

    // Hold a reference to the old data so it doesn't get garbage collected. 
    // We must protect it until the new data has been delivered. 
    List<SampleItem> oldData = mData; 
    mData = data; 

    if (isStarted()) { 
     // If the Loader is in a started state, deliver the results to the 
     // client. The superclass method does this for us. 
     super.deliverResult(data); 
    } 

    // Invalidate the old data as we don't need it any more. 
    if (oldData != null && oldData != data) { 
     releaseResources(oldData); 
    } 
    } 

    /*********************************************************/ 
    /** (3) Implement the Loader’s state-dependent behavior **/ 
    /*********************************************************/ 

    @Override 
    protected void onStartLoading() { 
    if (mData != null) { 
     // Deliver any previously loaded data immediately. 
     deliverResult(mData); 
    } 

    // Begin monitoring the underlying data source. 
    ////if (mObserver == null) { 
     ////mObserver = new SampleObserver(); 
     // TODO: register the observer 
    ////} 

    //// takeContentChanged() can still be implemented if you want 
    ////  to mix your refreshing in that mechanism 
    if (takeContentChanged() || mData == null) { 
     // When the observer detects a change, it should call onContentChanged() 
     // on the Loader, which will cause the next call to takeContentChanged() 
     // to return true. If this is ever the case (or if the current data is 
     // null), we force a new load. 
     forceLoad(); 
    } 
    } 

    @Override 
    protected void onStopLoading() { 
    // The Loader is in a stopped state, so we should attempt to cancel the 
    // current load (if there is one). 
    cancelLoad(); 

    // Note that we leave the observer as is. Loaders in a stopped state 
    // should still monitor the data source for changes so that the Loader 
    // will know to force a new load if it is ever started again. 
    } 

    @Override 
    protected void onReset() { 
    // Ensure the loader has been stopped. 
    onStopLoading(); 

    // At this point we can release the resources associated with 'mData'. 
    if (mData != null) { 
     releaseResources(mData); 
     mData = null; 
    } 

    // The Loader is being reset, so we should stop monitoring for changes. 
    ////if (mObserver != null) { 
     // TODO: unregister the observer 
    //// mObserver = null; 
    ////} 
    } 

    @Override 
    public void onCanceled(List<SampleItem> data) { 
    // Attempt to cancel the current asynchronous load. 
    super.onCanceled(data); 

    // The load has been canceled, so we should release the resources 
    // associated with 'data'. 
    releaseResources(data); 
    } 

    private void releaseResources(List<SampleItem> data) { 
    // For a simple List, there is nothing to do. For something like a Cursor, we 
    // would close it in this method. All resources associated with the Loader 
    // should be released here. 
    } 

    /*********************************************************************/ 
    /** (4) Observer which receives notifications when the data changes **/ 
    /*********************************************************************/ 

    // NOTE: Implementing an observer is outside the scope of this post (this example 
    // uses a made-up "SampleObserver" to illustrate when/where the observer should 
    // be initialized). 

    // The observer could be anything so long as it is able to detect content changes 
    // and report them to the loader with a call to onContentChanged(). For example, 
    // if you were writing a Loader which loads a list of all installed applications 
    // on the device, the observer could be a BroadcastReceiver that listens for the 
    // ACTION_PACKAGE_ADDED intent, and calls onContentChanged() on the particular 
    // Loader whenever the receiver detects that a new application has been installed. 
    // Please don’t hesitate to leave a comment if you still find this confusing! :) 
    ////private SampleObserver mObserver; 
} 

片段3:如何調用的第一次(只)

// Initialize a Loader with an id. If the Loader with this id is not 
    // initialized before 
    getLoaderManager().initLoader(LOADER_ID, null, this); 

片段4裝載機:刷新數據(回顧查詢)

// Check if the loader exists and then restart it. 
if (getLoaderManager().getLoader(LOADER_ID) != null) 
    getLoaderManager().restartLoader(LOADER_ID, null, this); 

Re ference:

  • 片段1:裝載機的使用從here
  • 片段2萃取:here對於更多的信息和邏輯整個孔文章
  • 片段3 & 4讀:只是裝載機用法。

這些完整的代碼也github

8

我認爲實現內容提供者是一個好主意,不管數據在應用程序之外無法訪問。它提供了非常現代的界面,根據我的經驗,您的應用程序錯誤容易出現數據庫鎖定問題和其他數據庫特定問題。

我在我的最新項目中實現了它,我非常樂意使用它。

2

上傳的創建者如果數據庫包含成千上萬的記錄考慮madlymad的回答
如果不能保持它的愚蠢和簡單,使用SQLiteOpenHelper並創建一個返回的方法將你的數據作爲字符串數組或定義你的一個對象。
也使用定製/常規CursorAdapterArrayAdapter

1

我使用SQLiteOpenHelper創建我的數據庫。我爲所有的表格創建了Java類,當我從數據庫中獲取數據時,我將它放在一個ArrayList中。 ArrayList然後加載到Listview的適配器中。

+0

正如其他地方所指出的,這對於較小的記錄集可能是好的,並且可能有問題。運行時是否阻塞UI線程?如果你有一個在後臺線程中使用這個策略,你可以更新你的答案來反映這一點嗎?我也有興趣在答案中看到一個很好的示例參考。 - 謝謝 –

7

我推薦OrmLite庫,一個輕量級的對象關係映射,可以爲Android工作。這個庫會讓你的生活更輕鬆。您不需要手動創建或更新數據庫,您不需要專注於管理數據庫連接,使用DAO方法(通常不需要編寫自己的代碼),所有查詢的選擇,插入和更新都將變得更加容易sql查詢)和很多功能。他們有some examples,你可以開始。

如果你想使用Loader,有一個ORMLite Extras,可用於github(您可以使用與支持android庫兼容的支持包)上的ORMLite的附加功能。下面是一個例子使用我以前的項目:

public class EventsFragment extends Fragment implements LoaderCallbacks<Cursor>{ 
    private static final int LOADER_ID = EventsFragment.class.getName().hashCode(); 
    @Override 
    public void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    getLoaderManager().initLoader(LOADER_ID, null, this); 
    } 

    @Override 
public View onCreateView(LayoutInflater inflater, ViewGroup container, 
     Bundle savedInstanceState) { 
    View layoutRoot = inflater.inflate(
      R.layout.fragment_events, null); 
    lvEvents = (ListView) layoutRoot.findViewById(R.id.lvEvents); 

    adapter = new EventAdapter(getActivity(), null, null); 
    lvEvents.setAdapter(adapter); 

    return layoutRoot; 
} 

    @Override 
    public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) { 
     try { 
      PreparedQuery<Event> query = getDatabaseHelper().getEventDao().getQuery(); 
      return getDatabaseHelper().getEventDao().getSQLCursorLoader(query); 
     } catch (Exception e) { 
     e.printStackTrace(); 
    } 

    return null; 
} 

@Override 
public void onLoadFinished(Loader<Cursor> arg0, Cursor cursor) { 
    adapter.swapCursor(cursor); 
    try { 
     adapter.setQuery(getDatabaseHelper().getEventDao().getQuery()); 
    } catch (SQLException e) { 
     e.printStackTrace(); 
    } 
     } 

@Override 
public void onLoaderReset(Loader<Cursor> arg0) { 
    adapter.swapCursor(null); 
} 

    private OrmliteDatabaseHelper getDatabaseHelper(){ 
     return ((MainActivity)getActivity()).getDatabaseHelper(); 
    } 
} 

適配器

public class EventAdapter extends OrmliteCursorAdapter<Event>{ 

public EventAdapter(Context context, Cursor c, PreparedQuery<Event> query) { 
    super(context, c, query); 
} 

@Override 
public void bindView(View itemView, Context context, Event item) { 
    TextView tvEventTitle = (TextView) itemView.findViewById(R.id.tvEventTitle); 
    TextView tvEventStartDate = (TextView) itemView.findViewById(R.id.tvEventStartDate); 

    tvEventTitle.setText(item.getTitle()); 
    tvEventStartDate.setText(item.getFormatStartDate()); 
} 

@Override 
public View newView(Context context, Cursor arg1, ViewGroup arg2) { 
    LayoutInflater inflater = LayoutInflater.from(context); 
    View retView = inflater.inflate(R.layout.event_item_row, arg2, false); 
    return retView; 
} 
} 

和一個自定義道,其上面的光標適配器提供PreparedQuery

public interface IEventDao extends Dao<Event, Integer>{ 
    PreparedQuery<Event> getQuery() throws SQLException; 
    OrmliteCursorLoader<Event> getSQLCursorLoader(Context context, PreparedQuery<Event> query) throws SQLException; 
} 

public class EventDao extends AndroidBaseDaoImpl<Event, Integer> implements IEventDao{ 

public EventDao(ConnectionSource connectionSource) throws SQLException { 
    super(connectionSource, Event.class); 
} 

public EventDao(ConnectionSource connectionSource, 
     DatabaseTableConfig<Event> tableConfig) throws SQLException { 
    super(connectionSource, tableConfig); 
} 

@Override 
public PreparedQuery<Event> getQuery() throws SQLException{ 
    return queryBuilder().prepare(); 
} 
} 

希望這可以幫助!