Android之经典Launcher主菜单模块学习

相信Android系统经典Launcher大家都见过是什么样子。如下图所示,是4.0比较原始的Launcher主菜单功能,今天我们要学习的就是这一块,通过这个小代码,我们可以复习的知识点有:

①. 应用的获取与处理,包括SD中的应用。

②. 动态监听用户应用安装、卸载以及语言系统的切换,比如中文切换到英文状态。

③. 仿ViewPager和PagerIndicater自定义View的实现,注意是仿哦,不是同一个。

④...

 

Android之经典Launcher主菜单模块学习_第1张图片

 

我们都知道,如果直接将系统的源码弄出来,直接导入eclipse是会报错的,下面我们看看从源码中移植出来的效果图,仅是这个模块而已!

Android之经典Launcher主菜单模块学习_第2张图片  Android之经典Launcher主菜单模块学习_第3张图片

 

下面让我们来看看源码结构,由于仅仅是一个简单的例子,所以细节未过多考虑,敬请谅解:

Android之经典Launcher主菜单模块学习_第4张图片

 

这个App是整个程序的入口,继承Application,在这里面,以静态变量的形式缓存了所有的应用,还有注册了应用添加、删除、变化等广播,以及相应处理并通知MainActivity,下面,就让我们来看看这个最重要的类,几乎最重要的知识点都在这里面了:

public class App extends Application {
	public static String TAG = "way";
	private BroadcastReceiver mLauncherReceiver;
	static ArrayList mApps;
	static final HandlerThread sWorkerThread = new HandlerThread("LoadingApps");
	static {
		sWorkerThread.start();
	}
	static final Handler sWorkerHandler = new Handler(sWorkerThread.getLooper());
	static final Handler sHandler = new Handler();
	private WeakReference mLauncher;

	@Override
	public void onCreate() {
		super.onCreate();
		registerRecever();// 注册应用添加、删除等广播
		getAllApps();
	}

	/**
	 * 注册应用添加、删除等广播
	 */
	private void registerRecever() {
		// register recevier
		mLauncherReceiver = new LauncherReceiver();
		IntentFilter filter = new IntentFilter();
		filter.addAction(Intent.ACTION_PACKAGE_ADDED);
		filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
		filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
		filter.addDataScheme("package");
		registerReceiver(mLauncherReceiver, filter);
		filter = new IntentFilter();
		filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
		filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
		registerReceiver(mLauncherReceiver, filter);
		filter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
		registerReceiver(mLauncherReceiver, filter);
	}

	/**
	 * 安全启动Activity,防止未找到应用或权限问题而挂掉
	 * 
	 * @param intent
	 * @return
	 */
	public boolean startActivitySafely(Intent intent) {
		intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		try {
			startActivity(intent);
			return true;
		} catch (ActivityNotFoundException e) {
			// Toast.makeText(this, R.string.activity_not_found,
			// Toast.LENGTH_SHORT).show();
			Log.e(TAG, "Unable to launch intent = " + intent, e);
		} catch (SecurityException e) {
			// Toast.makeText(this, R.string.activity_not_found,
			// Toast.LENGTH_SHORT).show();
			Log.e(TAG, "does not have the permission to launch intent = "
					+ intent, e);
		} catch (Exception e) {
			Log.e(TAG, "catch Exception ", e);
		}
		return false;
	}

	/**
	 * 获取所有应用信息,供外部调用
	 * 
	 * @return
	 */
	public ArrayList getAllApps() {
		if (mApps == null) {
			mApps = new ArrayList();
			fillAllapps();
		}
		return mApps;
	}

	/**
	 * 弱引用管理主界面
	 * 
	 * @param launcher
	 */
	public void setLauncher(MainActivity launcher) {
		this.mLauncher = new WeakReference(launcher);
	}

	/**
	 * 搜索所有的app
	 */
	private void fillAllapps() {
		final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
		mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);

