`

Android Launcher全面剖析

 
阅读更多

Android Launcher全面剖析

首先来说说我为什么写这篇文章,最近公司要我负责搞Launcher,网上一查这方面的资料比较少,并且不全,研究起来相当困难,所以就写了这篇文章,希望对大家有帮助。这篇文章是相当长的,希望读者能耐心读下去,实际上也花了我很长时间来写。好了闲话少说,我们切入正题。

这篇文章我会讲以下Launcher内容:

Launcher UI总体架构

Launcher Res下的Layout

Launcher Res下的Xml文件

Launcher Manifest文件

Launcher 常用类介绍

Launcher 启动过程

Launcher widget添加过程

Launcher celllayout的介绍

一 Launcher UI总体架构

Home screen可以说是一个手机的最重要应用,就像一个门户网站的首页,直接决定了用户的第一印象。下面对home screen做一简要分析。

home screen的代码位于packages/apps/Launcher目录。从文件launcher.xml,workspace_screen.xml可获知home screen的UI结构如下图所示:

整个homescreen是一个包含三个child view的FrameLayout(com.android.launcher.DragLayer)。

第一个child就是桌面com.android.launcher.Workspace。这个桌面又包含三个child。每个child就对应一个桌面。这就是你在Android上看到的三个桌面。每个桌面上可以放置下列对象:应用快捷方式,appwidget和folder。

第二个child是一个SlidingDrawer控件,这个控件由两个子控件组成。一个是com.android.launcher.HandleView,就是Android桌面下方的把手,当点击这个把手时,另一个子控件,com.android.launcher.AllAppsGridView就会弹出,这个子控件列出系统中当前安装的所有类型为category.launcher的Activity。

第三个child是com.android.launcher.DeleteZone。当用户在桌面上长按一个widget时,把手位置就会出现一个垃圾桶形状的控件,就是这个控件。

在虚拟桌面上可以摆放四种类型的对象:
1. ITEM_SHORTCUT,应用快捷方式
2. ITEM_APPWIDGET,app widget
3. ITEM_LIVE_FOLDER,文件夹
4. ITEM_WALLPAPER,墙纸。

类AddAdapter(AddAdapter.java)列出了这四个类型对象。当用户在桌面空白处长按时,下列函数序列被执行:
Launcher::onLongClick -->
Launcher::showAddDialog -->
Launcher::showDialog(DIALOG_CREATE_SHORTCUT); -->
Launcher::onCreateDialog -->
Launcher::CreateShortcut::createDialog:这个函数创建一个弹出式对话框,询问用户是要添加什么(快捷方式,appwidget, 文件夹和墙纸)其内容就来自AddAdapter。

类Favorites(LauncherSettings.java)和类LauncherProvider定义了一个content provider,用来存储桌面上可以放置的几个对象,包括shortcut, search和clock等。

类DesktopItemsLoader负责将桌面上所有的对象从content provider中提取。

线程private ApplicationsLoader mApplicationsLoader负责从包管理器中获取系统中安装的应用列表。(之后显示在AllAppsGridView上)。ApplicationsLoader::run实现:
1)通过包管理器列出系统中所有类型为Launcher,action为MAIN的activity;
2)对每一个Activity,
a) 将Activity相关元数据信息,如title, icon, intent等缓存到appInfoCache;
b) 填充到ApplicationsAdapter 中。填充过程中用到了一些小技巧,每填充4(UI_NOTIFICATION_RATE)个activity更新一下相应view。

在Launcher::onCreate中,函数startLoaders被调用。而该函数接着调用loadApplications和loadUserItems,分别获取系统的应用列表,以及显示在桌面上的对象列表(快捷方式,appwidget,folder等)。

Launcher上排列的所有应用图标由AllAppsGridView对象呈现。这个对象是一个GridView。其对应的Adapter是ApplicationsAdapter,对应的model则是ApplicationInfo数组。数组内容是由ApplicationsLoader装载的。
private class ApplicationsLoader implements Runnable。

Launcher中的AndroidManifest.xml可以看出整个Launcher的代码结构。

首先,是一些权限的声明。例如:

  1. <uses-permissionandroid:name="android.permission.CALL_PHONE"/>
  2. <uses-permissionandroid:name="android.permission.EXPAND_STATUS_BAR"/>

这部分可以略过;

其次,Application的构成,如上图:

(1)LauncherHomeScreenActivity

  1. <intent-filter>
  2. <actionandroid:name="android.intent.action.MAIN"/>
  3. <categoryandroid:name="android.intent.category.HOME"/>
  4. <categoryandroid:name="android.intent.category.DEFAULT"/>
  5. <categoryandroid:name="android.intent.category.MONKEY"/></intent-filter>


上面这段代码就标志着它是开机启动后HomeActivity。通过Launcher.javaonCreat()的分析我们可以大致把握屏幕的主要活动:

  1. protectedvoidonCreate(BundlesavedInstanceState){
  2. super.onCreate(savedInstanceState);
  3. //把xml文件的内容实例化到View中
  4. mInflater=getLayoutInflater();
  5. //监听应用程序控件改变事件
  6. mAppWidgetManager=AppWidgetManager.getInstance(this);
  7. mAppWidgetHost=newLauncherAppWidgetHost(this,APPWIDGET_HOST_ID);
  8. mAppWidgetHost.startListening();
  9. //用于调试?
  10. if(PROFILE_STARTUP){
  11. android.os.Debug.startMethodTracing("/sdcard/launcher");
  12. }
  13. //监听locale,mcc,mnc是否改变,如果改变,则重写新配置
  14. //mcc:mobilecountrycode(国家代码China460);mnc:mobilenetworkcode(网络代码)
  15. checkForLocaleChange();
  16. /*Thisallowssuchapplicationstohaveavirtualwallpaperthatislargerthanthephysicalscreen,matchingthesizeoftheirworkspace.*/
  17. setWallpaperDimension();
  18. //显示主屏幕UI元素,workspace,slidingdrawer(handleviewandappgridview),deletezone
  19. setContentView(R.layout.launcher);
  20. //Findsalltheviewsweneedandconfigurethemproperly.
  21. //完成workspace,slidingdrawer,deletezone的各种事件操作和监听
  22. setupViews();
  23. //Registersvariousintentreceivers.
  24. //允许其他应用对本应用的操作
  25. registerIntentReceivers();
  26. //Registersvariouscontentobservers.
  27. //例如,注册一个内容观察者跟踪喜爱的应用程序
  28. registerContentObservers();
  29. //重新保存前一个状态(目的??)
  30. mSavedState=savedInstanceState;
  31. restoreState(mSavedState);
  32. //调试?
  33. if(PROFILE_STARTUP){
  34. android.os.Debug.stopMethodTracing();
  35. }
  36. //LoadsthelistofinstalledapplicationsinmApplications.
  37. if(!mRestoring){
  38. startLoaders();
  39. }
  40. //Forhandlingdefaultkeys??
  41. mDefaultKeySsb=newSpannableStringBuilder();
  42. Selection.setSelection(mDefaultKeySsb,0);
  43. }

方法onActivityResult():完成在workspace上增加shortcutappwidgeLivefolder

方法onSaveInstantceState()onRestoreInstanceState():为了防止SensorLandPort布局自动切换时数据被置空,通过onSaveInstanceState方法可以保存当前窗口的状态,在即将布局切换前将当前的Activity压入历史堆栈。如果我们的Activity在后台没有因为运行内存吃紧被清理,则切换时回触发onRestoreIntanceState()


(2)WallpaperChooser:设置墙纸。

同理我们从onCreat()作为入口来分析这个活动的主要功能。

  1. publicvoidonCreate(Bundleicicle){
  2. super.onCreate(icicle);
  3. //设置允许改变的窗口状态,需在setContentView之前调用
  4. requestWindowFeature(Window.FEATURE_NO_TITLE);
  5. //添加墙纸资源,将资源标识符加入到动态数组中
  6. findWallpapers();
  7. //显示墙纸设置屏幕的UI元素,Imageview,GalleryandButton(LinearLayout)
  8. setContentView(R.layout.wallpaper_chooser);
  9. //图片查看功能的实现
  10. mGallery=(Gallery)findViewById(R.id.gallery);
  11. mGallery.setAdapter(newImageAdapter(this));
  12. mGallery.setOnItemSelectedListener(this);
  13. mGallery.setCallbackDuringFling(false);
  14. //Button事件监听,点击选择setWallpaper(Resid)
  15. findViewById(R.id.set).setOnClickListener(this);
  16. mImageView=(ImageView)findViewById(R.id.wallpaper);
  17. }

(3)default_searchable

对于home中任意的Acitivty,使能系统缺省Search模式,这样就可以使用android系统默认的searchUI


(4)InstallShortcutReceiver

继承自BroadcastReceiver,重写onReceier()方法,对于发送来的Broadcast(这里指Intent)进行过滤(IntentFilt)并且响应(这里是InstallShortcut())。这里分析下onReceive():

  1. <!--Enablesystem-defaultsearchmodeforanyactivityinHome-->
  2. <!--Intentreceivedusedtoinstallshortcutsfromotherapplications-->
  3. publicvoidonReceive(Contextcontext,Intentdata){
  4. //接受并过滤Intent
  5. if(!ACTION_INSTALL_SHORTCUT.equals(data.getAction())){
  6. return;
  7. }
  8. //获取屏幕
  9. intscreen=Launcher.getScreen();
  10. //安装快捷方式
  11. if(!installShortcut(context,data,screen)){
  12. //如果屏幕已满,搜寻其他屏幕
  13. for(inti=0;i<Launcher.SCREEN_COUNT;i++){
  14. if(i!=screen&&installShortcut(context,data,i))break;
  15. }
  16. }
  17. }

其中IntallShortcut()方法:首先,对传入的坐标进行判断(findEmptyCell(),如果是空白位置,则可以放置快捷方式;其次,缺省情况下,我们允许创建重复的快捷方式,具体创建过程(addShortcut())就是把快捷方式的信息传入数据库(addItemToDatabase())。


(5)UninstallShortcutReceiver

同理,UninstallShortcutReceiver()继承自BroadcastReceiver(),实现onReceiver()方法。定义一个ContentResolver对象完成对数据库的访问和操作(通过URI定位),进而删除快捷方式 。


(6)LauncherProvider

继承自ContentProvider(),主要是建立一个数据库来存放HomeScreen中的数据信息,并通过内容提供者来实现其他应用对launcher中数据的访问和操作。

重写了ContentProvider()中的方法

getType():返回数据类型。如果有自定义的全新类型,通过此方法完成数据的访问。

query():查询数据。传入URI,返回一个Cursor对象,通过Cursor完成对数据库数据的遍历访问。

Insert():插入一条数据。

bulkInsert():大容量数据的插入。

delete():删除一条数据。

update():更改一条数据。

sendNotify():发送通知。

DatabaseHelper继承自一个封装类SQLiteOpenHelper(),方便了数据库的管理和维护。

重写的方法:

onCreate():创建一个表。其中db.execSQL()方法执行一条SQL语句,通过一条字符串执行相关的操作。当然,对SQL基本语句应该了解。

onUpgrade():升级数据库。

HomeScreen数据库操作的一些方法:

addClockWidget(),addSearchWidget,addShortcut,addAppShortcut,

loadFavorites(),launcherAppWidgetBinder(),convertWidget(),updateContactsShortcuts(),

copyFromCursor()

补充:

AddAdapter(AddAdapter.java)列出了这四个类型对象。当用户在桌面空白处长按时,下列函数序列被执行:

Launcher::onLongClick-->

Launcher::showAddDialog-->

Launcher::showDialog(DIALOG_CREATE_SHORTCUT);-->

Launcher::onCreateDialog-->

Launcher::CreateShortcut::createDialog:这个函数创建一个弹出式对话框,询问用户是要添加什么(快捷方式,appwidget,文件夹和墙纸)其内容就来自AddAdapter。

DesktopItemsLoader负责将桌面上所有的对象从contentprovider中提取。

线程privateApplicationsLoadermApplicationsLoader负责从包管理器中获取系统中安装的应用列表。(之后显示在AllAppsGridView上)。ApplicationsLoader::run实现:
1)通过包管理器列出系统中所有类型为Launcher,action为MAIN的activity;
2)对每一个Activity,
a)将Activity相关元数据信息,如title,icon,intent等缓存到appInfoCache;
b)填充到ApplicationsAdapter中。填充过程中用到了一些小技巧,每填充4(UI_NOTIFICATION_RATE)个activity更新一下相应view。

在Launcher::onCreate中,函数startLoaders被调用。而该函数接着调用loadApplications和loadUserItems,分别获取系统的应用列表,以及显示在桌面上的对象列表(快捷方式,appwidget,folder等)。
Launcher上排列的所有应用图标由AllAppsGridView对象呈现。这个对象是一个GridView。其对应的Adapter是ApplicationsAdapter,对应的model则是ApplicationInfo数组。数组内容是由ApplicationsLoader装载的。

二 Launcher Res下的Layout

现在我们来看res目录里的布局文件,布局文件都放在layout*目录里。
本以为launcher的layout都放在layout目录里,由于屏幕放置方式的不同会对桌面造成一定的影响,所以googleAndroid项目组就决定因地制宜。比如当你横着放置屏幕的时候就会使用layout-land目录里的文件来对系统launcher进行布局,竖着屏幕的时候会使用layout-port内的布局文件来对launcher来布局。
横竖屏幕切换之际,会重新进行布局。那我们就以layout-land目录为例来看吧。
layout-land/launcuer.xml

复制到剪贴板XML/HTML代码
  1. <?xmlversion="1.0"encoding="utf-8"?>
  2. <!--
  3. /*
  4. **
  5. **Copyright2008,TheAndroidOpenSourceProject
  6. **
  7. **LicensedundertheApacheLicense,Version2.0(the"License");
  8. **youmaynotusethisfileexceptincompliancewiththeLicense.
  9. **YoumayobtainacopyoftheLicenseat
  10. **
  11. **http://www.apache.org/licenses/LICENSE-2.0
  12. **
  13. **Unlessrequiredbyapplicablelaworagreedtoinwriting,software
  14. **distributedundertheLicenseisdistributedonan"ASIS"BASIS,
  15. **WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied.
  16. **SeetheLicenseforthespecificlanguagegoverningpermissionsand
  17. **limitationsundertheLicense.
  18. */
  19. -->
  20. <manifest
  21. xmlns:android="http://schemas.android.com/apk/res/android"
  22. package="com.android.zkx_launcher"
  23. android:sharedUserId="@string/sharedUserId"
  24. >
  25. <!--应用程序的包名-->
  26. <!--<original-packageandroid:name="com.android.zkx_launcher2"/>-->
  27. <!--对系统资源的访问权限-->
  28. <permission
  29. android:name="com.android.zkx_launcher.permission.INSTALL_SHORTCUT"
  30. android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
  31. android:protectionLevel="normal"
  32. android:label="@string/permlab_install_shortcut"
  33. android:description="@string/permdesc_install_shortcut"/>
  34. <permission
  35. android:name="com.android.zkx_launcher.permission.UNINSTALL_SHORTCUT"
  36. android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
  37. android:protectionLevel="normal"
  38. android:label="@string/permlab_uninstall_shortcut"
  39. android:description="@string/permdesc_uninstall_shortcut"/>
  40. <permission
  41. android:name="com.android.zkx_launcher.permission.READ_SETTINGS"
  42. android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
  43. android:protectionLevel="normal"
  44. android:label="@string/permlab_read_settings"
  45. android:description="@string/permdesc_read_settings"/>
  46. <permission
  47. android:name="com.android.zkx_launcher.permission.WRITE_SETTINGS"
  48. android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
  49. android:protectionLevel="normal"
  50. android:label="@string/permlab_write_settings"
  51. android:description="@string/permdesc_write_settings"/>
  52. <uses-permissionandroid:name="android.permission.CALL_PHONE"/>
  53. <uses-permissionandroid:name="android.permission.EXPAND_STATUS_BAR"/>
  54. <uses-permissionandroid:name="android.permission.GET_TASKS"/>
  55. <uses-permissionandroid:name="android.permission.READ_CONTACTS"/>
  56. <uses-permissionandroid:name="android.permission.SET_WALLPAPER"/>
  57. <uses-permissionandroid:name="android.permission.SET_WALLPAPER_HINTS"/>
  58. <uses-permissionandroid:name="android.permission.VIBRATE"/>
  59. <uses-permissionandroid:name="android.permission.WRITE_SETTINGS"/>
  60. <uses-permissionandroid:name="android.permission.BIND_APPWIDGET"/>
  61. <uses-permissionandroid:name="com.android.launcher.permission.READ_SETTINGS"/>
  62. <uses-permissionandroid:name="com.android.launcher.permission.WRITE_SETTINGS"/>
  63. <!--对应用程序的配置-->
  64. <application
  65. android:name="LauncherApplication"
  66. android:process="@string/process"
  67. android:label="@string/application_name"
  68. android:icon="@drawable/ic_launcher_home">
  69. <!--配置应用程序额的名字,进程,标签,和图标
  70. label的值为values/strings.xml中application_name键值对的值
  71. icon为drawable目录下名为的ic_launcher_home的图片
  72. 实际上该图片的位置位于drawable-hdpi(高分辨率)目录下,是个小房子这个主要是为了支持多分辨率的.
  73. hdpi里面主要放高分辨率的图片,
  74. 如WVGA(480x800),FWVGA(480x854)mdpi里面主要放中等分辨率的图片,
  75. 如HVGA(320x480)ldpi里面主要放低分辨率的图片,
  76. 如QVGA(240x320)系统会根据机器的分辨率来分别到这几个文件夹里面去找对应的图片
  77. 所以在开发程序时为了兼容不同平台不同屏幕,建议各自文件夹根据需求均存放不同版本图片.
  78. 只需要在res目录下创建不同的layout文件夹,
  79. 比如layout-640x360,layout-800x480,
  80. 所有的layout文件在编译之后都会写入R.java里,
  81. 而系统会根据屏幕的大小自己选择合适的layout进行使用-->
  82. <!--设置intent-filter可以先启动该Activity-->
  83. <activity
  84. android:name="Launcher"
  85. android:launchMode="singleTask"
  86. android:clearTaskOnLaunch="true"
  87. android:stateNotNeeded="true"
  88. android:theme="@style/Theme"
  89. android:screenOrientation="nosensor"
  90. android:windowSoftInputMode="stateUnspecified|adjustPan">
  91. <intent-filter>
  92. <actionandroid:name="android.intent.action.MAIN"/>
  93. <categoryandroid:name="android.intent.category.HOME"/>
  94. <categoryandroid:name="android.intent.category.DEFAULT"/>
  95. <categoryandroid:name="android.intent.category.MONKEY"/>
  96. </intent-filter>
  97. </activity>
  98. <!--设置Wallpapaer的Activity-->
  99. <activity
  100. android:name="WallpaperChooser"
  101. android:label="@string/pick_wallpaper"
  102. android:icon="@drawable/ic_launcher_wallpaper"
  103. android:screenOrientation="nosensor"
  104. android:finishOnCloseSystemDialogs="true">
  105. <intent-filter>
  106. <actionandroid:name="android.intent.action.SET_WALLPAPER"/>
  107. <categoryandroid:name="android.intent.category.DEFAULT"/>
  108. </intent-filter>
  109. </activity>
  110. <!--安装快捷方式的Intent-->
  111. <!--Intentreceivedusedtoinstallshortcutsfromotherapplications-->
  112. <receiver
  113. android:name="InstallShortcutReceiver"
  114. android:permission="com.android.zkx_launcher.permission.INSTALL_SHORTCUT">
  115. <intent-filter>
  116. <actionandroid:name="com.android.zkx_launcher.action.INSTALL_SHORTCUT"/>
  117. </intent-filter>
  118. </receiver>
  119. <!--卸载快捷方式的Intent-->
  120. <!--Intentreceivedusedtouninstallshortcutsfromotherapplications-->
  121. <receiver
  122. android:name="UninstallShortcutReceiver"
  123. android:permission="com.android.zkx_launcher.permission.UNINSTALL_SHORTCUT">
  124. <intent-filter>
  125. <actionandroid:name="com.android.zkx_launcher.action.UNINSTALL_SHORTCUT"/>
  126. </intent-filter>
  127. </receiver>
  128. <!--供应商信息-->
  129. <!--ThesettingsprovidercontainsHome'sdata,liketheworkspacefavorites-->
  130. <provider
  131. android:name="LauncherProvider"
  132. android:authorities="com.android.zkx_launcher.settings"
  133. android:writePermission="com.android.zkx_launcher.permission.WRITE_SETTINGS"
  134. android:readPermission="com.android.zkx_launcher.permission.READ_SETTINGS"/>
  135. </application>
  136. </manifest>

三 Launcher Res下的Xml文件

Res/xml下有两个xml文件,default_workspace.xml&&default_wallpaper.xml

Andorid这个默认壁纸不在launcher里,在源码中frameworks/base/core/res/res /drawable/default_wallpaper.jpg.
frameworks/base/core/res/res路径下包含很多default资源。如果需要修改默认设置可以尝试到这里来找一找
<favorites xmlns:launcher="http://schemas.android.com/apk/res/com.unique.launcher">
<!-- Far-left screen [0] -->

<!-- Left screen [1] -->
<appwidget
launcher:packageName="com.google.android.apps.genie.geniewidget"
launcher:className="com.google.android.apps.genie.geniewidget.miniwidget.MiniWidgetProvider"
launcher:screen="1"
launcher:x="0"
launcher:y="0"
launcher:spanX="4"
launcher:spanY="1" />
#天气新闻时钟插件
#packageName:widgetpackageName
#className :实现 widget receiver 类的名称.
#launcher:container 放置的位 置(只能为desktop
#screen : 在哪一个screen添加

#x,y: 在screen中的位置

#launcher:spanX:在x方向上所占格数
#launcher:spanY:在y方向上所占格数


<!-- Middle screen [2] -->
<search
launcher:screen="2"
launcher:x="0"
launcher:y="0" />


<appwidget
launcher:packageName="com.android.protips"
launcher:className="com.android.protips.ProtipWidget"
launcher:screen="2"
launcher:x="0"
launcher:y="1"
launcher:spanX="4"
launcher:spanY="1 " />


<!-- Right screen [3] -->
<appwidget
launcher:packageName="com.android.music"
launcher:className="com.android.music.MediaAppWidgetProvider"
launcher:screen="3"
launcher:x="0"
launcher:y="0"
launcher:spanX="4"
launcher:spanY="1" />


<appwidget
launcher:packageName="com.android.vending"
launcher:className="com.android.vending.MarketWidgetProvider"
launcher:screen="3"
launcher:x="1"
launcher:y="1"
launcher:spanX="2"
launcher:spanY="2" />
#电子市场Android Market
<!-- Far-right screen [4] -->

</favorites>

四 Launcher Manifest文件
Launcher的AndroidManifest.xml文件有很多特殊性,分析一下就会理解整个程序的大概结构。
代码如下:

<manifestxmlns:android="http://schemas.android.com/apk/res/android"

package="net.sunniwell.launcher"

android:versionCode="1"android:versionName="1.0.1">

关于自定义权限,这是很好的例子,其他apk程序要想使用Launcher的功能必须添加这些权限,而这些权限都是在这里声明的。


这个是安装快捷方式的权限定义:


<permission

android:name="com.android.launcher.permission.INSTALL_SHORTCUT"

android:permissionGroup="android.permission-group.SYSTEM_TOOLS"

android:protectionLevel="normal"

android:label="@string/permlab_install_shortcut"

android:description="@string/permdesc_install_shortcut"/>



这个是卸载快捷方式的权限定义:


<permission

android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT"

android:permissionGroup="android.permission-group.SYSTEM_TOOLS"

android:protectionLevel="normal"

android:label="@string/permlab_uninstall_shortcut"

android:description="@string/permdesc_uninstall_shortcut"/>


这个是读取launcher.db内容的权限定义:


<permission

android:name="net.sunniwell.launcher.permission.READ_SETTINGS"

android:permissionGroup="android.permission-group.SYSTEM_TOOLS"

android:protectionLevel="normal"

android:label="@string/permlab_read_settings"

android:description="@string/permdesc_read_settings"/>


这个是修改和删除launcher.db内容的权限定义:


<permission

android:name="net.sunniwell.launcher.permission.WRITE_SETTINGS"

android:permissionGroup="android.permission-group.SYSTEM_TOOLS"

android:protectionLevel="normal"

android:label="@string/permlab_write_settings"

android:description="@string/permdesc_write_settings"/>

这些是Launcher的权限声明,通过这些就能看出launcher的大概功能了:

打电话权限:


<uses-permissionandroid:name="android.permission.CALL_PHONE"/>

使用状态栏权限:


<uses-permissionandroid:name="android.permission.EXPAND_STATUS_BAR"/>

获取当前或最近运行的任务的信息的权限:


<uses-permissionandroid:name="android.permission.GET_TASKS"/>

读取通信录权限:


<uses-permissionandroid:name="android.permission.READ_CONTACTS"/>

设置壁纸权限:

<uses-permissionandroid:name="android.permission.SET_WALLPAPER"/>

允许程序设置壁纸hits的权限:

<uses-permissionandroid:name="android.permission.SET_WALLPAPER_HINTS"/>

使用震动功能权限:

<uses-permissionandroid:name="android.permission.VIBRATE"/>

修改删除launcher.db内容权限:

<uses-permissionandroid:name="android.permission.WRITE_SETTINGS"/>

绑定widget权限:

<uses-permissionandroid:name="android.permission.BIND_APPWIDGET"/>

读取launcher.db内容权限:

<uses-permissionandroid:name="net.sunniwell.launcher.permission.READ_SETTINGS"/>

修改删除launcher.db内容权限:

<uses-permissionandroid:name="net.sunniwell.launcher.permission.WRITE_SETTINGS"/>

读写外部存储设备权限:

<uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>




<application

android:name="LauncherApplication"

activity应该运行的进程的名字:

android:process="android.process.acore"

android:label="@string/application_name"

android:icon="@drawable/swicon">


<activity

android:name="Launcher"

是否

android:launchMode="singleTask"

android:clearTaskOnLaunch="true"

这个activity是否在被杀死或者重启后能恢复原来的状态:

android:stateNotNeeded="true"

android:theme="@style/Theme"

android:screenOrientation="landscape"

android:windowSoftInputMode="stateUnspecified|adjustPan">


<intent-filter>


<actionandroid:name="android.intent.action.MAIN"/>


<categoryandroid:name="android.intent.category.LAUNCHER"/>



桌面应用的标记:

<categoryandroid:name="android.intent.category.HOME"/>


<categoryandroid:name="android.intent.category.DEFAULT"/>


自动化测试工具Monkey的标记,待研究

<categoryandroid:name="android.intent.category.MONKEY"/>



</intent-filter>


</activity>

选择壁纸的activity:

<activity

android:name="WallpaperChooser"

android:label="@string/pick_wallpaper"

android:icon="@drawable/ic_launcher_gallery">


设置壁纸的intent-filter


<intent-filter>


<actionandroid:name="android.intent.action.SET_WALLPAPER"/>


<categoryandroid:name="android.intent.category.DEFAULT"/>


</intent-filter>

搜索的activity

</activity>


<!-- Enable system-default search mode for any activity in Home -->


<meta-data

android:name="android.app.default_searchable"

android:value="*"/>

安装快捷方式的广播接收器:


<!-- Intent received used to install shortcuts from other applications -->



<receiver

android:name=".InstallShortcutReceiver"

android:permission="com.android.launcher.permission.INSTALL_SHORTCUT">


<intent-filter>


<actionandroid:name="com.android.launcher.action.INSTALL_SHORTCUT"/>


</intent-filter>


</receiver>


<!-- Intent received used to uninstall shortcuts from other applications -->

卸载快捷方式的广播接收器:


<receiver

android:name=".UninstallShortcutReceiver"

android:permission="com.android.launcher.permission.UNINSTALL_SHORTCUT">


<intent-filter>


<actionandroid:name="com.android.launcher.action.UNINSTALL_SHORTCUT"/>


</intent-filter>


</receiver>

声明ContentProvider,用于对launcher.db操作:


<!-- The settings provider contains Home's data, like the workspace favorites -->


<provider

android:name="SWLauncherProvider"

android:authorities="net.sunniwell.launcher.settings"

android:writePermission="net.sunniwell.launcher.permission.WRITE_SETTINGS"

android:readPermission="net.sunniwell.launcher.permission.READ_SETTINGS"/>


</application>


<uses-sdkandroid:minSdkVersion="4"/>

</manifest>
说明:
1.
<manifest标签头部还应声明:

android:sharedUserId="android.uid.shared",作用是获得系统权限,但是这样的程序属性只能在build整个系统时放进去(就是系统软件)才起作用,手动安装是没有权限的。

五 Launcher 常用类介绍

AddAdapter:维护了live fold, widget , shortcut , wallpaper 4个ListItem,长按桌面会显示该列表

AllAppsGridView:显示APP的网格

ApplicationInfo:一个可启动的应用

ApplicationsAdapter:gridview的adapter

BubbleTextView:一个定制了的textview

CellLayout:屏幕网格化

DeleteZone:UI的一部分

DragController,dragscroller, dragsource, droptarget:支持拖拽操作

DragLayer:内部支持拖拽的viewgroup

FastBitmapDrawable:工具

Folder:Icons的集合

FolderIcon:出现在workspace的icon代表了一个folder

FolderInfo: ItemInfo子类

HandleView:一个imageview。

InstallShortcutReceiver,UninstallShortcutReceiver:一个broadcastrecier

ItemInfo:代表Launcher中一个Item(例如folder)

Launcher: Launcher程序的主窗口

LauncherApplication:在VM中设置参数

LauncherAppWidgetHost,LauncherAppWidgetHostView,:Widget相关

LauncherModel:MVC中的M

LauncherProvider:一个contentprovider,为Launcher存储信息

LauncherSettings:设置相关的工具

LiveFolder,LiveFolderAdapter,LiveFolderIcon,LiveFolderInfo:livefolder相关

Search:搜索

UserFolder,UserFolderInfo:文件夹包含applications ,shortcuts

Utilities:小工具

WallpaperChooser:选择wallpaper的activity

Workspace:屏幕上的一块区域

widget :代表启动的widget实例,例如搜索

总结

1) Launcher中实现了MVC模式(M:launchermode , V:draglayer ,C: launcher),以此为主线,可以得到Launcher对各个组件管理的细节(如drag的实现)。

六 Launcher 起动过程

Android系统在启动时会安装应用程序,这些应用程序安装好之后,还需要有一个Home应用程序来负责把它们在桌面上展示出来,在Android系统中,这个默认的Home应用程序就是Launcher了,我将详细分析Launcher应用程序的启动过程。

Android系统的Home应用程序Launcher是由ActivityManagerService启动的,而ActivityManagerService和PackageManagerService一样,都是在开机时由SystemServer组件启动的,SystemServer组件首先是启动ePackageManagerServic,由它来负责安装系统的应用程序,系统中的应用程序安装好了以后,SystemServer组件接下来就要通过ActivityManagerService来启动Home应用程序Launcher了,Launcher在启动的时候便会通过PackageManagerServic把系统中已经安装好的应用程序以快捷图标的形式展示在桌面上,这样用户就可以使用这些应用程序了。

下面详细分析每一个步骤。

Step 1. SystemServer.main

这个函数定义在frameworks/base/services/java/com/android/server/SystemServer.java文件中:

  1. publicclassSystemServer
  2. {
  3. ......
  4. nativepublicstaticvoidinit1(String[]args);
  5. ......
  6. publicstaticvoidmain(String[]args){
  7. ......
  8. init1(args);
  9. ......
  10. }
  11. ......
  12. }
SystemServer组件是由Zygote进程负责启动的,启动的时候就会调用它的main函数,这个函数主要调用了JNI方法init1来做一些系统初始化的工作。

Step 2. SystemServer.init1

这个函数是一个JNI方法,实现在frameworks/base/services/jni/com_android_server_SystemServer.cpp文件中:

  1. namespaceandroid{
  2. extern"C"intsystem_init();
  3. staticvoidandroid_server_SystemServer_init1(JNIEnv*env,jobjectclazz)
  4. {
  5. system_init();
  6. }
  7. /*
  8. *JNIregistration.
  9. */
  10. staticJNINativeMethodgMethods[]={
  11. /*name,signature,funcPtr*/
  12. {"init1","([Ljava/lang/String;)V",(void*)android_server_SystemServer_init1},
  13. };
  14. intregister_android_server_SystemServer(JNIEnv*env)
  15. {
  16. returnjniRegisterNativeMethods(env,"com/android/server/SystemServer",
  17. gMethods,NELEM(gMethods));
  18. }
  19. };//namespaceandroid
这个函数很简单,只是调用了system_init函数来进一步执行操作。

Step 3.libsystem_server.system_init

函数system_init实现在libsystem_server库中,源代码位于frameworks/base/cmds/system_server/library/system_init.cpp文件中:

  1. extern"C"status_tsystem_init()
  2. {
  3. LOGI("Enteredsystem_init()");
  4. sp<ProcessState>proc(ProcessState::self());
  5. sp<IServiceManager>sm=defaultServiceManager();
  6. LOGI("ServiceManager:%p\n",sm.get());
  7. sp<GrimReaper>grim=newGrimReaper();
  8. sm->asBinder()->linkToDeath(grim,grim.get(),0);
  9. charpropBuf[PROPERTY_VALUE_MAX];
  10. property_get("system_init.startsurfaceflinger",propBuf,"1");
  11. if(strcmp(propBuf,"1")==0){
  12. //StarttheSurfaceFlinger
  13. SurfaceFlinger::instantiate();
  14. }
  15. //Startthesensorservice
  16. SensorService::instantiate();
  17. //Onthesimulator,audioflingeretaldon'tgetstartedthe
  18. //samewayasonthedevice,andweneedtostartthemhere
  19. if(!proc->supportsProcesses()){
  20. //StarttheAudioFlinger
  21. AudioFlinger::instantiate();
  22. //Startthemediaplaybackservice
  23. MediaPlayerService::instantiate();
  24. //Startthecameraservice
  25. CameraService::instantiate();
  26. //Starttheaudiopolicyservice
  27. AudioPolicyService::instantiate();
  28. }
  29. //AndnowstarttheAndroidruntime.Wehavetodothisbit
  30. //ofnastinessbecausetheAndroidruntimeinitializationrequires
  31. //someofthecoresystemservicestoalreadybestarted.
  32. //AllotherserversshouldjuststarttheAndroidruntimeat
  33. //thebeginningoftheirprocesses'smain(),beforecalling
  34. //theinitfunction.
  35. LOGI("Systemserver:startingAndroidruntime.\n");
  36. AndroidRuntime*runtime=AndroidRuntime::getRuntime();
  37. LOGI("Systemserver:startingAndroidservices.\n");
  38. runtime->callStatic("com/android/server/SystemServer","init2");
  39. //Ifrunninginourownprocess,justgointothethread
  40. //pool.Otherwise,calltheinitializationfinished
  41. //functoletthisprocesscontinueitsinitilization.
  42. if(proc->supportsProcesses()){
  43. LOGI("Systemserver:enteringthreadpool.\n");
  44. ProcessState::self()->startThreadPool();
  45. IPCThreadState::self()->joinThreadPool();
  46. LOGI("Systemserver:exitingthreadpool.\n");
  47. }
  48. returnNO_ERROR;
  49. }
这个函数首先会初始化SurfaceFlinger、SensorService、AudioFlinger、MediaPlayerService、CameraService和AudioPolicyService这几个服务,然后就通过系统全局唯一的AndroidRuntime实例变量runtime的callStatic来调用SystemServer的init2函数了。关于这个AndroidRuntime实例变量runtime的相关资料,可能参考前面一篇文章Android应用程序进程启动过程的源代码分析一文。

Step 4. AndroidRuntime.callStatic

这个函数定义在frameworks/base/core/jni/AndroidRuntime.cpp文件中:

  1. /*
  2. *CallastaticJavaProgrammingLanguagefunctionthattakesnoargumentsandreturnsvoid.
  3. */
  4. status_tAndroidRuntime::callStatic(constchar*className,constchar*methodName)
  5. {
  6. JNIEnv*env;
  7. jclassclazz;
  8. jmethodIDmethodId;
  9. env=getJNIEnv();
  10. if(env==NULL)
  11. returnUNKNOWN_ERROR;
  12. clazz=findClass(env,className);
  13. if(clazz==NULL){
  14. LOGE("ERROR:couldnotfindclass'%s'\n",className);
  15. returnUNKNOWN_ERROR;
  16. }
  17. methodId=env->GetStaticMethodID(clazz,methodName,"()V");
  18. if(methodId==NULL){
  19. LOGE("ERROR:couldnotfindmethod%s.%s\n",className,methodName);
  20. returnUNKNOWN_ERROR;
  21. }
  22. env->CallStaticVoidMethod(clazz,methodId);
  23. returnNO_ERROR;
  24. }
这个函数调用由参数className指定的java类的静态成员函数,这个静态成员函数是由参数methodName指定的。上面传进来的参数className的值为"com/android/server/SystemServer",而参数methodName的值为"init2",因此,接下来就会调用SystemServer类的init2函数了。

Step 5.SystemServer.init2

这个函数定义在frameworks/base/services/java/com/android/server/SystemServer.java文件中:

  1. publicclassSystemServer
  2. {
  3. ......
  4. publicstaticfinalvoidinit2(){
  5. Slog.i(TAG,"EnteredtheAndroidsystemserver!");
  6. Threadthr=newServerThread();
  7. thr.setName("android.server.ServerThread");
  8. thr.start();
  9. }
  10. }
这个函数创建了一个ServerThread线程,PackageManagerService服务就是这个线程中启动的了。这里调用了ServerThread实例thr的start函数之后,下面就会执行这个实例的run函数了。

Step 6.ServerThread.run

这个函数定义在frameworks/base/services/java/com/android/server/SystemServer.java文件中:

  1. classServerThreadextendsThread{
  2. ......
  3. @Override
  4. publicvoidrun(){
  5. ......
  6. IPackageManagerpm=null;
  7. ......
  8. //Criticalservices...
  9. try{
  10. ......
  11. Slog.i(TAG,"PackageManager");
  12. pm=PackageManagerService.main(context,
  13. factoryTest!=SystemServer.FACTORY_TEST_OFF);
  14. ......
  15. }catch(RuntimeExceptione){
  16. Slog.e("System","Failurestartingcoreservice",e);
  17. }
  18. ......
  19. }
  20. ......
  21. }
这个函数除了启动PackageManagerService服务之外,还启动了其它很多的服务,例如在前面学习Activity和Service的几篇文章中经常看到的ActivityManagerService服务,有兴趣的读者可以自己研究一下。
Step 7. ActivityManagerService.main

这个函数定义在frameworks/base/services/java/com/android/server/am/ActivityManagerServcie.java文件中:

  1. publicfinalclassActivityManagerServiceextendsActivityManagerNative
  2. implementsWatchdog.Monitor,BatteryStatsImpl.BatteryCallback{
  3. ......
  4. publicstaticfinalContextmain(intfactoryTest){
  5. AThreadthr=newAThread();
  6. thr.start();
  7. synchronized(thr){
  8. while(thr.mService==null){
  9. try{
  10. thr.wait();
  11. }catch(InterruptedExceptione){
  12. }
  13. }
  14. }
  15. ActivityManagerServicem=thr.mService;
  16. mSelf=m;
  17. ActivityThreadat=ActivityThread.systemMain();
  18. mSystemThread=at;
  19. Contextcontext=at.getSystemContext();
  20. m.mContext=context;
  21. m.mFactoryTest=factoryTest;
  22. m.mMainStack=newActivityStack(m,context,true);
  23. m.mBatteryStatsService.publish(context);
  24. m.mUsageStatsService.publish(context);
  25. synchronized(thr){
  26. thr.mReady=true;
  27. thr.notifyAll();
  28. }
  29. m.startRunning(null,null,null,null);
  30. returncontext;
  31. }
  32. ......
  33. }
这个函数首先通过AThread线程对象来内部创建了一个ActivityManagerService实例,然后将这个实例保存其成员变量mService中,接着又把这个ActivityManagerService实例保存在ActivityManagerService类的静态成员变量mSelf中,最后初始化其它成员变量,就结束了。

Step 8.PackageManagerService.main

这个函数定义在frameworks/base/services/java/com/android/server/PackageManagerService.java文件中:

  1. classPackageManagerServiceextendsIPackageManager.Stub{
  2. ......
  3. publicstaticfinalIPackageManagermain(Contextcontext,booleanfactoryTest){
  4. PackageManagerServicem=newPackageManagerService(context,factoryTest);
  5. ServiceManager.addService("package",m);
  6. returnm;
  7. }
  8. ......
  9. }
这个函数创建了一个PackageManagerService服务实例,然后把这个服务添加到ServiceManager中去,ServiceManager是Android系统Binder进程间通信机制的守护进程,负责管理系统中的Binder对象,在创建这个PackageManagerService服务实例时,会在PackageManagerService类的构造函数中开始执行安装应用程序的过程:

  1. classPackageManagerServiceextendsIPackageManager.Stub{
  2. ......
  3. publicPackageManagerService(Contextcontext,booleanfactoryTest){
  4. ......
  5. synchronized(mInstallLock){
  6. synchronized(mPackages){
  7. ......
  8. FiledataDir=Environment.getDataDirectory();
  9. mAppDataDir=newFile(dataDir,"data");
  10. mSecureAppDataDir=newFile(dataDir,"secure/data");
  11. mDrmAppPrivateInstallDir=newFile(dataDir,"app-private");
  12. ......
  13. mFrameworkDir=newFile(Environment.getRootDirectory(),"framework");
  14. mDalvikCacheDir=newFile(dataDir,"dalvik-cache");
  15. ......
  16. //Findbaseframeworks(resourcepackageswithoutcode).
  17. mFrameworkInstallObserver=newAppDirObserver(
  18. mFrameworkDir.getPath(),OBSERVER_EVENTS,true);
  19. mFrameworkInstallObserver.startWatching();
  20. scanDirLI(mFrameworkDir,PackageParser.PARSE_IS_SYSTEM
  21. |PackageParser.PARSE_IS_SYSTEM_DIR,
  22. scanMode|SCAN_NO_DEX,0);
  23. //Collectallsystempackages.
  24. mSystemAppDir=newFile(Environment.getRootDirectory(),"app");
  25. mSystemInstallObserver=newAppDirObserver(
  26. mSystemAppDir.getPath(),OBSERVER_EVENTS,true);
  27. mSystemInstallObserver.startWatching();
  28. scanDirLI(mSystemAppDir,PackageParser.PARSE_IS_SYSTEM
  29. |PackageParser.PARSE_IS_SYSTEM_DIR,scanMode,0);
  30. //Collectallvendorpackages.
  31. mVendorAppDir=newFile("/vendor/app");
  32. mVendorInstallObserver=newAppDirObserver(
  33. mVendorAppDir.getPath(),OBSERVER_EVENTS,true);
  34. mVendorInstallObserver.startWatching();
  35. scanDirLI(mVendorAppDir,PackageParser.PARSE_IS_SYSTEM
  36. |PackageParser.PARSE_IS_SYSTEM_DIR,scanMode,0);
  37. mAppInstallObserver=newAppDirObserver(
  38. mAppInstallDir.getPath(),OBSERVER_EVENTS,false);
  39. mAppInstallObserver.startWatching();
  40. scanDirLI(mAppInstallDir,0,scanMode,0);
  41. mDrmAppInstallObserver=newAppDirObserver(
  42. mDrmAppPrivateInstallDir.getPath(),OBSERVER_EVENTS,false);
  43. mDrmAppInstallObserver.startWatching();
  44. scanDirLI(mDrmAppPrivateInstallDir,PackageParser.PARSE_FORWARD_LOCK,
  45. scanMode,0);
  46. ......
  47. }
  48. }
  49. }
  50. ......
  51. }
这里会调用scanDirLI函数来扫描移动设备上的下面这五个目录中的Apk文件:

/system/framework

/system/app

/vendor/app

/data/app

/data/app-private

Step 9.ActivityManagerService.setSystemProcess

这个函数定义在frameworks/base/services/java/com/android/server/am/ActivityManagerServcie.java文件中:

  1. publicfinalclassActivityManagerServiceextendsActivityManagerNative
  2. implementsWatchdog.Monitor,BatteryStatsImpl.BatteryCallback{
  3. ......
  4. publicstaticvoidsetSystemProcess(){
  5. try{
  6. ActivityManagerServicem=mSelf;
  7. ServiceManager.addService("activity",m);
  8. ServiceManager.addService("meminfo",newMemBinder(m));
  9. if(MONITOR_CPU_USAGE){
  10. ServiceManager.addService("cpuinfo",newCpuBinder(m));
  11. }
  12. ServiceManager.addService("permission",newPermissionController(m));
  13. ApplicationInfoinfo=
  14. mSelf.mContext.getPackageManager().getApplicationInfo(
  15. "android",STOCK_PM_FLAGS);
  16. mSystemThread.installSystemApplicationInfo(info);
  17. synchronized(mSelf){
  18. ProcessRecordapp=mSelf.newProcessRecordLocked(
  19. mSystemThread.getApplicationThread(),info,
  20. info.processName);
  21. app.persistent=true;
  22. app.pid=MY_PID;
  23. app.maxAdj=SYSTEM_ADJ;
  24. mSelf.mProcessNames.put(app.processName,app.info.uid,app);
  25. synchronized(mSelf.mPidsSelfLocked){
  26. mSelf.mPidsSelfLocked.put(app.pid,app);
  27. }
  28. mSelf.updateLruProcessLocked(app,true,true);
  29. }
  30. }catch(PackageManager.NameNotFoundExceptione){
  31. thrownewRuntimeException(
  32. "Unabletofindandroidsystempackage",e);
  33. }
  34. }
  35. ......
  36. }

这个函数首先是将这个ActivityManagerService实例添加到ServiceManager中去托管,这样其它地方就可以通过ServiceManager.getService接口来访问这个全局唯一的ActivityManagerService实例了,接着又通过调用mSystemThread.installSystemApplicationInfo函数来把应用程序框架层下面的android包加载进来 ,这里的mSystemThread是一个ActivityThread类型的实例变量,它是在上面的Step 7中创建的,后面就是一些其它的初始化工作了。

Step 10. ActivityManagerService.systemReady

这个函数是在上面的Step 6中的ServerThread.run函数在将系统中的一系列服务都初始化完毕之后才调用的,它定义在frameworks/base/services/java/com/android/server/am/ActivityManagerServcie.java文件中:

  1. publicfinalclassActivityManagerServiceextendsActivityManagerNative
  2. implementsWatchdog.Monitor,BatteryStatsImpl.BatteryCallback{
  3. ......
  4. publicvoidsystemReady(finalRunnablegoingCallback){
  5. ......
  6. synchronized(this){
  7. ......
  8. mMainStack.resumeTopActivityLocked(null);
  9. }
  10. }
  11. ......
  12. }

这个函数的内容比较多,这里省去无关的部分,主要关心启动Home应用程序的逻辑,这里就是通过mMainStack.resumeTopActivityLocked函数来启动Home应用程序的了,这里的mMainStack是一个ActivityStack类型的实例变量。

Step 11. ActivityStack.resumeTopActivityLocked

这个函数定义在frameworks/base/services/java/com/android/server/am/ActivityStack.java文件中:

  1. publicclassActivityStack{
  2. ......
  3. finalbooleanresumeTopActivityLocked(ActivityRecordprev){
  4. //Findthefirstactivitythatisnotfinishing.
  5. ActivityRecordnext=topRunningActivityLocked(null);
  6. ......
  7. if(next==null){
  8. //Therearenomoreactivities!Let'sjuststartupthe
  9. //Launcher...
  10. if(mMainStack){
  11. returnmService.startHomeActivityLocked();
  12. }
  13. }
  14. ......
  15. }
  16. ......
  17. }

这里调用函数topRunningActivityLocked返回的是当前系统Activity堆栈最顶端的Activity,由于此时还没有Activity被启动过,因此,返回值为null,即next变量的值为null,于是就调用mService.startHomeActivityLocked语句,这里的mService就是前面在Step 7中创建的ActivityManagerService实例了。

Step 12.ActivityManagerService.startHomeActivityLocked

这个函数定义在frameworks/base/services/java/com/android/server/am/ActivityManagerServcie.java文件中:

  1. publicfinalclassActivityManagerServiceextendsActivityManagerNative
  2. implementsWatchdog.Monitor,BatteryStatsImpl.BatteryCallback{
  3. ......
  4. booleanstartHomeActivityLocked(){
  5. ......
  6. Intentintent=newIntent(
  7. mTopAction,
  8. mTopData!=null?Uri.parse(mTopData):null);
  9. intent.setComponent(mTopComponent);
  10. if(mFactoryTest!=SystemServer.FACTORY_TEST_LOW_LEVEL){
  11. intent.addCategory(Intent.CATEGORY_HOME);
  12. }
  13. ActivityInfoaInfo=
  14. intent.resolveActivityInfo(mContext.getPackageManager(),
  15. STOCK_PM_FLAGS);
  16. if(aInfo!=null){
  17. intent.setComponent(newComponentName(
  18. aInfo.applicationInfo.packageName,aInfo.name));
  19. //Don'tdothisifthehomeappiscurrentlybeing
  20. //instrumented.
  21. ProcessRecordapp=getProcessRecordLocked(aInfo.processName,
  22. aInfo.applicationInfo.uid);
  23. if(app==null||app.instrumentationClass==null){
  24. intent.setFlags(intent.getFlags()|Intent.FLAG_ACTIVITY_NEW_TASK);
  25. mMainStack.startActivityLocked(null,intent,null,null,0,aInfo,
  26. null,null,0,0,0,false,false);
  27. }
  28. }
  29. returntrue;
  30. }
  31. ......
  32. }

函数首先创建一个CATEGORY_HOME类型的Intent,然后通过Intent.resolveActivityInfo函数向PackageManagerService查询Category类型为HOME的Activity,这里我们假设只有系统自带的Launcher应用程序注册了HOME类型的Activity(见packages/apps/Launcher2/AndroidManifest.xml文件):

  1. <manifest
  2. xmlns:android="http://schemas.android.com/apk/res/android"
  3. package="com.android.launcher"
  4. android:sharedUserId="@string/sharedUserId"
  5. >
  6. ......
  7. <application
  8. android:name="com.android.launcher2.LauncherApplication"
  9. android:process="@string/process"
  10. android:label="@string/application_name"
  11. android:icon="@drawable/ic_launcher_home">
  12. <activity
  13. android:name="com.android.launcher2.Launcher"
  14. android:launchMode="singleTask"
  15. android:clearTaskOnLaunch="true"
  16. android:stateNotNeeded="true"
  17. android:theme="@style/Theme"
  18. android:screenOrientation="nosensor"
  19. android:windowSoftInputMode="stateUnspecified|adjustPan">
  20. <intent-filter>
  21. <actionandroid:name="android.intent.action.MAIN"/>
  22. <categoryandroid:name="android.intent.category.HOME"/>
  23. <categoryandroid:name="android.intent.category.DEFAULT"/>
  24. <categoryandroid:name="android.intent.category.MONKEY"/>
  25. </intent-filter>
  26. </activity>
  27. ......
  28. </application>
  29. </manifest>

因此,这里就返回com.android.launcher2.Launcher这个Activity了。由于是第一次启动这个Activity,接下来调用函数getProcessRecordLocked返回来的ProcessRecord值为null,于是,就调用mMainStack.startActivityLocked函数启动com.android.launcher2.Launcher这个Activity了,这里的mMainStack是一个ActivityStack类型的成员变量。

Step 13. ActivityStack.startActivityLocked

这个函数定义在frameworks/base/services/java/com/android/server/am/ActivityStack.java文件中

Step 14.Launcher.onCreate

这个函数定义在packages/apps/Launcher2/src/com/android/launcher2/Launcher.java文件中:

  1. publicfinalclassLauncherextendsActivity
  2. implementsView.OnClickListener,OnLongClickListener,LauncherModel.Callbacks,AllAppsView.Watcher{
  3. ......
  4. @Override
  5. protectedvoidonCreate(BundlesavedInstanceState){
  6. ......
  7. if(!mRestoring){
  8. mModel.startLoader(this,true);
  9. }
  10. ......
  11. }
  12. ......
  13. }

这里的mModel是一个LauncherModel类型的成员变量,这里通过调用它的startLoader成员函数来执行加应用程序的操作。

Step 15.LauncherModel.startLoader

这个函数定义在packages/apps/Launcher2/src/com/android/launcher2/LauncherModel.java文件中:

  1. publicclassLauncherModelextendsBroadcastReceiver{
  2. ......
  3. publicvoidstartLoader(Contextcontext,booleanisLaunching){
  4. ......
  5. synchronized(mLock){
  6. ......
  7. //Don'tbothertostartthethreadifweknowit'snotgoingtodoanything
  8. if(mCallbacks!=null&&mCallbacks.get()!=null){
  9. //Ifthereisalreadyonerunning,tellittostop.
  10. LoaderTaskoldTask=mLoaderTask;
  11. if(oldTask!=null){
  12. if(oldTask.isLaunching()){
  13. //don'tdowngradeisLaunchingifwe'realreadyrunning
  14. isLaunching=true;
  15. }
  16. oldTask.stopLocked();
  17. }
  18. mLoaderTask=newLoaderTask(context,isLaunching);
  19. sWorker.post(mLoaderTask);
  20. }
  21. }
  22. }
  23. ......
  24. }

这里不是直接加载应用程序,而是把加载应用程序的操作作为一个消息来处理。这里的sWorker是一个Handler,通过它的post方式把一个消息放在消息队列中去,然后系统就会调用传进去的参数mLoaderTask的run函数来处理这个消息,这个mLoaderTask是LoaderTask类型的实例,于是,下面就会执行LoaderTask类的run函数了。

Step 16. LoaderTask.run

这个函数定义在packages/apps/Launcher2/src/com/android/launcher2/LauncherModel.java文件中:

  1. publicclassLauncherModelextendsBroadcastReceiver{
  2. ......
  3. privateclassLoaderTaskimplementsRunnable{
  4. ......
  5. publicvoidrun(){
  6. ......
  7. keep_running:{
  8. ......
  9. //secondstep
  10. if(loadWorkspaceFirst){
  11. ......
  12. loadAndBindAllApps();
  13. }else{
  14. ......
  15. }
  16. ......
  17. }
  18. ......
  19. }
  20. ......
  21. }
  22. ......
  23. }

这里调用loadAndBindAllApps成员函数来进一步操作。

Step 17.LoaderTask.loadAndBindAllApps
这个函数定义在packages/apps/Launcher2/src/com/android/launcher2/LauncherModel.java文件中:

  1. publicclassLauncherModelextendsBroadcastReceiver{
  2. ......
  3. privateclassLoaderTaskimplementsRunnable{
  4. ......
  5. privatevoidloadAndBindAllApps(){
  6. ......
  7. if(!mAllAppsLoaded){
  8. loadAllAppsByBatch();
  9. if(mStopped){
  10. return;
  11. }
  12. mAllAppsLoaded=true;
  13. }else{
  14. onlyBindAllApps();
  15. }
  16. }
  17. ......
  18. }
  19. ......
  20. }

由于还没有加载过应用程序,这里的mAllAppsLoaded为false,于是就继续调用loadAllAppsByBatch函数来进一步操作了。

Step 18.LoaderTask.loadAllAppsByBatch
这个函数定义在packages/apps/Launcher2/src/com/android/launcher2/LauncherModel.java文件中:

  1. publicclassLauncherModelextendsBroadcastReceiver{
  2. ......
  3. privateclassLoaderTaskimplementsRunnable{
  4. ......
  5. privatevoidloadAllAppsByBatch(){
  6. ......
  7. finalIntentmainIntent=newIntent(Intent.ACTION_MAIN,null);
  8. mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
  9. finalPackageManagerpackageManager=mContext.getPackageManager();
  10. List<ResolveInfo>apps=null;
  11. intN=Integer.MAX_VALUE;
  12. intstartIndex;
  13. inti=0;
  14. intbatchSize=-1;
  15. while(i<N&&!mStopped){
  16. if(i==0){
  17. mAllAppsList.clear();
  18. ......
  19. apps=packageManager.queryIntentActivities(mainIntent,0);
  20. ......
  21. N=apps.size();
  22. ......
  23. if(mBatchSize==0){
  24. batchSize=N;
  25. }else{
  26. batchSize=mBatchSize;
  27. }
  28. ......
  29. Collections.sort(apps,
  30. newResolveInfo.DisplayNameComparator(packageManager));
  31. }
  32. startIndex=i;
  33. for(intj=0;i<N&&j<batchSize;j++){
  34. //Thisbuildstheiconbitmaps.
  35. mAllAppsList.add(newApplicationInfo(apps.get(i),mIconCache));
  36. i++;
  37. }
  38. finalbooleanfirst=i<=batchSize;
  39. finalCallbackscallbacks=tryGetCallbacks(oldCallbacks);
  40. finalArrayList<ApplicationInfo>added=mAllAppsList.added;
  41. mAllAppsList.added=newArrayList<ApplicationInfo>();
  42. mHandler.post(newRunnable(){
  43. publicvoidrun(){
  44. finallongt=SystemClock.uptimeMillis();
  45. if(callbacks!=null){
  46. if(first){
  47. callbacks.bindAllApplications(added);
  48. }else{
  49. callbacks.bindAppsAdded(added);
  50. }
  51. ......
  52. }else{
  53. ......
  54. }
  55. }
  56. });
  57. ......
  58. }
  59. ......
  60. }
  61. ......
  62. }
  63. ......
  64. }

函数首先构造一个CATEGORY_LAUNCHER类型的Intent:

  1. finalIntentmainIntent=newIntent(Intent.ACTION_MAIN,null);
  2. mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);

接着从mContext变量中获得PackageManagerService的接口:

  1. finalPackageManagerpackageManager=mContext.getPackageManager();

下一步就是通过这个PackageManagerService.queryIntentActivities接口来取回所有Action类型为Intent.ACTION_MAIN,并且Category类型为Intent.CATEGORY_LAUNCHER的Activity了。

我们先进入到PackageManagerService.queryIntentActivities函数中看看是如何获得这些Activity的,然后再回到这个函数中来看其余操作。

Step 19.PackageManagerService.queryIntentActivities

这个函数定义在frameworks/base/services/java/com/android/server/PackageManagerService.java文件中:

  1. classPackageManagerServiceextendsIPackageManager.Stub{
  2. ......
  3. publicList<ResolveInfo>queryIntentActivities(Intentintent,
  4. StringresolvedType,intflags){
  5. ......
  6. synchronized(mPackages){
  7. StringpkgName=intent.getPackage();
  8. if(pkgName==null){
  9. return(List<ResolveInfo>)mActivities.queryIntent(intent,
  10. resolvedType,flags);
  11. }
  12. ......
  13. }
  14. ......
  15. }
  16. ......
  17. }

系统在启动PackageManagerService时,会把系统中的应用程序都解析一遍,然后把解析得到的Activity都保存在mActivities变量中,这里通过这个mActivities变量的queryIntent函数返回符合条件intent的Activity,这里要返回的便是Action类型为Intent.ACTION_MAIN,并且Category类型为Intent.CATEGORY_LAUNCHER的Activity了。

回到Step 18中的LoaderTask.loadAllAppsByBatch函数中,从queryIntentActivities函数调用处返回所要求的Activity后,便调用函数tryGetCallbacks(oldCallbacks)得到一个返CallBack接口,这个接口是由Launcher类实现的,接着调用这个接口的.bindAllApplications函数来进一步操作。注意,这里又是通过消息来处理加载应用程序的操作的。

Step 20.Launcher.bindAllApplications

这个函数定义在packages/apps/Launcher2/src/com/android/launcher2/Launcher.java文件中:

  1. publicfinalclassLauncherextendsActivity
  2. implementsView.OnClickListener,OnLongClickListener,LauncherModel.Callbacks,AllAppsView.Watcher{
  3. ......
  4. privateAllAppsViewmAllAppsGrid;
  5. ......
  6. publicvoidbindAllApplications(ArrayList<ApplicationInfo>apps){
  7. mAllAppsGrid.setApps(apps);
  8. }
  9. ......
  10. }

这里的mAllAppsGrid是一个AllAppsView类型的变量,它的实际类型一般就是AllApps2D了。

Step 21.AllApps2D.setApps

这个函数定义在packages/apps/Launcher2/src/com/android/launcher2/AllApps2D.java文件中:

  1. publicclassAllApps2D
  2. extendsRelativeLayout
  3. implementsAllAppsView,
  4. AdapterView.OnItemClickListener,
  5. AdapterView.OnItemLongClickListener,
  6. View.OnKeyListener,
  7. DragSource{
  8. ......
  9. publicvoidsetApps(ArrayList<ApplicationInfo>list){
  10. mAllAppsList.clear();
  11. addApps(list);
  12. }
  13. publicvoidaddApps(ArrayList<ApplicationInfo>list){
  14. finalintN=list.size();
  15. for(inti=0;i<N;i++){
  16. finalApplicationInfoitem=list.get(i);
  17. intindex=Collections.binarySearch(mAllAppsList,item,
  18. LauncherModel.APP_NAME_COMPARATOR);
  19. if(index<0){
  20. index=-(index+1);
  21. }
  22. mAllAppsList.add(index,item);
  23. }
  24. mAppsAdapter.notifyDataSetChanged();
  25. }
  26. ......
  27. }

函数setApps首先清空mAllAppsList列表,然后调用addApps函数来为上一步得到的每一个应用程序创建一个ApplicationInfo实例了,有了这些ApplicationInfo实例之后,就可以在桌面上展示系统中所有的应用程序了。

到了这里,系统默认的Home应用程序Launcher就把PackageManagerService中的应用程序加载进来了,当我们在屏幕上点击下面这个图标时,就会把刚才加载好的应用程序以图标的形式展示出来了:

点击这个按钮时,便会响应Launcher.onClick函数:

  1. publicfinalclassLauncherextendsActivity
  2. implementsView.OnClickListener,OnLongClickListener,LauncherModel.Callbacks,AllAppsView.Watcher{
  3. ......
  4. publicvoidonClick(Viewv){
  5. Objecttag=v.getTag();
  6. if(taginstanceofShortcutInfo){
  7. ......
  8. }elseif(taginstanceofFolderInfo){
  9. ......
  10. }elseif(v==mHandleView){
  11. if(isAllAppsVisible()){
  12. ......
  13. }else{
  14. showAllApps(true);
  15. }
  16. }
  17. }
  18. ......
  19. }

接着就会调用showAllApps函数显示应用程序图标:

  1. publicfinalclassLauncherextendsActivity
  2. implementsView.OnClickListener,OnLongClickListener,LauncherModel.Callbacks,AllAppsView.Watcher{
  3. ......
  4. voidshowAllApps(booleananimated){
  5. mAllAppsGrid.zoom(1.0f,animated);
  6. ((View)mAllAppsGrid).setFocusable(true);
  7. ((View)mAllAppsGrid).requestFocus();
  8. //TODO:fadethesetwotoo
  9. mDeleteZone.setVisibility(View.GONE);
  10. }
  11. ......
  12. }

这样我们就可以看到系统中的应用程序了:



当点击上面的这些应用程序图标时,便会响应AllApps2D.onItemClick函数:

  1. publicclassAllApps2D
  2. extendsRelativeLayout
  3. implementsAllAppsView,
  4. AdapterView.OnItemClickListener,
  5. AdapterView.OnItemLongClickListener,
  6. View.OnKeyListener,
  7. DragSource{
  8. ......
  9. publicvoidonItemClick(AdapterViewparent,Viewv,intposition,longid){
  10. ApplicationInfoapp=(ApplicationInfo)parent.getItemAtPosition(position);
  11. mLauncher.startActivitySafely(app.intent,app);
  12. }
  13. ......
  14. }

这里的成员变量mLauncher的类型为Launcher,于是就调用Launcher.startActivitySafely函数来启动应用程序了。

七 Launcher widget添加过程

Android中的AppWidget与google widget和中移动的widget并不是一个概念,这里的AppWidget只是把一个进程的控件嵌入到别外一个进程的窗口里的一种方法。View在另外一个进程里显示,但事件的处理方法还是在原来的进程里。这有点像 X Window中的嵌入式窗口。

首先我们需要了解RemoteViews, AppWidgetHost, AppWidgetHostView等概念

RemoteViews:并不是一个真正的View,它没有实现View的接口,而只是一个用于描述View的实体。比如:创建View需要的资源ID和各个控件的事件响应方法。RemoteViews会通过进程间通信机制传递给AppWidgetHost。

AppWidgetHost

AppWidgetHost是真正容纳AppWidget的地方,它的主要功能有两个:

o 监听来自AppWidgetService的事件:

class Callbacks extends IAppWidgetHost.Stub{publicvoidupdateAppWidget(intappWidgetId,RemoteViews views){Message msg=mHandler.obtainMessage(HANDLE_UPDATE);msg.arg1=appWidgetId;msg.obj=views;msg.sendToTarget();} publicvoidproviderChanged(intappWidgetId,AppWidgetProviderInfo info){Message msg=mHandler.obtainMessage(HANDLE_PROVIDER_CHANGED);msg.arg1=appWidgetId;msg.obj=info;msg.sendToTarget();}}

这是主要处理update和provider_changed两个事件,根据这两个事件更新widget。

class UpdateHandler extends Handler{public UpdateHandler(Looper looper){super(looper);} publicvoidhandleMessage(Message msg){switch(msg.what){caseHANDLE_UPDATE:{updateAppWidgetView(msg.arg1,(RemoteViews)msg.obj);break;}caseHANDLE_PROVIDER_CHANGED:{onProviderChanged(msg.arg1,(AppWidgetProviderInfo)msg.obj);break;}}}}

o 另外一个功能就是创建AppWidgetHostView。前面我们说过RemoteViews不是真正的View,只是View的描述,而AppWidgetHostView才是真正的View。这里先创建AppWidgetHostView,然后通过AppWidgetService查询appWidgetId对应的RemoteViews,最后把RemoteViews传递给AppWidgetHostView去updateAppWidget。

public final AppWidgetHostView createView(Context context,intappWidgetId,AppWidgetProviderInfo appWidget){AppWidgetHostView view=onCreateView(context,appWidgetId,appWidget);view.setAppWidget(appWidgetId,appWidget);synchronized(mViews){mViews.put(appWidgetId,view);}RemoteViews views=null;try{views=sService.getAppWidgetViews(appWidgetId);}catch(RemoteException e){throw new RuntimeException("system server dead?",e);}view.updateAppWidget(views);returnview;}

AppWidgetHostView

AppWidgetHostView是真正的View,但它只是一个容器,用来容纳实际的AppWidget的View。这个AppWidget的View是根据RemoteViews的描述来创建。这是在updateAppWidget里做的:

publicvoidupdateAppWidget(RemoteViews remoteViews){...if(content==null&&layoutId==mLayoutId){try{remoteViews.reapply(mContext,mView);content=mView;recycled=true;if(LOGD)Log.d(TAG,"was able to recycled existing layout");}catch(RuntimeException e){exception=e;}}// Try normal RemoteView inflationif(content==null){try{content=remoteViews.apply(mContext,this);if(LOGD)Log.d(TAG,"had to inflate new layout");}catch(RuntimeException e){exception=e;}}...if(!recycled){prepareView(content);addView(content);}if(mView!=content){removeView(mView);mView=content;}...}

remoteViews.apply创建了实际的View,下面代码可以看出:

public View apply(Context context,ViewGroup parent){View result=null; Context c=prepareContext(context); Resources r=c.getResources();LayoutInflater inflater=(LayoutInflater)c .getSystemService(Context.LAYOUT_INFLATER_SERVICE); inflater=inflater.cloneInContext(c);inflater.setFilter(this); result=inflater.inflate(mLayoutId,parent,false); performApply(result);returnresult;}

Host的实现者

AppWidgetHost和AppWidgetHostView是在框架中定义的两个基类。应用程序可以利用这两个类来实现自己的Host。Launcher是缺省的桌面,它是一个Host的实现者。

LauncherAppWidgetHostView扩展了AppWidgetHostView,实现了对长按事件的处理。

LauncherAppWidgetHost扩展了AppWidgetHost,这里只是重载了onCreateView,创建LauncherAppWidgetHostView的实例。

AppWidgetService

AppWidgetService存在的目的主要是解开AppWidgetProvider和AppWidgetHost之间的耦合。如果AppWidgetProvider和AppWidgetHost的关系固定死了,AppWidget就无法在任意进程里显示了。而有了AppWidgetService,AppWidgetProvider根本不需要知道自己的AppWidget在哪里显示了。

LauncherAppWidgetHostView: 扩展了AppWidgetHostView,实现了对长按事件的处理

LauncherAppWidgetHost: 扩展了AppWidgetHost,这里只是重载了onCreateView,创建LauncherAppWidgetHostView的实例

24/**
25*Specific{@linkAppWidgetHost}thatcreatesour{@linkLauncherAppWidgetHostView}
26*whichcorrectlycapturesalllong-pressevents.Thisensuresthatuserscan
27*alwayspickupandmovewidgets.
28*/
29publicclassLauncherAppWidgetHostextendsAppWidgetHost{
30publicLauncherAppWidgetHost(Contextcontext,inthostId){
31super(context,hostId);
32}
33
34@Override
35protectedAppWidgetHostViewonCreateView(Contextcontext,intappWidgetId,
36AppWidgetProviderInfoappWidget){
37returnnewLauncherAppWidgetHostView(context);
38}
39}

首先在Launcher.java中定义了如下两个变量

174privateAppWidgetManagermAppWidgetManager;
175privateLauncherAppWidgetHostmAppWidgetHost;

在onCreate函数中初始化,

224mAppWidgetManager=AppWidgetManager.getInstance(this);
225mAppWidgetHost=newLauncherAppWidgetHost(this,APPWIDGET_HOST_ID);
226mAppWidgetHost.startListening();
上述代码,获取mAppWidgetManager的实例,并创建LauncherAppWidgetHost,以及监听
AppWidgetManager只是应用程序与底层Service之间的一个桥梁,是Android中标准的aidl实现方式
应用程序通过AppWidgetManager调用Service中的方法
35/**
36*UpdatesAppWidgetstate;getsinformationaboutinstalledAppWidgetprovidersandother
37*AppWidgetrelatedstate.
38*/
39publicclassAppWidgetManager{
197staticWeakHashMap<Context,WeakReference<AppWidgetManager>>sManagerCache=newWeakHashMap();
198staticIAppWidgetServicesService;
204/**
205*GettheAppWidgetManagerinstancetouseforthesupplied{@linkandroid.content.Context
206*Context}object.
207*/
208publicstaticAppWidgetManagergetInstance(Contextcontext){
209synchronized(sManagerCache){
210if(sService==null){
211IBinderb=ServiceManager.getService(Context.APPWIDGET_SERVICE);
212sService=IAppWidgetService.Stub.asInterface(b);
213}
215WeakReference<AppWidgetManager>ref=sManagerCache.get(context);
216AppWidgetManagerresult=null;
217if(ref!=null){
218result=ref.get();
219}
220if(result==null){
221result=newAppWidgetManager(context);
222sManagerCache.put(context,newWeakReference(result));
223}
224returnresult;
225}
226}
228privateAppWidgetManager(Contextcontext){
229mContext=context;
230mDisplayMetrics=context.getResources().getDisplayMetrics();
231}

以上代码是设计模式中标准的单例模式

frameworks/base/core/java/android/appwidget/AppWidgetHost.java

90publicAppWidgetHost(Contextcontext,inthostId){
91mContext=context;
92mHostId=hostId;
93mHandler=newUpdateHandler(context.getMainLooper());
94synchronized(sServiceLock){
95if(sService==null){
96IBinderb=ServiceManager.getService(Context.APPWIDGET_SERVICE);
97sService=IAppWidgetService.Stub.asInterface(b);
98}
99}
100}

可以看到AppWidgetHost有自己的HostId,Handler,和sService

93mHandler=newUpdateHandler(context.getMainLooper());

这是啥用法呢?

参数为Looper,即消息处理放到此Looper的MessageQueue中,有哪些消息呢?

40staticfinalintHANDLE_UPDATE=1;
41staticfinalintHANDLE_PROVIDER_CHANGED=2;
49classCallbacksextendsIAppWidgetHost.Stub{
50publicvoidupdateAppWidget(intappWidgetId,RemoteViewsviews){
51Messagemsg=mHandler.obtainMessage(HANDLE_UPDATE);
52msg.arg1=appWidgetId;
53msg.obj=views;
54msg.sendToTarget();
55}
57publicvoidproviderChanged(intappWidgetId,AppWidgetProviderInfoinfo){
58Messagemsg=mHandler.obtainMessage(HANDLE_PROVIDER_CHANGED);
59msg.arg1=appWidgetId;
60msg.obj=info;
61msg.sendToTarget();
62}
63}
65classUpdateHandlerextendsHandler{
66publicUpdateHandler(Looperlooper){
67super(looper);
68}
69
70publicvoidhandleMessage(Messagemsg){
71switch(msg.what){
72caseHANDLE_UPDATE:{
73updateAppWidgetView(msg.arg1,(RemoteViews)msg.obj);
74break;
75}
76caseHANDLE_PROVIDER_CHANGED:{
77onProviderChanged(msg.arg1,(AppWidgetProviderInfo)msg.obj);
78break;
79}
80}
81}
82}

通过以上可以看到主要有两中类型的消息,HANDLE_UPDATE和HANDLE_PROVIDER_CHANGED

处理即通过自身定义的方法

231/**
232*CalledwhentheAppWidgetproviderforaAppWidgethasbeenupgradedtoanewapk.
233*/
234protectedvoidonProviderChanged(intappWidgetId,AppWidgetProviderInfoappWidget){
235AppWidgetHostViewv;
236synchronized(mViews){
237v=mViews.get(appWidgetId);
238}
239if(v!=null){
240v.updateAppWidget(null,AppWidgetHostView.UPDATE_FLAGS_RESET);
241}
242}
244voidupdateAppWidgetView(intappWidgetId,RemoteViewsviews){
245AppWidgetHostViewv;
246synchronized(mViews){
247v=mViews.get(appWidgetId);
248}
249if(v!=null){
250v.updateAppWidget(views,0);
251}
252}

那么此消息是何时由谁发送的呢?

从以上的代码中看到AppWidgetHost定义了内部类Callback,扩展了类IAppWidgetHost.Stub,类Callback中负责发送以上消息

Launcher中会调用本类中的如下方法,

102/**
103*StartreceivingonAppWidgetChangedcallsforyourAppWidgets.Callthiswhenyouractivity
104*becomesvisible,i.e.fromonStart()inyourActivity.
105*/
106publicvoidstartListening(){
107int[]updatedIds;
108ArrayList<RemoteViews>updatedViews=newArrayList<RemoteViews>();
109
110try{
111if(mPackageName==null){
112mPackageName=mContext.getPackageName();
113}
114updatedIds=sService.startListening(mCallbacks,mPackageName,mHostId,updatedViews);
115}
116catch(RemoteExceptione){
117thrownewRuntimeException("systemserverdead?",e);
118}
120finalintN=updatedIds.length;
121for(inti=0;i<N;i++){
122updateAppWidgetView(updatedIds[i],updatedViews.get(i));
123}
124}
最终调用AppWidgetService中的方法startListening方法,并把mCallbacks传过去,由Service负责发送消息
Launcher中添加Widget
在Launcher中添加widget,有两种途径,通过Menu或者长按桌面的空白区域,都会弹出Dialog,让用户选择添加
如下代码是当用户选择
1999/**
2000*Handletheactionclickedinthe"Addtohome"dialog.
2001*/
2002publicvoidonClick(DialogInterfacedialog,intwhich){
2003Resourcesres=getResources();
2004cleanup();
2006switch(which){
2007caseAddAdapter.ITEM_SHORTCUT:{
2008//Insertextraitemtohandlepickingapplication
2009pickShortcut();
2010break;
2013caseAddAdapter.ITEM_APPWIDGET:{
2014intappWidgetId=Launcher.this.mAppWidgetHost.allocateAppWidgetId();
2016IntentpickIntent=newIntent(AppWidgetManager.ACTION_APPWIDGET_PICK);
2017pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,appWidgetId);
2018//startthepickactivity
2019startActivityForResult(pickIntent,REQUEST_PICK_APPWIDGET);
2020break;

当用户在Dialog中选择AddAdapter.ITEM_APPWIDGET时,首先会通过AppWidgethost分配一个appWidgetId,并最终调到AppWidgetService中去

同时发送Intent,其中保存有刚刚分配的appWidgetId,AppWidgetManager.EXTRA_APPWIDGET_ID

139/**
140*GetaappWidgetIdforahostinthecallingprocess.
141*
142*@returnaappWidgetId
143*/
144publicintallocateAppWidgetId(){
145try{
146if(mPackageName==null){
147mPackageName=mContext.getPackageName();
148}
149returnsService.allocateAppWidgetId(mPackageName,mHostId);
150}
151catch(RemoteExceptione){
152thrownewRuntimeException("systemserverdead?",e);
153}
154}
2016IntentpickIntent=newIntent(AppWidgetManager.ACTION_APPWIDGET_PICK);
2017pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,appWidgetId);
2018//startthepickactivity
2019startActivityForResult(pickIntent,REQUEST_PICK_APPWIDGET);
这段代码之后,代码将会怎么执行呢,根据Log信息,可以看到代码将会执行到Setting应用中
此类将会通过AppWidgetService获取到当前系统已经安装的Widget,并显示出来
78/**
79*Createlistentriesforanycustomwidgetsrequestedthrough
80*{@linkAppWidgetManager#EXTRA_CUSTOM_INFO}.
81*/
82voidputCustomAppWidgets(List<PickAdapter.Item>items){
83finalBundleextras=getIntent().getExtras();
84
85//getandvalidatetheextrastheygaveus
86ArrayList<AppWidgetProviderInfo>customInfo=null;
87ArrayList<Bundle>customExtras=null;
88try_custom_items:{
89customInfo=extras.getParcelableArrayList(AppWidgetManager.EXTRA_CUSTOM_INFO);
90if(customInfo==null||customInfo.size()==0){
91Log.i(TAG,"EXTRA_CUSTOM_INFOnotpresent.");
92breaktry_custom_items;
93}
95intcustomInfoSize=customInfo.size();
96for(inti=0;i<customInfoSize;i++){
97Parcelablep=customInfo.get(i);
98if(p==null||!(pinstanceofAppWidgetProviderInfo)){
99customInfo=null;
100Log.e(TAG,"errorusingEXTRA_CUSTOM_INFOindex="+i);
101breaktry_custom_items;
102}
103}
105customExtras=extras.getParcelableArrayList(AppWidgetManager.EXTRA_CUSTOM_EXTRAS);
106if(customExtras==null){
107customInfo=null;
108Log.e(TAG,"EXTRA_CUSTOM_INFOwithoutEXTRA_CUSTOM_EXTRAS");
109breaktry_custom_items;
110}
112intcustomExtrasSize=customExtras.size();
113if(customInfoSize!=customExtrasSize){
114Log.e(TAG,"listsizemismatch:EXTRA_CUSTOM_INFO:"+customInfoSize
115+"EXTRA_CUSTOM_EXTRAS:"+customExtrasSize);
116breaktry_custom_items;
117}
120for(inti=0;i<customExtrasSize;i++){
121Parcelablep=customExtras.get(i);
122if(p==null||!(pinstanceofBundle)){
123customInfo=null;
124customExtras=null;
125Log.e(TAG,"errorusingEXTRA_CUSTOM_EXTRASindex="+i);
126breaktry_custom_items;
127}
128}
129}
131if(LOGD)Log.d(TAG,"Using"+customInfo.size()+"customitems");
132putAppWidgetItems(customInfo,customExtras,items);
133}
从上述代码中可以看到,可以放置用户自己定义的伪Widget
关于伪widget,个人有如下想法:
早期Android版本中的Google Search Bar就属于伪Widget,其实就是把widget做到Launcher中,但是用户体验与真widget并没有区别,个人猜想HTC的sense就是这样实现的。
优点:是不需要进程间的通信,效率将会更高,并且也可以规避点Widget开发的种种限制
缺点:导致Launcher代码庞大,不易于维护
用户选择完之后,代码如下
135/**
136*{@inheritDoc}
137*/
138@Override
139publicvoidonClick(DialogInterfacedialog,intwhich){
140Intentintent=getIntentForPosition(which);
141
142intresult;
143if(intent.getExtras()!=null){
144//Ifthereareanyextras,it'sbecausethisentryiscustom.
145//Don'ttrytobindit,justpassitbacktotheapp.
146setResultData(RESULT_OK,intent);
147}else{
148try{
149mAppWidgetManager.bindAppWidgetId(mAppWidgetId,intent.getComponent());
150result=RESULT_OK;
151}catch(IllegalArgumentExceptione){
152//Thisisthrownifthey'realreadybound,orotherwisesomehow
153//bogus.Settheresulttocanceled,andexit.Theapp*should*
154//cleanupatthispoint.Wecouldpasstheerroralong,but
155//it'snotclearthatthat'suseful--thewidgetwillsimplynot
156//appear.
157result=RESULT_CANCELED;
158}
159setResultData(result,null);
160}
161finish();
162}
将会149mAppWidgetManager.bindAppWidgetId(mAppWidgetId,intent.getComponent());
如果此次添加的Widget是intent.getComponent()的第一个实例,将会发送如下广播
171/**
172*SentwhenaninstanceofanAppWidgetisaddedtoahostforthefirsttime.
173*ThisbroadcastissentatboottimeifthereisaAppWidgetHostinstalledwith
174*aninstanceforthisprovider.
175*
176*@seeAppWidgetProvider#onEnabledAppWidgetProvider.onEnabled(Contextcontext)
177*/
178publicstaticfinalStringACTION_APPWIDGET_ENABLED="android.appwidget.action.APPWIDGET_ENABLED";
紧接着会发送UPDATE广播
135/**
136*SentwhenitistimetoupdateyourAppWidget.
137*
138*<p>ThismaybesentinresponsetoanewinstanceforthisAppWidgetproviderhaving
139*beeninstantiated,therequested{@linkAppWidgetProviderInfo#updatePeriodMillisupdateinterval}
140*havinglapsed,orthesystembooting.
141*
142*<p>
143*Theintentwillcontainthefollowingextras:
144*<table>
145*<tr>
146*<td>{@link#EXTRA_APPWIDGET_IDS}</td>
147*<td>TheappWidgetIdstoupdate.ThismaybealloftheAppWidgetscreatedforthis
148*provider,orjustasubset.ThesystemtriestosendupdatesforasfewAppWidget
149*instancesaspossible.</td>
150*</tr>
151*</table>
152*
153*@seeAppWidgetProvider#onUpdateAppWidgetProvider.onUpdate(Contextcontext,AppWidgetManagerappWidgetManager,int[]appWidgetIds)
154*/
155publicstaticfinalStringACTION_APPWIDGET_UPDATE="android.appwidget.action.APPWIDGET_UPDATE";

待用户选择完要添加的widget之后,将会回到Launcher.java中的函数onActivityResult中

538caseREQUEST_PICK_APPWIDGET:
539addAppWidget(data);
540break;

上述addAppWidget中做了哪些事情呢?

1174voidaddAppWidget(Intentdata){
1175//TODO:catchbadwidgetexceptionwhensent
1176intappWidgetId=data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,-1);
1177AppWidgetProviderInfoappWidget=mAppWidgetManager.getAppWidgetInfo(appWidgetId);
1179if(appWidget.configure!=null){
1180//Launchovertoconfigurewidget,ifneeded
1181Intentintent=newIntent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
1182intent.setComponent(appWidget.configure);
1183intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,appWidgetId);
1185startActivityForResultSafely(intent,REQUEST_CREATE_APPWIDGET);
1186}else{
1187//Otherwisejustaddit
1188onActivityResult(REQUEST_CREATE_APPWIDGET,Activity.RESULT_OK,data);

首先获取appWidgetId,再通过AppWidgetManager获取AppWidgetProviderInfo,最后判断此Widget是否存在ConfigActivity,如果存在则启动ConfigActivity,否则直接调用函数onActivityResult

541caseREQUEST_CREATE_APPWIDGET:
542completeAddAppWidget(data,mAddItemCellInfo);
543break;

通过函数completeAddAppWidget把此widget的信息插入到数据库中,并添加到桌面上

873/**
874*Addawidgettotheworkspace.
875*
876*@paramdataTheintentdescribingtheappWidgetId.
877*@paramcellInfoThepositiononscreenwheretocreatethewidget.
878*/
879privatevoidcompleteAddAppWidget(Intentdata,CellLayout.CellInfocellInfo){
880Bundleextras=data.getExtras();
881intappWidgetId=extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID,-1);
883if(LOGD)Log.d(TAG,"dumpingextrascontent="+extras.toString());
885AppWidgetProviderInfoappWidgetInfo=mAppWidgetManager.getAppWidgetInfo(appWidgetId);
887//Calculatethegridspansneededtofitthiswidget
888CellLayoutlayout=(CellLayout)mWorkspace.getChildAt(cellInfo.screen);
889int[]spans=layout.rectToCell(appWidgetInfo.minWidth,appWidgetInfo.minHeight);
891//TryfindingopenspaceonLauncherscreen
892finalint[]xy=mCellCoordinates;
893if(!findSlot(cellInfo,xy,spans[0],spans[1])){
894if(appWidgetId!=-1)mAppWidgetHost.deleteAppWidgetId(appWidgetId);
895return;
896}
898//BuildLauncher-specificwidgetinfoandsavetodatabase
899LauncherAppWidgetInfolauncherInfo=newLauncherAppWidgetInfo(appWidgetId);
900launcherInfo.spanX=spans[0];
901launcherInfo.spanY=spans[1];
903LauncherModel.addItemToDatabase(this,launcherInfo,
904LauncherSettings.Favorites.CONTAINER_DESKTOP,
905mWorkspace.getCurrentScreen(),xy[0],xy[1],false);
907if(!mRestoring){
908mDesktopItems.add(launcherInfo);
910//Performactualinflationbecausewe'relive
911launcherInfo.hostView=mAppWidgetHost.createView(this,appWidgetId,appWidgetInfo);
913launcherInfo.hostView.setAppWidget(appWidgetId,appWidgetInfo);
914launcherInfo.hostView.setTag(launcherInfo);
916mWorkspace.addInCurrentScreen(launcherInfo.hostView,xy[0],xy[1],
917launcherInfo.spanX,launcherInfo.spanY,isWorkspaceLocked());
918}
919}

Launcher中删除widget

长按一个widget,并拖入到DeleteZone中可实现删除

具体代码在DeleteZone中

92publicvoidonDrop(DragSourcesource,intx,inty,intxOffset,intyOffset,
93DragViewdragView,ObjectdragInfo){
94finalItemInfoitem=(ItemInfo)dragInfo;
96if(item.container==-1)return;
98if(item.container==LauncherSettings.Favorites.CONTAINER_DESKTOP){
99if(iteminstanceofLauncherAppWidgetInfo){
100mLauncher.removeAppWidget((LauncherAppWidgetInfo)item);
101}
102}else{
103if(sourceinstanceofUserFolder){
104finalUserFolderuserFolder=(UserFolder)source;
105finalUserFolderInfouserFolderInfo=(UserFolderInfo)userFolder.getInfo();
106//ItemmustbeaShortcutInfootherwiseitcouldn'thavebeeninthefolder
107//inthefirstplace.
108userFolderInfo.remove((ShortcutInfo)item);
109}
110}
111if(iteminstanceofUserFolderInfo){
112finalUserFolderInfouserFolderInfo=(UserFolderInfo)item;
113LauncherModel.deleteUserFolderContentsFromDatabase(mLauncher,userFolderInfo);
114mLauncher.removeFolder(userFolderInfo);
115}elseif(iteminstanceofLauncherAppWidgetInfo){
116finalLauncherAppWidgetInfolauncherAppWidgetInfo=(LauncherAppWidgetInfo)item;
117finalLauncherAppWidgetHostappWidgetHost=mLauncher.getAppWidgetHost();
118if(appWidgetHost!=null){
119appWidgetHost.deleteAppWidgetId(launcherAppWidgetInfo.appWidgetId);
120}
121}
122LauncherModel.deleteItemFromDatabase(mLauncher,item);
123}

删除时,判断删除的类型是否是AppWidget,如果是的话,要通过AppWidgetHost,删除AppWidetId,并最终从数据库中删除。

八 Launcher celllayout介绍

(1) 大家都知道workspace是有celllayout组成

Celllayout被划分为了4行4列的表格,用Boolean类型的mOccupied二维数组来标记每个cell是否被占用。在attrs.xml中定义了shortAxisCells和longAxisCells分别存储x轴和y轴方向的cell个数。在Celllayout构造函数中初始化。

(2) 内部类CellInfo为静态类,实现了ContextMenu.ContextMenuInfo接口,其对象用于存储cell的基本信息

VacantCell类用于存储空闲的cell,用到了同步机制用于管理对空闲位置的操作。所有的空cell都存储在vacantCells中。

cellX和cellY用于记录cell的位置,起始位0。如:(0,0) (0,1),每一页从新开始编号。

clearVacantCells作用是将Vacant清空:具体是释放每个cell,将list清空。

findVacantCellsFromOccupied从存放cell的数值中找到空闲的cell。在Launcher.Java中的restoreState方法中调用。

(3) mPortrait用于标记是横屏还是竖屏,FALSE表示竖屏,默认为FALSE。

(4)修改CellLayout页面上cell的布局:


CellLayout页面上默认的cell为4X4=16个,可以通过修改配置文件来达到修改目的。

在CellLayout.Java类的CellLayout(Context context, AttributeSet attrs, int defStyle)构造方法中用变量mShortAxisCells和mLongAxisCells存储行和列。

其值是在自定义配置文件attrs.xml中定义的,并在workspace_screen.xml中赋初值的,初值都为4,即4行、4列。可以在workspace_screen.xml修改对应的值。

注意:CellLayout构造方法中从attrs.xml中获取定义是这样的:mShortAxisCells = a.getInt(R.styleable.CellLayout_shortAxisCells, 4);当workspace_screen.xml中没有给定义的变量赋值时,上面的4就起作用。

(5)Launcher(主屏/待机) App的BUG: 没有初始化定义CellLayout中屏幕方向的布尔值参数:

  1. Launcher App:\cupcake\packages\apps\Launcher

待机画面分为多层,桌面Desktop Items在\res\layout-*\workspace_screen.xml中置:

  1. <com.android.launcher.CellLayout
  2. ... ...
  3. launcher:shortAxisCells="4"
  4. launcher:longAxisCells="4"
  5. ... ...
  6. />

以上表示4行4列.

再看看com.android.launcher.CellLayout ,其中有定义屏幕方向的参数:

  1. private boolean mPortrait;

但是一直没有初始化,也就是mPortrait=false,桌面的单元格设置一直是以非竖屏(横屏)的设置定义进行初始化。

再来看看横屏和竖屏情况下的初始化不同之处,就可以看出BUG了:

  1. boolean[][] mOccupied;//二元单元格布尔值数组
  2. if (mPortrait) {
  3. mOccupied = new boolean[mShortAxisCells][mLongAxisCells];
  4. } else {
  5. mOccupied = new boolean[mLongAxisCells][mShortAxisCells];
  6. }

如果我们满屏显示桌面(横向和纵向的单元格数不一致),而不是默认的只显示4行4列,则mShortAxisCells = 4, mLongAxisCells = 5,数组应该初始化是:new boolean[4][5],但是实际是按照非竖屏处理,初始化成了new boolean[5][4],会产生数组越界异常。

可以在构造函数中,添加通过屏幕方向初始化mPortrait,代码如下:

  1. public CellLayout(Context context, AttributeSet attrs, int defStyle)
  2. {
  3. super(context, attrs, defStyle);
  4. mPortrait = this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;// 新增代码
  5. ... ...

好了就写这些,太累了,以后再补上其他跟Launcher有关的

分享到:
评论

相关推荐

    前18大旋转修整器企业占据全球87%的市场份额.docx

    前18大旋转修整器企业占据全球87%的市场份额

    Planet-SkySat-Imagery-Product-Specification-Jan2020.pdf

    SKYSAT IMAGERY PRODUCT SPECIFICATION PLANET.COM VIDEO Full motion videos are collected between 30 and 120 seconds by a single camera from any of the active SkySats. Videos are collected using only the Panchromatic half of the camera, hence all videos are PAN only. Videos are packaged and delivered with a video mpeg-4 file, plus all image frames with accompanying video metadata and a frame index file (reference Product Types below)

    Screenshot_20240506_133458_com.netease.yhtj.vivo.jpg

    Screenshot_20240506_133458_com.netease.yhtj.vivo.jpg

    2019年A~F题特等奖论文合集.pdf

    大学生,数学建模,美国大学生数学建模竞赛,MCM/ICM,历年美赛特等奖O奖论文

    雷达物位变送器安装和操作手册

    雷达物位变送器安装和操作手册

    node-v11.6.0-linux-armv7l.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    Python3实现快速排序(源代码)

    快速排序是一种基于分治策略的排序算法,通过选择一个基准元素,将待排序的数组划分为两个子数组,一个包含所有小于基准的元素,另一个包含所有大于基准的元素,然后递归地对这两个子数组进行快速排序。快速排序在平均情况下具有O(n log n)的时间复杂度,是一种非常高效的排序算法。然而,在最坏情况下,当输入数据已经有序或接近有序时,快速排序的性能会退化为O(n^2)。此外,快速排序是不稳定的排序算法,即相等的元素可能在排序过程中改变相对位置。尽管如此,快速排序仍然因其高效的平均性能而在实际应用中广泛使用。在Python3中,可以通过递归或迭代的方式实现快速排序算法,但为了避免额外的空间开销,通常会采用原地排序的方式来实现。

    毕业课设基于51单片机的出租车计价器(昼夜)

    【作品名称】:基于51单片机的出租车计价器(昼夜) 含(程序、仿真图、流程图、原理图) 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 出租车计价器: 1、不同情况具有不同的收费标准,具有白天和夜晚不同的计价能力 2、能进行手动修改单价 3、具有数据的复位功能(起步价,起步公里数,里程单价,白天晚上不一样) 4、能够在掉电的情况下存储单价等数据 5、步进电机模拟里程,一圈表示一里路

    2024年中国API 11P往复式气体压缩机行业研究报告.docx

    2024年中国API 11P往复式气体压缩机行业研究报告

    Windows 10系统上安装和配置Tomcat的步骤

    附件是Windows 10系统上安装和配置Tomcat的步骤,文件绿色安全,请大家放心下载,仅供交流学习使用,无任何商业目的!

    广东工业大学《计算网络A》实验报告期末考试试题回忆版.doc

    此试题是考试后回忆版本,你会发现是惊喜。恭喜你考个好成绩。

    数据库+人大金仓+Linux系统安装

    数据库+人大金仓+Linux系统安装

    2023年美赛特等奖论文-C-2309397-解密.pdf

    大学生,数学建模,美国大学生数学建模竞赛,MCM/ICM,2023年美赛特等奖O奖论文

    opencv-python-4.5.4.60-cp36-cp36m-win-amd64.whl

    opencv-python-4.5.4.60-cp36-cp36m-win-amd64.whl

    减肥管理,全球前10强生产商排名及市场份额.docx

    减肥管理,全球前10强生产商排名及市场份额

    上海大学大学生创新创业训练计划申请书(创新训练项目).doc

    内容概要:《上海大学大学生创新创业训练计划申请书(创新训练项目)》是用于申请参加上海大学的大学生创新创业训练计划的申请书,旨在帮助学生提出创新项目计划,获得培训和支持,促进学生创新创业能力的提升。 适用人群:适合上海大学的在校大学生,特别是对创新创业感兴趣、有创新想法和创业计划的学生,希望通过该计划获得指导和资源支持,实现自己的创业梦想。 使用场景及目标:申请书的使用场景是为了参加上海大学的大学生创新创业训练计划,目标是通过提交详细的创新项目计划,获得评审通过并获得培训、指导和资金支持,从而推动学生的创新创业实践和能力提升。 其他说明:申请书应包括清晰的创新项目描述、项目可行性分析、预期目标和计划、团队介绍等内容,以展现学生的创新能力和项目潜力。申请书的撰写需要认真准备,体现出学生对创新创业的热情和才华,以提高申请成功的机会。

    IEC 60364-7-716-2023 第7-716部分:特殊装置或场所要求.信息和通信技术ICT电缆基础设施上ELV直流配电

    IEC 60364-7-716-2023 低压电气装置.第7-716部分:特殊装置或场所的要求.信息和通信技术(ICT)电缆基础设施上的ELV直流配电.pdf

    IEC PAS 61851-1-1 2023 电动汽车导电充电系统.第1-1部分:使用4型车辆耦合器电动汽车导电带电系统特殊要求

    IEC PAS 61851-1-1 2023 电动汽车导电充电系统.第1-1部分:使用4型车辆耦合器的电动汽车导电带电系统的特殊要求.pdf

    前11大客运渡轮服务企业占据全球30.3%的市场份额.docx

    前11大客运渡轮服务企业占据全球30.3%的市场份额

    wsl+MCgpu安装记录

    wsl+MCgpu安装记录

Global site tag (gtag.js) - Google Analytics