Android异步加载器Loader的使用

来源:互联网 发布:java工程师岗位 编辑:程序博客网 时间:2024/04/30 17:15

在Activity或Fragment中加载数据,一般的I/O或数据库操作或数据解析操作,可能会比较耗时,为了提升用户体验,一般将这一的操作放在单独的线程中运行,这样保证操作的流畅性。
Android 3.0 中引入了加载器,支持轻松在 Activity 或片段中异步加载数据。 加载器具有以下特征:
- 可用于每个 Activity 和 Fragment。
- 支持异步加载数据。
- 监控其数据源并在内容变化时传递新结果。
- 在某一配置更改后重建加载器时,会自动重新连接上一个加载器的 Cursor。 因此,它们无需重新查询其数据。

使用Loader

使用加载器的应用通常包括:
- Activity 或 Fragment。
- LoaderManager 的实例。
- 一个 CursorLoader,用于加载由 ContentProvider 支持的数据。您也可以实现自己的 Loader 或 AsyncTaskLoader 子类,- 从其他源中加载数据。
- 一个 LoaderManager.LoaderCallbacks 实现。您可以使用它来创建新加载器,并管理对现有加载器的引用。
- 一种显示加载器数据的方法,如 SimpleCursorAdapter。
- 使用 CursorLoader 时的数据源,如 ContentProvider

启动Loader

LoaderManager 可在 Activity 或 Fragment 内管理一个或多个 Loader 实例。每个 Activity 或片段只有一个 LoaderManager
通常,会使用 Activity 的 onCreate() 方法或片段的 onActivityCreated() 方法初始化 Loader。操作如下:

// Prepare the loader.  Either re-connect with an existing one,// or start a new one.getLoaderManager().initLoader(0, null, this);

注意,initLoader() 方法将返回已创建的 Loader,但您不必捕获其引用。LoaderManager 将自动管理加载器的生命周期。LoaderManager 将根据需要启动和停止加载,并维护加载器的状态及其相关内容。 这意味着你会很少直接与加载器进行交互

重启Loader

当您使用 initLoader() 时(如上所述),它将使用含有指定 ID 的现有加载器(如有)。如果没有,则它会创建一个。但有时,您想放弃这些旧数据并重新开始。

要放弃旧数据,请使用 restartLoader()。例如,当用户的查询更改时,此 SearchView.OnQueryTextListener 实现将重启加载器。 加载器需要重启,以便它能够使用修订后的搜索筛选器执行新查询。

