任务
我最近在做一个项目,客户要求我们的系统自带他们指定的apk,并且是可以卸载的。这没有什么难得。
还有一个要求就是要在主屏幕上有他们apk的快捷图标。
apk安装问题
首先我们要处理安装的问题,要可以卸载肯定不能放到 /system/app/ 目录下。我们可以把某个脚本作为一个服务启动。那就需要在 xxx.rc 增加服务。这样开机就会跑这个脚本。注意pm的使用。开机就跑这个脚本。那时候pm可能还没有准备好。注意检测pm的返回值。如果返回值是空的。要用while等待。
我们使用的是mtk的方案,mtk改造过android ,在系统启动时会安装 /data/app/ 目录下的apk文件。如果你在打包系统的时候就把apk文件放到这个目录,没有问题。如果你在我上面说的那个启动脚本中cp。那就要注意cp之后要 pkill system_server 这个进程被杀后会自动重启,进而安转apk。
主屏上的快捷图标
创建快捷方式,本来就是有android 标准接口的。就是发送广播 com.android.launcher.action.INSTALL_SHORTCUT ,我这里给出一个示例代码。
/**
* 为PackageName的App添加快捷方式
*
* @param context context
* @param pkg 待添加快捷方式的应用包名
* @return 返回true为正常执行完毕
*/
public static boolean addShortcutByPackageName(Context context, String pkg) {
// 快捷方式名
String title = "unknown";
// MainActivity完整名
String mainAct = null;
// 应用图标标识
int iconIdentifier = 0;
// 根据包名寻找MainActivity
PackageManager pkgMag = context.getPackageManager();
Intent queryIntent = new Intent(Intent.ACTION_MAIN, null);
queryIntent.addCategory(Intent.CATEGORY_LAUNCHER);
// 重要,添加后可以进入直接已经打开的页面
queryIntent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
queryIntent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY);
List<ResolveInfo> list = pkgMag.queryIntentActivities(queryIntent, PackageManager.GET_ACTIVITIES);
for (int i = 0; i < list.size(); i++) {
ResolveInfo info = list.get(i);
if (info.activityInfo.packageName.equals(pkg)) {
title = info.loadLabel(pkgMag).toString();
mainAct = info.activityInfo.name;
iconIdentifier = info.activityInfo.applicationInfo.icon;
break;
}
} if (mainAct == null) {
// 没有启动类
return false; }
Intent shortcut = new Intent( "com.android.launcher.action.INSTALL_SHORTCUT");
// 快捷方式的名称
shortcut.putExtra(Intent.EXTRA_SHORTCUT_NAME, title);
// 不允许重复创建
shortcut.putExtra("duplicate", false);
ComponentName comp = new ComponentName(pkg, mainAct);
shortcut.putExtra(Intent.EXTRA_SHORTCUT_INTENT, queryIntent.setComponent(comp));
// 快捷方式的图标
Context pkgContext = null;
if (context.getPackageName().equals(pkg)) {
pkgContext = context;
} else {
// 创建第三方应用的上下文环境,为的是能够根据该应用的图标标识符寻找到图标文件。
try {
pkgContext = context.createPackageContext(pkg, Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
} if (pkgContext != null) {
Intent.ShortcutIconResource iconRes = Intent.ShortcutIconResource .fromContext(pkgContext, iconIdentifier);
shortcut.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconRes);
}
// 发送广播,让接收者创建快捷方式
// 需权限<uses-permission
// android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
context.sendBroadcast(shortcut);
return true;
}
由于我们是在脚本中安装的apk,安装完成后就在脚本中发送一个广播。广播接收机放在launcher中。
脚本:
while [ ! -f /data/data/com.android.launcher3/databases/launcher.db ]; do sleep 1; done
am broadcast -a subingxi.shieke.launcher
第一行是用于确保launcher已经启动。然后发广播。我把广播接收机放在
packages/apps/Launcher3/src/com/android/launcher3/Launcher.java
IntentFilter filterShike = new IntentFilter("routon.shieke.launcher");
registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG,"subingxi to create shike laucher");
boolean pkg_installed = false;
int i = 10;
for (;i > 0;i--) {
try {
PackageInfo pi = getPackageManager().getPackageInfo(“subingxi.aaa", 0);
PackageInfo pi1 = getPackageManager().getPackageInfo("subingxi.bbb", 0);
PackageInfo pi2 = getPackageManager().getPackageInfo("subingxi.ccc", 0);
PackageInfo pi3 = getPackageManager().getPackageInfo("subingxi.ddd", 0);
if(pi != null && pi1 != null && pi2 != null && pi3 != null) {
Log.d(TAG,"subingxi to create shike laucher ok ");
pkg_installed = true;
}
} catch (NameNotFoundException ex) {
Log.d(TAG,"subingxi to create shike laucher no ");
pkg_installed = false;
}
if(pkg_installed)
break;
try {
Thread.sleep(1000);
}catch (Exception e) {
}
}
addShortcutByPackageName(Launcher.this,"subingxi.aaa");
addShortcutByPackageName(Launcher.this,"subingxi.bbb");
addShortcutByPackageName(Launcher.this,"subingxi.ccc");
addShortcutByPackageName(Launcher.this,"subingxi.ddd");
}
},filterShike);
这里先检查是否安装完成,没有安装完成就等待。
这就完了吗?你会发现创建的快捷方式不是在主屏上,而是在主屏的右一屏上(也叫首屏)。这是因为android 提供的接口(广播 com.android.launcher.action.INSTALL_SHORTCUT )就是这样的。那怎么处理一下呢。
把快捷图标放在主屏上
要想把快捷方式放到主屏上,就必须分析系统处理广播 com.android.launcher.action.INSTALL_SHORTCUT 的过程。具体分析过程较为复杂,这里只记录最终结果。基于android6.0
packages/apps/Launcher3/src/com/android/launcher3/LauncherModel.java
private static boolean findNextAvailableIconSpaceInScreen(ArrayList<ItemInfo> occupiedPos,
int[] xy, int spanX, int spanY) {
LauncherAppState app = LauncherAppState.getInstance();
InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
final int xCount = (int) profile.numColumns;
final int yCount = (int) profile.numRows;
boolean[][] occupied = new boolean[xCount][yCount];
if (occupiedPos != null) {
for (ItemInfo r : occupiedPos) {
int right = r.cellX + r.spanX;
int bottom = r.cellY + r.spanY;
for (int x = r.cellX; 0 <= x && x < right && x < xCount; x++) {
for (int y = r.cellY; 0 <= y && y < bottom && y < yCount; y++) {
occupied[x][y] = true;
}
}
}
}
return Utilities.findVacantCell(xy, spanX, spanY, xCount, yCount, occupied);
}
/**
* Find a position on the screen for the given size or adds a new screen.
* @return screenId and the coordinates for the item.
*/
@Thunk Pair<Long, int[]> findSpaceForItem(
Context context,
ArrayList<Long> workspaceScreens,
ArrayList<Long> addedWorkspaceScreensFinal,
int spanX, int spanY) {
LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>();
// Use sBgItemsIdMap as all the items are already loaded.
assertWorkspaceLoaded();
synchronized (sBgLock) {
for (ItemInfo info : sBgItemsIdMap) {
if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
ArrayList<ItemInfo> items = screenItems.get(info.screenId);
if (items == null) {
items = new ArrayList<>();
screenItems.put(info.screenId, items);
}
items.add(info);
}
}
}
// Find appropriate space for the item.
long screenId = 0;
int[] cordinates = new int[2];
boolean found = false;
int screenCount = workspaceScreens.size();
// First check the preferred screen.
// subingxi
//int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1;
int preferredScreenIndex = 0;
//subingxi end
Log.e(TAG,"subingxi_1 the preferredScreenIndex :"+preferredScreenIndex);
if (preferredScreenIndex < screenCount) {
screenId = workspaceScreens.get(preferredScreenIndex);
found = findNextAvailableIconSpaceInScreen(
screenItems.get(screenId), cordinates, spanX, spanY);
}
if (!found) {
// Search on any of the screens starting from the first screen.
for (int screen = 1; screen < screenCount; screen++) {
screenId = workspaceScreens.get(screen);
if (findNextAvailableIconSpaceInScreen(
screenItems.get(screenId), cordinates, spanX, spanY)) {
// We found a space for it
found = true;
break;
}
}
}
if (!found) {
// Still no position found. Add a new screen to the end.
screenId = LauncherAppState.getLauncherProvider().generateNewScreenId();
// Save the screen id for binding in the workspace
workspaceScreens.add(screenId);
addedWorkspaceScreensFinal.add(screenId);
// If we still can't find an empty space, then God help us all!!!
if (!findNextAvailableIconSpaceInScreen(
screenItems.get(screenId), cordinates, spanX, spanY)) {
throw new RuntimeException("Can't find space to add the item");
}
}
return Pair.create(screenId, cordinates);
}
这个函数是用于查找空缺位置用于放快捷图标,注意56行被我换成了57行。这样就会先从主屏开始查找空缺。