		final PackageManager packageManager = getPackageManager();
		List apps = packageManager.queryIntentActivities(
				mainIntent, 0);
		if (mApps != null)
			mApps.clear();
		else
			mApps = new ArrayList();
		for (ResolveInfo app : apps) {
			mApps.add(new ApplicationInfo(this, app));
		}
		Collections.sort(mApps, APP_NAME_COMPARATOR);// 按应用名排序
		// sortAppsByCustom(mApps);//自定义排序,如果有的话
	}

	/**
	 * 重新搜索所有应用 当重新加载的时候调用
	 */
	private void refillAllapps() {
		fillAllapps();
	}

	/**
	 * 根据包名寻找应用
	 * 
	 * @param packageName
	 *            包名
	 * @return
	 */
	private List findActivitiesForPackage(String packageName) {
		final PackageManager packageManager = getPackageManager();
		final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
		mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
		mainIntent.setPackage(packageName);

		final List apps = packageManager.queryIntentActivities(
				mainIntent, 0);
		return apps != null ? apps : new ArrayList();
	}

	/**
	 * 添加指定包名的应用,当监听到用户安装新应用的时候调用
	 * 
	 * @param packageName
	 */
	private void addPackage(String packageName) {
		final List matches = findActivitiesForPackage(packageName);

		if (matches.size() > 0) {
			for (ResolveInfo info : matches) {
				mApps.add(new ApplicationInfo(this, info));
			}
		}
	}

	/**
	 * 移除指定包名的应用,当监听到用户删除应用时调用
	 * 
	 * @param packageName
	 */
	private void removePackage(String packageName) {
		for (int i = mApps.size() - 1; i >= 0; i--) {
			ApplicationInfo info = mApps.get(i);
			final ComponentName component = info.intent.getComponent();
			if (packageName.equals(component.getPackageName())) {
				mApps.remove(i);
			}
		}
	}

	/**
	 * 此处可以自定义应用排序方式,我未调用该函数
	 * 
	 * @param list
	 */
	private void sortAppsByCustom(List list) {
		int N = list.size();

		// 没有自定义排序表,则按名称排序
		for (int i = 0; i < N; i++) {
			ApplicationInfo app = list.get(i);
			app.index = i;
		}
		Collections.sort(list, APPLICATION_CUST_SORT);
	}

	/**
	 * 自定义排序的Comparator
	 */
	static final Comparator APPLICATION_CUST_SORT = new Comparator() {
		public final int compare(ApplicationInfo a, ApplicationInfo b) {
			return a.index - b.index;
		}
	};

	/**
	 * 根据应用名排序的Comparator
	 */
	static final Comparator APP_NAME_COMPARATOR = new Comparator() {
		public final int compare(ApplicationInfo a, ApplicationInfo b) {
			int result = Collator.getInstance().compare(a.title.toString(),
					b.title.toString());
			if (result == 0) {
				result = a.componentName.compareTo(b.componentName);
			}
			return result;
		}
	};

	/**
	 * 处理应用更新的任务
	 *包括添加、删除、更新、更改语言等 
	 * 
	 */
	private class PackageUpdatedTask implements Runnable {
		public static final int OP_NONE = 0;//未知状态
		public static final int OP_ADD = 1;//添加应用
		public static final int OP_UPDATE = 2;//更新应用
		public static final int OP_REMOVED = 3;//移除应用
		public static final int OP_RELOAD = 4;//重新加载,比如切换语言等

		int mOp;
		String[] mPackages;

		public PackageUpdatedTask(int op, String[] packages) {
			mOp = op;
			mPackages = packages;
		}

		public PackageUpdatedTask(int op) {
			mOp = op;
		}

		@Override
		public void run() {
			if (mOp == OP_RELOAD) {
				refillAllapps();
			} else {
				final String[] packages = mPackages;
				final int N = packages.length;
				switch (mOp) {
				case OP_ADD:
					if (N > 0) {
						sHandler.post(new Runnable() {
							@Override
							public void run() {
								for (int i = 0; i < N; i++) {
									Log.d(TAG,
											"PackageUpdatedTask add packageName = "
													+ packages[i]);
									addPackage(packages[i]);
								}
								sHandler.post(new Runnable() {

									@Override
									public void run() {
										MainActivity launcher = mLauncher.get();
										if (launcher != null) {
											launcher.bindAllapps();//回调主界面更新
										}
									}
								});
							}
						});
					}
					break;

				case OP_REMOVED:
					if (N > 0) {
						sHandler.post(new Runnable() {

							@Override
							public void run() {
								for (int i = 0; i < N; i++) {
									Log.d(TAG,
											"PackageUpdatedTask remove packageName = "
													+ packages[i]);
									removePackage(packages[i]);
								}
								sHandler.post(new Runnable() {

									@Override
									public void run() {
										MainActivity launcher = mLauncher.get();
										if (launcher != null) {
											launcher.bindAllapps();//回调主界面更新
										}
									}
								});
							}
						});
					}
					break;

				case OP_UPDATE://更新,这里未作处理
					for (int i = 0; i < N; i++) {
						Log.d(TAG, "PackageUpdatedTask update packageName = "
								+ packages[i]);
					}
					break;
				}
			}
		}

	}

	private class LauncherReceiver extends BroadcastReceiver {
		@Override
		public void onReceive(Context context, Intent intent) {
			final String action = intent.getAction();
			Log.d(TAG, "LauncherReceiver onRecive action = " + action);
			//应用添加、改变、移除的广播
			if (Intent.ACTION_PACKAGE_ADDED.equals(action)
					|| Intent.ACTION_PACKAGE_CHANGED.equals(action)
					|| Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
				final String packageName = intent.getData()
						.getSchemeSpecificPart();
				final boolean replacing = intent.getBooleanExtra(
						Intent.EXTRA_REPLACING, false);
				Log.d(TAG, "LauncherReceiver onRecive packageName = "
						+ packageName);
				int op = PackageUpdatedTask.OP_NONE;
				if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
					op = PackageUpdatedTask.OP_UPDATE;
				} else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
					if (!replacing)
						op = PackageUpdatedTask.OP_REMOVED;
				} else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
					if (!replacing)
						op = PackageUpdatedTask.OP_ADD;
				}
				if (op != PackageUpdatedTask.OP_NONE) {
					sWorkerHandler.post(new PackageUpdatedTask(op,
							new String[] { packageName }));
				}

			} else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
					.equals(action)) {//SD卡应用可用的广播,有用户的应用安装到SD卡中
				String[] packages = intent
						.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
				sWorkerHandler.post(new PackageUpdatedTask(
						PackageUpdatedTask.OP_ADD, packages));
			} else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE
					.equals(action)) {//SD卡应用可用的广播
				String[] packages = intent
						.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
				sWorkerHandler.post(new PackageUpdatedTask(
						PackageUpdatedTask.OP_REMOVED, packages));
			} else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {//系统切换语言的广播
				sWorkerHandler.post(new PackageUpdatedTask(
						PackageUpdatedTask.OP_RELOAD));
			}
		}

	}
}


