android Loader eoe_blog_933560_author

心鑫發表於2014-02-20

官方的例子

/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.actionbar;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.IntentCompat;
import android.support.v4.content.Loader;
import android.support.v4.content.pm.ActivityInfoCompat;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.SearchViewCompat;
import android.support.v4.widget.SearchViewCompat.OnCloseListenerCompat;
import android.support.v4.widget.SearchViewCompat.OnQueryTextListenerCompat;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;

import java.io.File;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * Demonstration of the implementation of a custom Loader.
 */
public class LoaderCustomSupport extends FragmentActivity {

	/**
	 * This class holds the per-item data in our Loader.
	 */
	public static class AppEntry {
		private final File mApkFile;

		private Drawable mIcon;

		private final ApplicationInfo mInfo;

		private String mLabel;

		private final AppListLoader mLoader;

		private boolean mMounted;

		public AppEntry(AppListLoader loader, ApplicationInfo info) {
			mLoader = loader;
			mInfo = info;
			mApkFile = new File(info.sourceDir);
		}

		public ApplicationInfo getApplicationInfo() {
			return mInfo;
		}

		public Drawable getIcon() {
			if (mIcon == null) {
				if (mApkFile.exists()) {
					mIcon = mInfo.loadIcon(mLoader.mPm);
					return mIcon;
				} else {
					mMounted = false;
				}
			} else if (!mMounted) {
				// If the app wasn't mounted but is now mounted, reload
				// its icon.
				if (mApkFile.exists()) {
					mMounted = true;
					mIcon = mInfo.loadIcon(mLoader.mPm);
					return mIcon;
				}
			} else {
				return mIcon;
			}

			return mLoader.getContext().getResources()
					.getDrawable(android.R.drawable.sym_def_app_icon);
		}

		public String getLabel() {
			return mLabel;
		}

		void loadLabel(Context context) {
			if (mLabel == null || !mMounted) {
				if (!mApkFile.exists()) {
					mMounted = false;
					mLabel = mInfo.packageName;
				} else {
					mMounted = true;
					CharSequence label = mInfo.loadLabel(context
							.getPackageManager());
					mLabel = label != null ? label.toString()
							: mInfo.packageName;
				}
			}
		}

