美文网首页Android开发Android技术知识Android开发经验谈
android framework之旅(五)开发一个简单桌面

android framework之旅(五)开发一个简单桌面

作者: 风少侠 | 来源:发表于2018-04-24 14:12 被阅读262次

    环境

    开发工具: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>
    

    相关文章

      网友评论

        本文标题:android framework之旅(五)开发一个简单桌面

        本文链接:https://www.haomeiwen.com/subject/vfyllftx.html