ApplicationInfo其实就是一个JavaBean,但又不全是,他也有一个特殊之处,就是对所有应用的图标做了一下处理,使得显示在我们面前的应用图标不至于参差不齐,或者区别太大,这也算是一个值得学习之处。我们还是来看一下吧,重点是下面那个应用图标处理工具类。

public class ApplicationInfo {
	public CharSequence title;//应用名
	public Bitmap iconBitmap;//应用图标
	public Intent intent;//应用的Intent
	public ComponentName componentName;//应用包名
	
    int	index;	
    
	private PackageManager mPackageManager;
	private Context mContext;
	
	public ApplicationInfo(Context context, ResolveInfo info) {
		mContext = context;
		mPackageManager = context.getPackageManager();
		this.componentName = new ComponentName(
				info.activityInfo.applicationInfo.packageName,
				info.activityInfo.name);
		this.title = info.loadLabel(mPackageManager);
		this.iconBitmap = loadIcon(info);
		Intent intent = new Intent(Intent.ACTION_MAIN);
		intent.addCategory(Intent.CATEGORY_LAUNCHER);
		intent.setComponent(componentName);
		intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
		this.intent = intent;
	}
	
	private Bitmap loadIcon(ResolveInfo info) {
		Bitmap bitmap = BitmapUtility.createIconBitmap(
				info.activityInfo.loadIcon(mPackageManager), mContext);
		
		return bitmap;
	}
}
/**
 * 这是一个应用图标处理的工具类
 * @author way
 *
 */
