相信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 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