public boolean onQueryTextChanged(String newText) {    // Called when the action bar search text has changed.  Update    // the search filter, and restart the loader to do a new query    // with this filter.    mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;    getLoaderManager().restartLoader(0, null, this);    return true;}

使用 LoaderManager 回调

  • onCreateLoader():针对指定的 ID 进行实例化并返回新的 Loader
    当您尝试访问加载器时(例如,通过 initLoader()),该方法将检查是否已存在由该 ID 指定的加载器。如果没有,它将触发 LoaderManager.LoaderCallbacks 方法 onCreateLoader()。在此方法中,您可以创建新加载器。 通常,这将是 CursorLoader,但您也可以实现自己的 Loader 子类。
  • onLoadFinished() :将在先前创建的加载器完成加载时调用
    当先前创建的加载器完成加载时,将调用此方法。该方法必须在为此加载器提供的最后一个数据释放之前调用。 此时,您应移除所有使用的旧数据(因为它们很快会被释放),但不要自行释放这些数据,因为这些数据归其加载器所有,其加载器会处理它们。
    当加载器发现应用不再使用这些数据时,即会释放它们。 例如,如果数据是来自 CursorLoader 的一个 Cursor,则您不应手动对其调用 close()。如果 Cursor 放置在 CursorAdapter 中,则应使用 swapCursor() 方法,使旧 Cursor 不会关闭。
// This is the Adapter being used to display the list's data.SimpleCursorAdapter mAdapter;...public void onLoadFinished(Loader<Cursor> loader, Cursor data) {    // Swap the new cursor in.  (The framework will take care of closing the    // old cursor once we return.)    mAdapter.swapCursor(data);}
  • onLoaderReset(): 将在先前创建的加载器重置且其数据因此不可用时调用
    此方法将在先前创建的加载器重置且其数据因此不可用时调用。 通过此回调,您可以了解何时将释放数据,因而能够及时移除其引用。
    此实现调用值为 null 的swapCursor():
// This is the Adapter being used to display the list's data.SimpleCursorAdapter mAdapter;...public void onLoaderReset(Loader<Cursor> loader) {    // This is called when the last Cursor provided to onLoadFinished()    // above is about to be closed.  We need to make sure we are no    // longer using it.    mAdapter.swapCursor(null);}

示例

以下是一个 Fragment 完整实现示例。它展示了一个 ListView,其中包含针对联系人内容提供程序的查询结果。它使用 CursorLoader 管理提供程序的查询。

应用如需访问用户联系人(正如此示例中所示),其清单文件必须包括权限 READ_CONTACTS。

public static class CursorLoaderListFragment extends ListFragment        implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {    // This is the Adapter being used to display the list's data.    SimpleCursorAdapter mAdapter;    // If non-null, this is the current filter the user has provided.    String mCurFilter;    @Override public void onActivityCreated(Bundle savedInstanceState) {        super.onActivityCreated(savedInstanceState);        // Give some text to display if there is no data.  In a real        // application this would come from a resource.        setEmptyText("No phone numbers");        // We have a menu item to show in action bar.        setHasOptionsMenu(true);        // Create an empty adapter we will use to display the loaded data.        mAdapter = new SimpleCursorAdapter(getActivity(),                android.R.layout.simple_list_item_2, null,                new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },                new int[] { android.R.id.text1, android.R.id.text2 }, 0);        setListAdapter(mAdapter);        // Prepare the loader.  Either re-connect with an existing one,        // or start a new one.        getLoaderManager().initLoader(0, null, this);    }    @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {        // Place an action bar item for searching.        MenuItem item = menu.add("Search");        item.setIcon(android.R.drawable.ic_menu_search);        item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);        SearchView sv = new SearchView(getActivity());        sv.setOnQueryTextListener(this);        item.setActionView(sv);    }    public boolean onQueryTextChange(String newText) {        // Called when the action bar search text has changed.  Update        // the search filter, and restart the loader to do a new query        // with this filter.        mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;        getLoaderManager().restartLoader(0, null, this);        return true;    }    @Override public boolean onQueryTextSubmit(String query) {        // Don't care about this.        return true;    }    @Override public void onListItemClick(ListView l, View v, int position, long id) {        // Insert desired behavior here.        Log.i("FragmentComplexList", "Item clicked: " + id);    }    // These are the Contacts rows that we will retrieve.    static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {        Contacts._ID,        Contacts.DISPLAY_NAME,        Contacts.CONTACT_STATUS,        Contacts.CONTACT_PRESENCE,        Contacts.PHOTO_ID,        Contacts.LOOKUP_KEY,    };    public Loader<Cursor> onCreateLoader(int id, Bundle args) {        // This is called when a new Loader needs to be created.  This        // sample only has one Loader, so we don't care about the ID.        // First, pick the base URI to use depending on whether we are        // currently filtering.        Uri baseUri;        if (mCurFilter != null) {            baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,                    Uri.encode(mCurFilter));        } else {            baseUri = Contacts.CONTENT_URI;        }        // Now create and return a CursorLoader that will take care of        // creating a Cursor for the data being displayed.        String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("                + Contacts.HAS_PHONE_NUMBER + "=1) AND ("                + Contacts.DISPLAY_NAME + " != '' ))";        return new CursorLoader(getActivity(), baseUri,                CONTACTS_SUMMARY_PROJECTION, select, null,                Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");    }    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {        // Swap the new cursor in.  (The framework will take care of closing the        // old cursor once we return.)        mAdapter.swapCursor(data);    }    public void onLoaderReset(Loader<Cursor> loader) {        // This is called when the last Cursor provided to onLoadFinished()        // above is about to be closed.  We need to make sure we are no        // longer using it.        mAdapter.swapCursor(null);    }}

更多示例

加载设备中所有安装应用的例子,使用自定义Loader的方式实现:

public static class AppListLoader extends AsyncTaskLoader<List<AppEntry>> {        final InterestingConfigChanges mLastConfig = new InterestingConfigChanges();        final PackageManager mPm;        List<AppEntry> mApps;        PackageIntentReceiver mPackageObserver;        public AppListLoader(Context context) {            super(context);            // Retrieve the package manager for later use; note we don't            // use 'context' directly but instead the save global application            // context returned by getContext().            mPm = getContext().getPackageManager();        }        /**         * This is where the bulk of our work is done.  This function is         * called in a background thread and should generate a new set of         * data to be published by the loader.         */        @Override public List<AppEntry> loadInBackground() {            // Retrieve all known applications.            List<ApplicationInfo> apps = mPm.getInstalledApplications(                    PackageManager.GET_UNINSTALLED_PACKAGES |                    PackageManager.GET_DISABLED_COMPONENTS);            if (apps == null) {                apps = new ArrayList<ApplicationInfo>();            }            final Context context = getContext();            // Create corresponding array of entries and load their labels.            List<AppEntry> entries = new ArrayList<AppEntry>(apps.size());            for (int i=0; i<apps.size(); i++) {                AppEntry entry = new AppEntry(this, apps.get(i));                entry.loadLabel(context);                entries.add(entry);            }            // Sort the list.            Collections.sort(entries, ALPHA_COMPARATOR);            // Done!            return entries;        }        /**         * Called when there is new data to deliver to the client.  The         * super class will take care of delivering it; the implementation         * here just adds a little more logic.         */        @Override public void deliverResult(List<AppEntry> apps) {            if (isReset()) {                // An async query came in while the loader is stopped.  We                // don't need the result.                if (apps != null) {                    onReleaseResources(apps);                }            }            List<AppEntry> oldApps = apps;            mApps = apps;            if (isStarted()) {                // If the Loader is currently started, we can immediately                // deliver its results.                super.deliverResult(apps);            }            // At this point we can release the resources associated with            // 'oldApps' if needed; now that the new result is delivered we            // know that it is no longer in use.            if (oldApps != null) {                onReleaseResources(oldApps);            }        }        /**         * Handles a request to start the Loader.         */        @Override protected void onStartLoading() {            if (mApps != null) {                // If we currently have a result available, deliver it                // immediately.                deliverResult(mApps);            }            // Start watching for changes in the app data.            if (mPackageObserver == null) {                mPackageObserver = new PackageIntentReceiver(this);            }            // Has something interesting in the configuration changed since we            // last built the app list?            boolean configChange = mLastConfig.applyNewConfig(getContext().getResources());            if (takeContentChanged() || mApps == null || configChange) {                // If the data has changed since the last time it was loaded                // or is not currently available, start a load.                forceLoad();            }        }        /**         * Handles a request to stop the Loader.         */        @Override protected void onStopLoading() {            // Attempt to cancel the current load task if possible.            cancelLoad();        }        /**         * Handles a request to cancel a load.         */        @Override public void onCanceled(List<AppEntry> apps) {            super.onCanceled(apps);            // At this point we can release the resources associated with 'apps'            // if needed.            onReleaseResources(apps);        }        /**         * Handles a request to completely reset the Loader.         */        @Override protected void onReset() {            super.onReset();            // Ensure the loader is stopped            onStopLoading();            // At this point we can release the resources associated with 'apps'            // if needed.            if (mApps != null) {                onReleaseResources(mApps);                mApps = null;            }            // Stop monitoring for changes.            if (mPackageObserver != null) {                getContext().unregisterReceiver(mPackageObserver);                mPackageObserver = null;            }        }        /**         * Helper function to take care of releasing resources associated         * with an actively loaded data set.         */        protected void onReleaseResources(List<AppEntry> apps) {            // For a simple List<> there is nothing to do.  For something            // like a Cursor, we would close it here.        }    }

核心实现在Fragment中:

public static class AppListFragment extends ListFragment            implements OnQueryTextListener, LoaderManager.LoaderCallbacks<List<AppEntry>> {        // This is the Adapter being used to display the list's data.        AppListAdapter mAdapter;        // If non-null, this is the current filter the user has provided.        String mCurFilter;        @Override public void onActivityCreated(Bundle savedInstanceState) {            super.onActivityCreated(savedInstanceState);            // Give some text to display if there is no data.  In a real            // application this would come from a resource.            setEmptyText("No applications");            // We have a menu item to show in action bar.            setHasOptionsMenu(true);            // Create an empty adapter we will use to display the loaded data.            mAdapter = new AppListAdapter(getActivity());            setListAdapter(mAdapter);            // Start out with a progress indicator.            setListShown(false);            // Prepare the loader.  Either re-connect with an existing one,            // or start a new one.            getLoaderManager().initLoader(0, null, this);        }        @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {            // Place an action bar item for searching.            MenuItem item = menu.add("Search");            item.setIcon(android.R.drawable.ic_menu_search);            item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM                    | MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);            SearchView sv = new SearchView(getActivity());            sv.setOnQueryTextListener(this);            item.setActionView(sv);        }        @Override public boolean onQueryTextChange(String newText) {            // Called when the action bar search text has changed.  Since this            // is a simple array adapter, we can just have it do the filtering.            mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;            mAdapter.getFilter().filter(mCurFilter);            return true;        }        @Override public boolean onQueryTextSubmit(String query) {            // Don't care about this.            return true;        }        @Override public void onListItemClick(ListView l, View v, int position, long id) {            // Insert desired behavior here.            Log.i("LoaderCustom", "Item clicked: " + id);        }        @Override public Loader<List<AppEntry>> onCreateLoader(int id, Bundle args) {            // This is called when a new Loader needs to be created.  This            // sample only has one Loader with no arguments, so it is simple.            return new AppListLoader(getActivity());        }        @Override public void onLoadFinished(Loader<List<AppEntry>> loader, List<AppEntry> data) {            // Set the new data in the adapter.            mAdapter.setData(data);            // The list should now be shown.            if (isResumed()) {                setListShown(true);            } else {                setListShownNoAnimation(true);            }        }        @Override public void onLoaderReset(Loader<List<AppEntry>> loader) {            // Clear the data in the adapter.            mAdapter.setData(null);        }    }

列表适配器:

public static class AppListAdapter extends ArrayAdapter<AppEntry> {        private final LayoutInflater mInflater;        public AppListAdapter(Context context) {            super(context, android.R.layout.simple_list_item_2);            mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);        }        public void setData(List<AppEntry> data) {            clear();            if (data != null) {                addAll(data);            }        }        /**         * Populate new items in the list.         */        @Override public View getView(int position, View convertView, ViewGroup parent) {            View view;            if (convertView == null) {                view = mInflater.inflate(R.layout.list_item_icon_text, parent, false);            } else {                view = convertView;            }            AppEntry item = getItem(position);            ((ImageView)view.findViewById(R.id.icon)).setImageDrawable(item.getIcon());            ((TextView)view.findViewById(R.id.text)).setText(item.getLabel());            return view;        }    }

参考地址:http://developer.android.com/intl/zh-cn/guide/components/loaders.html

0 0