相信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;@Overridepublic void onCreate() {super.onCreate();registerRecever();// 註冊應用添加、刪除等廣播getAllApps();}/** * 註冊應用添加、刪除等廣播 */private void registerRecever() {// register receviermLauncherReceiver = 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();elsemApps = 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;}@Overridepublic 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() {@Overridepublic void run() {for (int i = 0; i < N; i++) {Log.d(TAG,"PackageUpdatedTask add packageName = "+ packages[i]);addPackage(packages[i]);}sHandler.post(new Runnable() {@Overridepublic void run() {MainActivity launcher = mLauncher.get();if (launcher != null) {launcher.bindAllapps();//回調主介面更新}}});}});}break;case OP_REMOVED:if (N > 0) {sHandler.post(new Runnable() {@Overridepublic void run() {for (int i = 0; i < N; i++) {Log.d(TAG,"PackageUpdatedTask remove packageName = "+ packages[i]);removePackage(packages[i]);}sHandler.post(new Runnable() {@Overridepublic 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 {@Overridepublic 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;//應用的Intentpublic ComponentName componentName;//應用程式套件名 intindex; 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;@Overrideprotected 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);}}@Overridepublic 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