环境
开发工具:Android Studio
开发板:NanoPC T3
源码版本:5.1.1
目标
- 开机启动自己的桌面
- 设置自己的桌面背景
- 桌面上只放置自己需要的应用图标
- 点击应用图标启动相应应用
- 监听应用的安装和卸载广播,更新桌面
放一张效果图(原谅渣像素/(ㄒoㄒ)/~~):
效果.jpg
实现方式
我使用的开发板启动桌面为Launcher2,所以以下内容都是参考的Launcher2的源码。
开机启动自己的桌面
要开机启动自己的桌面,首先要了解系统桌面是怎么被启动的,详情可以参考这篇博客,android系统init进程启动后会启动Native服务和系统服务,ActivityManagerServic中启动Home:
public void systemReady(final Runnable goingCallback) {
...
startHomeActivityLocked(mCurrentUserId, "systemReady");
...
}
追踪一下该方法,最后是启动的这样一个Intent:
Intent getHomeIntent() {
Intent intent = new Intent(mTopAction, mTopData != null ? Uri.parse(mTopData) : null);
intent.setComponent(mTopComponent);
if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
intent.addCategory(Intent.CATEGORY_HOME);//划重点,圈住,要考的
}
return intent;
}
so,我们只要让自己的IntentFilter匹配上面这个Intent就可以响应系统启动桌面的这个意图,看一下Launcher2中的manifest:
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.launcher">
<original-package android:name="com.android.launcher2" />
//...权限相关
<application
android:name="com.android.launcher2.LauncherApplication"
android:label="@string/application_name"
android:icon="@mipmap/ic_launcher_home"
android:hardwareAccelerated="true"
android:largeHeap="@bool/config_largeHeap"
android:supportsRtl="true">
<activity
android:name="com.android.launcher2.Launcher"
android:launchMode="singleTask"
android:clearTaskOnLaunch="true"
android:stateNotNeeded="true"
android:resumeWhilePausing="true"
android:theme="@style/Theme"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="nosensor">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.MONKEY"/>
</intent-filter>
</activity>
<activity
android:name="com.android.launcher2.WallpaperChooser"
android:theme="@style/Theme.WallpaperPicker"
android:label="@string/pick_wallpaper"
android:icon="@mipmap/ic_launcher_wallpaper"
android:finishOnCloseSystemDialogs="true"
android:process=":wallpaper_chooser">
<intent-filter>
<action android:name="android.intent.action.SET_WALLPAPER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="android.wallpaper.preview"
android:resource="@xml/wallpaper_picker_preview" />
</activity>
//...安装卸载应用和快捷方式的相关广播接收器
//settings的内容提供者,保存桌面数据
<meta-data android:name="android.nfc.disable_beam_default"
android:value="true" />
</application>
</manifest>
可以看到Launcher2相对来说还不是很复杂,只有两个Activity和一系列Package相关的广播接收器,其中Launcher就是我们的桌面,我们可以将自己的应用按照上面这样配置下,然后删除系统apk,并将我们的apk放到系统对应目录,重启开发板即可。
设置自己的桌面背景
这个我这里选了个偷懒的方式,直接设置了Activity的背景,因为我们这个产品是不需要用户自己更换主题这种操作的。
桌面上只放置自己需要的应用图标
首先还是继续看下系统是怎么获取到应用列表的,我们从Launcher的onCreate方法看起:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//...初始化一系列对象,LauncherModel、图标缓存、拖拽管理类、widget管理类、inflater等等
//初始化布局控件
//是否第一次启动,进而决定是否显示引导功能
//通过LauncherModel加载相关数据
if (!mRestoring) {
if (sPausedFromUserAction) {
mModel.startLoader(true, -1);
} else {
mModel.startLoader(true, mWorkspace.getCurrentPage());
}
}
...
}
继续追踪mModel.startLoader(),发现就是启动了一个异步任务,我们来看下该异步任务的run方法:
public void run() {
...
// First step. Load workspace first, this is necessary since adding of apps from
// managed profile in all apps is deferred until onResume. See http://b/17336902.
if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
loadAndBindWorkspace();
...
// Second step. Load all apps.
if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
loadAndBindAllApps();
...
}
该任务主要进行了两个操作,加载workspace和加载所有app,接下来继续追踪加载app的方法,发现内部调用了loadAllAppsByBatch()方法,最后调用到LauncherAppsService的getLauncherActivities()方法:
@Override
public List<ResolveInfo> getLauncherActivities(String packageName, UserHandle user)
throws RemoteException {
ensureInUserProfiles(user, "Cannot retrieve activities for unrelated profile " + user);
if (!isUserEnabled(user)) {
return new ArrayList<ResolveInfo>();
}
final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
mainIntent.setPackage(packageName);
long ident = Binder.clearCallingIdentity();
try {
List<ResolveInfo> apps = mPm.queryIntentActivitiesAsUser(mainIntent, 0 /* flags */,
user.getIdentifier());
return apps;
} finally {
Binder.restoreCallingIdentity(ident);
}
}
这部分就是我们需要的加载所有应用的代码,然后根据包名过滤掉我们不需要的app,将得到的数据用RecyclerView展示,就可以只显示我们自己需要的应用:
private static List<ResolveInfo> getMyApps(PackageManager pm){
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
List<ResolveInfo> myApps = pm.queryIntentActivities(intent,0);
List<ResolveInfo> temp = new ArrayList<>();
for (ResolveInfo info : myApps) {
Log.d("lxf Launcher2", info.activityInfo.packageName);
String packName = info.activityInfo.packageName;
if (packName.equals("com.android.settings")
|| packName.equals("com.android.browser")
|| packName.equals("com.android.providers.downloads.ui")
|| packName.equals("com.estrongs.android.pop")
|| packName.contains("cn.izis")) {
temp.add(info);
}
}
myApps.clear();
myApps.addAll(temp);
return myApps;
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
ResolveInfo info = mApps.get(position);
holder.icon.setImageDrawable(info.loadIcon(pm));
holder.name.setText(info.loadLabel(pm));
holder.itemView.setTag(position);
}
取消RecyclerView滑动边界的阴影可以这么设置:
android:overScrollMode="never"
点击应用图标启动相应应用
这个就是根据包名去启动对应的应用,有android功底的应该都懂,直接上代码:
ComponentName componentName = new ComponentName(resolveInfo.activityInfo.packageName,resolveInfo.activityInfo.name);
Intent intent = new Intent();
intent.setComponent(componentName);
startActivity(intent);
监听应用的安装和卸载广播,更新桌面
这里我采用了一种简单粗暴的方式,收到应用安装或卸载的广播时,直接重新获取所有的app列表:
public static class MyReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null || intent.getAction() == null)
return;
if (intent.getAction().equals(Intent.ACTION_PACKAGE_ADDED)
||intent.getAction().equals(Intent.ACTION_PACKAGE_REMOVED)){
myApps.clear();
myApps.addAll(getMyApps(context.getPackageManager()));
System.out.println("lxf" + myApps.size());
appsAdapter.notifyDataSetChanged();
}
}
}
在manifest中注册广播:
<receiver android:name=".MainActivity$MyReceiver">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED"/>
<action android:name="android.intent.action.PACKAGE_REMOVED"/>
<data android:scheme="package"/>
</intent-filter>
</receiver>
网友评论