final class BitmapUtility {
    private static int sIconWidth = -1;
    private static int sIconHeight = -1;
    private static int sIconTextureWidth = -1;
    private static int sIconTextureHeight = -1;
    
    private static final Rect sOldBounds = new Rect();
    private static final Canvas sCanvas = new Canvas();
    static {
        sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
                Paint.FILTER_BITMAP_FLAG));
    }
    
    private static void initStatics(Context context) {
        final Resources resources = context.getResources();

        sIconWidth = sIconHeight = (int) resources.getDimension(android.R.dimen.app_icon_size);
        sIconTextureWidth = sIconTextureHeight = sIconWidth;
    }    
    
    static Bitmap createIconBitmap(Drawable icon, Context context) {
        synchronized (sCanvas) { // we share the statics :-(
            if (sIconWidth == -1) {
                initStatics(context);
            }

            int width = sIconWidth;
            int height = sIconHeight;

            if (icon instanceof PaintDrawable) {
                PaintDrawable painter = (PaintDrawable) icon;
                painter.setIntrinsicWidth(width);
                painter.setIntrinsicHeight(height);
            } else if (icon instanceof BitmapDrawable) {
                // Ensure the bitmap has a density.
                BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
                Bitmap bitmap = bitmapDrawable.getBitmap();
                if (bitmap.getDensity() == Bitmap.DENSITY_NONE) {
                    bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics());
                }
            }
            int sourceWidth = icon.getIntrinsicWidth();
            int sourceHeight = icon.getIntrinsicHeight();

            if (sourceWidth > 0 && sourceHeight > 0) {
                // There are intrinsic sizes.
                if (width < sourceWidth || height < sourceHeight) {
                    // It's too big, scale it down.
                    final float ratio = (float) sourceWidth / sourceHeight;
                    if (sourceWidth > sourceHeight) {
                        height = (int) (width / ratio);
                    } else if (sourceHeight > sourceWidth) {
                        width = (int) (height * ratio);
                    }
                } else if (sourceWidth < width && sourceHeight < height) {
                    // Don't scale up the icon
                    width = sourceWidth;
                    height = sourceHeight;
                }
            }

            // no intrinsic size --> use default size
            int textureWidth = sIconTextureWidth;
            int textureHeight = sIconTextureHeight;

            final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
                    Bitmap.Config.ARGB_8888);
            final Canvas canvas = sCanvas;
            canvas.setBitmap(bitmap);

            final int left = (textureWidth-width) / 2;
            final int top = (textureHeight-height) / 2;

            sOldBounds.set(icon.getBounds());
            icon.setBounds(left, top, left+width, top+height);
            icon.draw(canvas);
            icon.setBounds(sOldBounds);

            return bitmap;
        }
    }
    
}


应用全部加装完毕,剩下就是在主界面中显示了,自然而然到了MainActivity:

public class MainActivity extends Activity {
	private App mSceneLauncherApplication;
	private SceneAllAppsPagedView mSceneAllApps;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.scene_allapps);
		mSceneLauncherApplication = (App) getApplication();
		mSceneLauncherApplication.setLauncher(this);
		initView();
		bindAllapps();
	}

	/**
	 * 绑定所有应用
	 */
	public void bindAllapps() {
		mSceneAllApps.setApps(mSceneLauncherApplication.getAllApps());
	}
	/**
	 * 初始化view
	 */
	private void initView() {
		mSceneAllApps = (SceneAllAppsPagedView)findViewById(R.id.scene_allapps);
	}

	/**
	 * 处理应用点击的回调
	 * @param v
	 */
	public void onAppsItemClick(View v) {
		final ApplicationInfo info = (ApplicationInfo) v.getTag();
		if (info != null) {
			mSceneLauncherApplication.startActivitySafely(info.intent);
		}
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

}


相信如果只是显示一些应用,这个很简单,很多人都可以实现,但是像系统应用,他会考虑很多细节问题,比如缓存优化、线程安全、应用图标优化、图标点击效果等等,都是我们值得学习的地方,其他的类就是一些自定义View了,有需要的童鞋可以下载源码看看:http://download.csdn.net/detail/weidi1989/5927283

 

 

 

你可能感兴趣的:(Android)