相信Android系统经典Launcher大家都见过是什么样子。如下图所示,是4.0比较原始的Launcher主菜单功能,今天我们要学习的就是这一块,通过这个小代码,我们可以复习的知识点有:
①. 应用的获取与处理,包括SD中的应用。
②. 动态监听用户应用安装、卸载以及语言系统的切换,比如中文切换到英文状态。
③. 仿ViewPager和PagerIndicater自定义View的实现,注意是仿哦,不是同一个。
④...
我们都知道,如果直接将系统的源码弄出来,直接导入eclipse是会报错的,下面我们看看从源码中移植出来的效果图,仅是这个模块而已!
下面让我们来看看源码结构,由于仅仅是一个简单的例子,所以细节未过多考虑,敬请谅解:
这个App是整个程序的入口,继承Application,在这里面,以静态变量的形式缓存了所有的应用,还有注册了应用添加、删除、变化等广播,以及相应处理并通知MainActivity,下面,就让我们来看看这个最重要的类,几乎最重要的知识点都在这里面了:
public class App extends Application { public static String TAG = "way"; private BroadcastReceiver mLauncherReceiver; static ArrayList<ApplicationInfo> 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<MainActivity> 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<ApplicationInfo> getAllApps() { if (mApps == null) { mApps = new ArrayList<ApplicationInfo>(); fillAllapps(); } return mApps; } /** * 弱引用管理主界面 * * @param launcher */ public void setLauncher(MainActivity launcher) { this.mLauncher = new WeakReference<MainActivity>(launcher); } /** * 搜索所有的app */ private void fillAllapps() { final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); final PackageManager packageManager = getPackageManager(); List<ResolveInfo> apps = packageManager.queryIntentActivities( mainIntent, 0); if (mApps != null) mApps.clear(); else mApps = new ArrayList<ApplicationInfo>(); 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<ResolveInfo> 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<ResolveInfo> apps = packageManager.queryIntentActivities( mainIntent, 0); return apps != null ? apps : new ArrayList<ResolveInfo>(); } /** * 添加指定包名的应用,当监听到用户安装新应用的时候调用 * * @param packageName */ private void addPackage(String packageName) { final List<ResolveInfo> 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<ApplicationInfo> 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<ApplicationInfo> APPLICATION_CUST_SORT = new Comparator<ApplicationInfo>() { public final int compare(ApplicationInfo a, ApplicationInfo b) { return a.index - b.index; } }; /** * 根据应用名排序的Comparator */ static final Comparator<ApplicationInfo> APP_NAME_COMPARATOR = new Comparator<ApplicationInfo>() { 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