		@Override
		public String toString() {
			return mLabel;
		}
	}

	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);
		}

		/**
		 * 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;
		}

		public void setData(List<AppEntry> data) {
			clear();
			if (data != null) {
				for (AppEntry appEntry : data) {
					add(appEntry);
				}
			}
		}
	}

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

		// This is the Adapter being used to display the list's data.
		AppListAdapter mAdapter;

		@Override
		public void onStart() {
			printLog("onStart");
			super.onStart();
		}

		public void onResume() {
			super.onResume();
			printLog("onResume");
		};

		public void onPause() {
			printLog("onPause");
			super.onPause();
		};

		public void onStop() {
			printLog("onStop");
			super.onStop();
		};

		// If non-null, this is the current filter the user has provided.
		String mCurFilter;

		OnQueryTextListenerCompat mOnQueryTextListenerCompat;

		@Override
		public View onCreateView(LayoutInflater inflater, ViewGroup container,
				Bundle savedInstanceState) {
			printLog("onCreateView");
			return super.onCreateView(inflater, container, savedInstanceState);
		}

		@Override
		public void onActivityCreated(Bundle savedInstanceState) {
			super.onActivityCreated(savedInstanceState);
			printLog("onActivityCreated");
			// 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 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.
			printLog("onCreateLoader");
			return new AppListLoader(getActivity());
		}

		@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);
			MenuItemCompat
					.setShowAsAction(
							item,
							MenuItemCompat.SHOW_AS_ACTION_IF_ROOM
									| MenuItemCompat.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
			final View searchView = SearchViewCompat
					.newSearchView(getActivity());
			if (searchView != null) {
				SearchViewCompat.setOnQueryTextListener(searchView,
						new OnQueryTextListenerCompat() {
							@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;
							}
						});
				SearchViewCompat.setOnCloseListener(searchView,
						new OnCloseListenerCompat() {
							@Override
							public boolean onClose() {
								if (!TextUtils.isEmpty(SearchViewCompat
										.getQuery(searchView))) {
									SearchViewCompat.setQuery(searchView, null,
											true);
								}
								return true;
							}

						});
				MenuItemCompat.setActionView(item, searchView);
			}
		}

		@Override
		public void onListItemClick(ListView l, View v, int position, long id) {
			// Insert desired behavior here.
			Log.i("LoaderCustom", "Item clicked: " + id);
		}

		@Override
		public void onLoaderReset(Loader<List<AppEntry>> loader) {
			printLog("onLoaderReset");
			mAdapter.setData(null);
		}

		@Override
		public void onLoadFinished(Loader<List<AppEntry>> loader,
				List<AppEntry> data) {
			// Set the new data in the adapter.
			mAdapter.setData(data);
			printLog("onLoadFinished");
			// The list should now be shown.
			if (isResumed()) {
				setListShown(true);
			} else {
				setListShownNoAnimation(true);
			}
		}
	}

	/**
	 * A custom Loader that loads all of the installed applications.
	 */
	public static class AppListLoader extends AsyncTaskLoader<List<AppEntry>> {
		List<AppEntry> mApps;
		final InterestingConfigChanges mLastConfig = new InterestingConfigChanges();

		PackageIntentReceiver mPackageObserver;
		final PackageManager mPm;

		public AppListLoader(Context context) {
			super(context);
			printLog("AppListLoader");
			// 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();
		}

		/**
		 * 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) {
			printLog("deliverResult");
			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);
			}
		}

		/**
		 * 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() {
			printLog("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;
		}

		/**
		 * Handles a request to cancel a load.
		 */
		@Override
		public void onCanceled(List<AppEntry> apps) {
			super.onCanceled(apps);
			printLog("onCanceled");
			// At this point we can release the resources associated with 'apps'
			// if needed.
			onReleaseResources(apps);
		}

		/**
		 * 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.
			printLog("onReleaseResources");
		}

		/**
		 * Handles a request to completely reset the Loader.
		 */
		@Override
		protected void onReset() {
			super.onReset();
			printLog("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;
			}
		}

		/**
		 * 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);
			}
			printLog("onStartLoading");
			// 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();
			printLog("onStopLoading");
		}
	}

	/**
	 * Helper for determining if the configuration has changed in an interesting
	 * way so we need to rebuild the app list.
	 */
	public static class InterestingConfigChanges {
		final Configuration mLastConfiguration = new Configuration();
		int mLastDensity;

		boolean applyNewConfig(Resources res) {
			int configChanges = mLastConfiguration.updateFrom(res
					.getConfiguration());
			boolean densityChanged = mLastDensity != res.getDisplayMetrics().densityDpi;
			if (densityChanged
					|| (configChanges & (ActivityInfo.CONFIG_LOCALE
							| ActivityInfoCompat.CONFIG_UI_MODE | ActivityInfo.CONFIG_SCREEN_LAYOUT)) != 0) {
				mLastDensity = res.getDisplayMetrics().densityDpi;
				return true;
			}
			return false;
		}
	}

	/**
	 * Helper class to look for interesting changes to the installed apps so
	 * that the loader can be updated.
	 */
	public static class PackageIntentReceiver extends BroadcastReceiver {
		final AppListLoader mLoader;

		public PackageIntentReceiver(AppListLoader loader) {
			mLoader = loader;
			IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
			filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
			filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
			filter.addDataScheme("package");
			mLoader.getContext().registerReceiver(this, filter);
			// Register for events related to sdcard installation.
			IntentFilter sdFilter = new IntentFilter();
			sdFilter.addAction(IntentCompat.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
			sdFilter.addAction(IntentCompat.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
			mLoader.getContext().registerReceiver(this, sdFilter);
		}

		@Override
		public void onReceive(Context context, Intent intent) {
			// Tell the loader about the change.
			mLoader.onContentChanged();
		}
	}

	/**
	 * Perform alphabetical comparison of application entry objects.
	 */
	public static final Comparator<AppEntry> ALPHA_COMPARATOR = new Comparator<AppEntry>() {
		private final Collator sCollator = Collator.getInstance();

		@Override
		public int compare(AppEntry object1, AppEntry object2) {
			return sCollator.compare(object1.getLabel(), object2.getLabel());
		}
	};

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		FragmentManager fm = getSupportFragmentManager();

		// Create the list fragment and add it as our sole content.
		if (fm.findFragmentById(android.R.id.content) == null) {
			AppListFragment list = new AppListFragment();
			fm.beginTransaction().add(android.R.id.content, list).commit();
		}
	}

	public static void printLog(String string) {
		Log.d("Loader", string);
	}
}


輸出結果

02-20 17:13:03.718: D/Loader(10625): onCreateView
02-20 17:13:03.843: D/Loader(10625): onActivityCreated
02-20 17:13:03.867: D/Loader(10625): onCreateLoader
02-20 17:13:03.867: D/Loader(10625): AppListLoader
02-20 17:13:03.875: D/Loader(10625): onStart
02-20 17:13:03.875: D/Loader(10625): onStartLoading
02-20 17:13:03.882: D/Loader(10625): onResume
02-20 17:13:03.890: D/Loader(10625): loadInBackground
02-20 17:13:05.148: D/Loader(10625): deliverResult
02-20 17:13:05.148: D/Loader(10625): onLoadFinished
02-20 17:13:05.156: D/Loader(10625): onReleaseResources
//切換離去
02-20 17:13:10.867: D/Loader(10625): onPause
02-20 17:13:10.867: D/Loader(10625): onStop
02-20 17:13:10.867: D/Loader(10625): onStopLoading

//切換回來
02-20 17:13:14.531: D/Loader(10625): onCreateView
02-20 17:13:14.539: D/Loader(10625): onActivityCreated
02-20 17:13:14.546: D/Loader(10625): onStart
02-20 17:13:14.546: D/Loader(10625): deliverResult
02-20 17:13:14.546: D/Loader(10625): onReleaseResources
02-20 17:13:14.546: D/Loader(10625): onStartLoading
02-20 17:13:14.570: D/Loader(10625): onLoadFinished
02-20 17:13:14.570: D/Loader(10625): onResume
//切換離去
02-20 17:13:49.445: D/Loader(10625): onPause
02-20 17:13:49.445: D/Loader(10625): onStop
02-20 17:13:49.445: D/Loader(10625): onStopLoading
//切換回來
02-20 17:13:50.695: D/Loader(10625): onCreateView
02-20 17:13:50.703: D/Loader(10625): onActivityCreated
02-20 17:13:50.718: D/Loader(10625): onStart
02-20 17:13:50.718: D/Loader(10625): deliverResult
02-20 17:13:50.718: D/Loader(10625): onReleaseResources
02-20 17:13:50.718: D/Loader(10625): onStartLoading
02-20 17:13:50.750: D/Loader(10625): onLoadFinished
02-20 17:13:50.757: D/Loader(10625): onResume

總結:

每次切換回來都 用呼叫 onStartLoading 切換去都會呼叫onSopLoading
forceLoad() 才呼叫loadInBackground()


相關文章