`

定制你自己的桌面--- launcher小觑[转]

阅读更多

定制你自己的桌面--- launcher小觑
    launcher也就是我们的Home ,可以简单地把它理解为一个简化的linux GUI。作为一个GUI它首先必须完成它最本分的功能,就是它必须能提供对所有应用 程序(CATEGORY_LAUNCHER)的映射;不过作为一个GUI,它除了做好本分之外还必须是符合大众审美的美女(wallpaper);另外还必须具有良好的交互性,没有良好的交互性就像你对一位美女殷勤了半天,她却直接对无视,那结果是比较糟糕的~~
   所谓兵马未动,粮草先行,在了解launcher的细节之前,我们首先需要完成对一些知识 的扫盲。当然这些知识我们都可以在SDK guide大叔那边找到,俺可以很负责任地告诉大家,如果你把SDK guide大叔的三板斧都学会了,APK你基本就处于无敌状态了,绝对护甲+10000,最起码基础 知识是够了,其他比的就是创意了:
       1、必须比较完整地了解APK的4个部件,尤其是Activity,现在可以简单地理解Activity是一个应用程序的窗口。
       2、必须了解UI的那部分内容,这部分内容是比较多的,English一般的我看得是比较抑郁的,但如果你想设计一个符合自己审美要求的美女或者帅哥是必须得得了解的,不需要一下能完全理解,但至少出了问题你知道去哪部分查~~
       3、Resources那部分内容可以当百科全书查
       4、intent那部分内容也是需要了解比较详细的,他是和应用沟通的渠道,大家可以参考一下小斯大虾写的文档。
       5、manifest必须了解,security可以看看
       6、Graphic部分的内容是给需要更高品味的GUI设计提供的,虽然它可能主要用在游戏上面,但我觉得如果要作出够酷的GUI肯定是需要2d,3d引擎的。
       7、AppWidget可以作为了解,用的时候再翻阅
      各位路过的大虾们肯定被这么多的粮草给直接雷倒了,其实需要我们详细掌握的是1和2,其他的都可以当作百科全书,但是如果能仔细地看透了那是最好了。

    好,万事俱备只欠东风了,我们首先来看看这个Home是在啥时候由谁来启动的。这部分知识可以跳过,但是理解一下是好的,你可以了解一个APK进程是如何怀胎十月之后诞生的。可能下面说到的词汇有些生涩,所以建议先看看Android Anatomy and Physiology.pdf。
       Linux kernel启动以后会通过App_main进程来初始化android Runtime Java 运行环境,而zygote是android的第一个进程。所有的android的应用以及大部分系统 服务都是通过zygote fork出来的子进程(我现在看到的只有native的service manager不是由zygote fork出来的)。在system server中启动的若干系统服务中与我们启动进程相关的就是Acitivity Manager。
      当systerm server启动好所有服务以后,系统就进入”system ready”状态,这个时候Activity Manager就登场了。Activity Manager光看代码行就知道是一个重量级的服务,它主要管理Activity之间的跳转,以及进程的生命周期。当Activity Manager发现系统已经启动好以后它就会发出一个intent:
     Intent intent = new Intent(
                mTopAction,
                mTopData != null ? Uri.parse(mTopData) : null);
               intent.setComponent(mTopComponent);
               if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
                 intent.addCategory(Intent.CATEGORY_HOME);
             }
通过这个category类型为home的intent,Activity Manager就会通过:
     startActivityLocked(null, intent, null, null, 0, aInfo,
                            null, null, 0, 0, 0, false, false);
启动Home进程了。而这个启动Home进程的过程实际上还是去通过zygote fork出的一个子进程。因此只要在manifest中具备这样的intent-filter都可以在开机的时候作为Home启动:
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.HOME"/>
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
多个home之间的switch会在开始的时候有个选择,至于这个选择好像是package manager来实现的,没有仔细研究过。

   好啦,了解了Lancher 是如何执行的,我们再来看看整个lancher内部构造。看看一个lancher如何构造才算是个长得对得起观众的娃:
   1、取得系统中所有安装好的应用程序,并提供能运行这些程序的映射(形象的理解就是一个一个应用程序的小图标)。这是Lancher的骨架,正所谓何谓lancher是吧~~如果它不能提供应用程序的访问,再好看也至多是一个华丽的花瓶而已,啥用米有。
   2、更好一点我们就需要为这个设计良好的骨架提供一些画皮以及一系列动画效果,就是我们的wallpaper以及一系列的图像,animation,graphic之类的。如果完成这部分工作,基本上我们的Home就基本成型了。
   3、要使得我们的GUI更具亲和性更方便使用,我们还提供一些额外的功能,比如说现在lancher实现的图标的拖动,快捷方式等等。这些都是仁者见仁智者见智的事情,取决你天马行空的设计了。

   总结起来一个lancher包含3个部分内容:应用程序信息采集,事件处理,动画。下面我们来讲述一个自己的launcher的实现过程:
   1、设计
   从纯用户的角度来设计你的界面,你希望达到什么样的效果,尽量写得详细。尤其是应用程序信息以如何方式的出现,以及对它的操作一般都是一个好设计的亮点。 我们现在设计一个简单的,我们需要一个墙纸,然后在这个墙纸上面有一个条形的控件来显示我们的应用程序图标。选择这些图标以后会在屏幕中间出现一张图表示 这个应用程序的功能,然后单击这个图就会打开这个应用程序。
   2、设计的总体实现
   针对自己的设想来设计这个lancher的整体实现,如果有无法实现的内容就要及时修改设计,或者换一种设计方案 。 我们这里使用一个FrameLayout来作为我们的Lancher的容器。然后分层,最下面一层用来放置可能需要的快捷方式以及我们的 wallpaper,然后在wallpaper层上放一个我们自己定义的component来显示我们的应用程序信息。个人觉得FrameLayout是 比较适合作为lancher的layout的,它类似于photoshop的图层这样的控制,上面的图层会覆盖下面的图层。
   3、具体功能的具体实现
   这里具体到代码上就是设计各种java功能类了。对于wallpaper和图标的拖拽移动这里简单提一下,更多的可以去看Android Lancher的实现。wallpaper一般是注册一个broadcastreceiver来处理系统中所有的更改背景图片的请求,而图标的脱拽移动则 涉及到Draglayer这个类。
   我们来把重点放在如何取得Android已安装的应用程序信息上。这里就涉及到我们另外一个重要的service了,它就是package manager,它负责对安装的包进行管理 。这里涉及到一些权限,我是直接照着android lancher的实现把它的权限拷贝过来的:
    <uses-permission android:name="android.permission.CALL_PHONE" />
    <uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />
    <uses-permission android:name="android.permission.GET_TASKS" />
    <uses-permission android:name="android.permission.READ_CONTACTS"/>
    <uses-permission android:name="android.permission.SET_WALLPAPER" />
    <uses-permission android:name="android.permission.SET_WALLPAPER_HINTS" />
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
   下面来看看具体的实现,我们创建一个自己的控件,使用LinearLayout来装载ImageSwitcher和Gallery两个控件,用 Gallery来显示获得的应用程序信息,用ImageSwitcher来显示应用程序的介绍,单击ImageSwitcher就能打开相应的应用程序。
   public class MyLancherSwitcher extends LinearLayout implements ViewSwitcher.ViewFactory, AdapterView.OnItemSelectedListener,AdapterView.OnItemClickListener{
…………
        mImageSwitcher = new ImageSwitcher(context) ;
        mGallery = new Gallery(context) ;
       
        this.addView(mImageSwitcher, new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,400)) ;
        this.addView(mGallery, new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, 80)) ;

…………

   架构选好了,下面就是如何为这个两个控件提供已安装的应用程序的信息,首先我们取得package manager :
   PackageManager manager = this.getContext().getPackageManager();

   然后package manager通过intent信息来提供相应的应用程序信息:
   Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
   mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
  
   final List<ResolveInfo> apps = manager.queryIntentActivities(mainIntent, 0);
   Collections.sort(apps, new ResolveInfo.DisplayNameComparator(manager));

   然后我们定义个自己的类MyAppInfo来存储这些取得的信息:
    for (int i = 0; i < count; i++) {
                MyAppInfo application = new MyAppInfo();
                ResolveInfo info = apps.get(i);

                application.title = info.loadLabel(manager);
                application.setActivity(new ComponentName(
                        info.activityInfo.applicationInfo.packageName,
                        info.activityInfo.name),
                        Intent.FLAG_ACTIVITY_NEW_TASK
                        | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
                application.icon = info.activityInfo.loadIcon(manager);

                mApplications.add(application);
            }

    final void setActivity(ComponentName className, int launchFlags) {
        intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_LAUNCHER);
        intent.setComponent(className);
        intent.setFlags(launchFlags);
    }
   
    我们使用一个数组来存储这些MyAppInfo信息,并把它提供给Gallery:
    private static ArrayList<MyAppInfo> mApplications;
    mGallery.setAdapter(new ApplicationsAdapter(this.getContext(), mApplications)) ;
   
   最后重载ArrayAdapter<MyAppInfo>的getView()函数对画图进行一些裁减就OK了,Gallery就能显示我们 的应用程序的图片信息了。最后我们把Gallery中被选中的图片的应用程序信息传给ImageSwitcher,并为ImageSwithcher注册 一个按键事件,就可以启动应用程序了:
    private OnClickListener mImageSwitcherListener = new OnClickListener(){
        public void onClick(View v){
            if(mAppInfo == null){}
            else
            v.getContext().startActivity(mAppInfo.intent);
        }
    } ;
   
    这样基本我们lancher的骨架就搞定了,不过还有一个,那就是当我们新安装或删除一个应用程序的时候,我们的Home必须捕获这个intent,并及 时调整home里面的应用程序信息,因此我要为我们的控件注册一个package的broadcast receiver :
    private class ApplicationsIntentReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            loadApplications(false);
        }
    }
  
     private void registerIntentReceivers() {
        filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
        filter.addDataScheme("package");
        registerReceiver(mApplicationsReceiver, filter);
    }
   
   Ok这样我们的lancher就基本完成了,剩下的就是为各个事件添加你需要的动画效果,这里就不说了。以前没有经历过java编程,但是个人觉得 android java应用的编程还是相对简单的,只是因为东西很多所以显得有点复杂,但是基本上使用起来还是很方便的,基本就是继承之后重载或者实现接口,而且 Android为Ui的编程提供了一个更方便的方式就是使用XML,使用xml可以更直观地来进行你的设计,而且也方便了你以后的修改和移植。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics