【转】android SystemUI 流程分析

android4 SystemUI 流程分析

什么是SystemUI?

对于Phone来说SystemUI指的是:StatusBar(状态栏)、NavigationBar(导航栏)。而对于Tablet或者是TV来说SystemUI指的是:
CombinedBar(包括了StatusBar和NavigationBar)。

启动后Phone界面上的信号,蓝牙标志,Wifi标志等等这些状态显示标志都会在StatusBar上显示。当我们的设备开机后,首先
需要给用户呈现的就是各种界面同时也包括了我们的SystemUI,因此对于整个Android系统来说,SystemUI都有举足轻重的作用。

现在就从代码开始一步步的分析

1、启动流程

代码路径:fameworks/base/packages/SystemUI

建立工程导入到eclipse中代码具体图示:

先从 AndroidManifest.xml 看看有哪些东东,以前说过Android中有四大组件,这里就有如下的三大部分:

系统服务 Service :

SystemUIService
TakeScreenshotService
LoadAverageService

广播接收器 BroadcastReceive:

BootReceiver

Activity 应用:

USB的挺多哟...
UsbStorageActivity
UsbConfirmActivity
UsbPermissionActivity
UsbStorageActivity
UsbAccessoryUriActivity

NetworkOverLimitActivity

<!-- started from ... somewhere -->
Nyandroid

具体定义请看 AndroidManifest.xml 文件,上面只是简单的列一下

先看第一个Activity -- Nyandroid 这里做了什么呢?
就是网上传说中的 好多安卓机器人飞过去。。。。其中代码很简单,简单说一下动画效果的代码:

[java] view plain copy

print?

  1. <span style="font-size:14px">public class FlyingCat extends ImageView {
  2. public FlyingCat(Context context, AttributeSet as) {
  3. super(context, as);
  4. setImageResource(R.drawable.nyandroid_anim); // @@@
  5. if (DEBUG) setBackgroundColor(0x80FF0000);
  6. }
  7. ...
  8. }</span>

定义在 frameworks\base\packages\SystemUI\res\drawable\nyandroid_anim.xml

[html] view plain copy

print?

  1. <span style="font-size:14px"><animation-list
  2. xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:oneshot="false">
  4. <item android:drawable="@drawable/nyandroid00" android:duration="80" />
  5. <item android:drawable="@drawable/nyandroid01" android:duration="80" />
  6. <item android:drawable="@drawable/nyandroid02" android:duration="80" />
  7. <item android:drawable="@drawable/nyandroid03" android:duration="80" />
  8. <item android:drawable="@drawable/nyandroid04" android:duration="80" />
  9. <item android:drawable="@drawable/nyandroid05" android:duration="80" />
  10. <item android:drawable="@drawable/nyandroid06" android:duration="80" />
  11. <item android:drawable="@drawable/nyandroid07" android:duration="80" />
  12. <item android:drawable="@drawable/nyandroid08" android:duration="80" />
  13. <item android:drawable="@drawable/nyandroid09" android:duration="80" />
  14. <item android:drawable="@drawable/nyandroid10" android:duration="80" />
  15. <item android:drawable="@drawable/nyandroid11" android:duration="80" />
  16. </animation-list></span>

相关图片在: frameworks\base\packages\SystemUI\res\drawable-nodpi  如图示:

然后再看最重要的服务:SystemUIService

一般来说,Service启动一般由开机广播或者StartService/BindService这几种方式来启动。既然这个Service是一个系统
服务,应该是由系统这边启动,那么看下 SystemServer.java ,果然发现如下启动代码:

[java] view plain copy

print?

  1. <span style="font-size:14px">startSystemUi(contextF);
  2. static final void startSystemUi(Context context) {
  3. Intent intent = new Intent();
  4. intent.setComponent(new ComponentName("com.android.systemui",
  5. "com.android.systemui.SystemUIService"));
  6. Slog.d(TAG, "Starting service: " + intent);
  7. context.startService(intent);
  8. }</span>

对于Android启动流程请看如下系统文章:

http://blog.csdn.net/andyhuabing/article/details/7346203  android启动--深入理解init进程
http://blog.csdn.net/andyhuabing/article/details/7349986  android启动--深入理解zygote
http://blog.csdn.net/andyhuabing/article/details/7351691  android启动--深入理解zygote (II)
http://blog.csdn.net/andyhuabing/article/details/7353910  android启动--深入理解启动HOME

那么就继续跟踪 SystemUIService 中代码:

[java] view plain copy

print?

  1. <span style="font-size:14px">/**
  2. * The class names of the stuff to start.
  3. */
  4. final Object[] SERVICES = new Object[] {
  5. 0, // system bar or status bar, filled in below.
  6. com.android.systemui.power.PowerUI.class,
  7. };
  8. @Override
  9. public void onCreate() {
  10. // Pick status bar or system bar.
  11. IWindowManager wm = IWindowManager.Stub.asInterface(
  12. ServiceManager.getService(Context.WINDOW_SERVICE));
  13. try {
  14. SERVICES[0] = wm.canStatusBarHide()
  15. ? R.string.config_statusBarComponent
  16. : R.string.config_systemBarComponent;
  17. } catch (RemoteException e) {
  18. Slog.w(TAG, "Failing checking whether status bar can hide", e);
  19. }
  20. final int N = SERVICES.length;
  21. mServices = new SystemUI[N];
  22. for (int i=0; i<N; i++) {
  23. Class cl = chooseClass(SERVICES[i]);
  24. Slog.d(TAG, "loading: " + cl);
  25. try {
  26. mServices[i] = (SystemUI)cl.newInstance();
  27. } catch (IllegalAccessException ex) {
  28. throw new RuntimeException(ex);
  29. } catch (InstantiationException ex) {
  30. throw new RuntimeException(ex);
  31. }
  32. mServices[i].mContext = this;
  33. Slog.d(TAG, "running: " + mServices[i]);
  34. mServices[i].start();
  35. }
  36. }</span>

在这代码中:

[java] view plain copy

print?

  1. <span style="font-size:14px">SERVICES[0] = wm.canStatusBarHide()
  2. ? R.string.config_statusBarComponent
  3. : R.string.config_systemBarComponent;
  4. </span>

通过AIDL获取WindowManager对象并调用 wm.canStatusBarHide() 这个代码在哪里呢?

查看: frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java

[java] view plain copy

print?

  1. <span style="font-size:14px">    public boolean canStatusBarHide() {
  2. return mStatusBarCanHide;
  3. }
  4. public void setInitialDisplaySize(int width, int height) {
  5. ...
  6. // Determine whether the status bar can hide based on the size
  7. // of the screen.  We assume sizes > 600dp are tablets where we
  8. // will use the system bar.
  9. int shortSizeDp = shortSize
  10. * DisplayMetrics.DENSITY_DEFAULT
  11. / DisplayMetrics.DENSITY_DEVICE;
  12. mStatusBarCanHide = shortSizeDp < 600;
  13. }</span>

从以上代码来看,shortSizeDp小于600dp时,则系统会认为该设备是Phone反之则认为是Tablet。
根据mStatusBarCanHide的值,设定StatusBar或者SystemBar(CombinedBar)的高度,以及是否显示NavigationBar。

2、StatusBar(状态栏)及NavigationBar(导航栏)

如果是 StatusBar 则 SERVICES[0] 存放 com.android.systemui.statusbar.phone.PhoneStatusBar 否则存放 
com.android.systemui.statusbar.tablet.TabletStatusBar 
SERVICES[1] 存放 com.android.systemui.power.PowerUI.class

从我的机器上打印来看,
E/SystemServer( 1299): Starting service: Intent { cmp=com.android.systemui/.SystemUIService }

D/SystemUIService( 1382): running: [email protected]
D/SystemUIService( 1382): running: [email protected]
I/PowerUI ( 1382): start

然后调用 mServices[i].start();那么就分析 TabletStatusBar 中的start方法吧

[java] view plain copy

print?

  1. <span style="font-size:14px">    @Override
  2. public void start() {
  3. super.start(); // will add the main bar view
  4. }</span>

调用到 frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/StatusBar.Java

[java] view plain copy

print?

  1. <span style="font-size:14px">public void start() {
  2. // First set up our views and stuff.
  3. View sb = makeStatusBarView();
  4. // Connect in to the status bar manager service
  5. StatusBarIconList iconList = new StatusBarIconList();
  6. ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>();
  7. ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>();
  8. mCommandQueue = new CommandQueue(this, iconList);
  9. mBarService = IStatusBarService.Stub.asInterface(
  10. ServiceManager.getService(Context.STATUS_BAR_SERVICE));
  11. int[] switches = new int[7];
  12. ArrayList<IBinder> binders = new ArrayList<IBinder>();
  13. try {
  14. mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications,
  15. switches, binders);
  16. } catch (RemoteException ex) {
  17. // If the system process isn‘t there we‘re doomed anyway.
  18. }
  19. disable(switches[0]);
  20. setSystemUiVisibility(switches[1]);
  21. topAppWindowChanged(switches[2] != 0);
  22. // StatusBarManagerService has a back up of IME token and it‘s restored here.
  23. setImeWindowStatus(binders.get(0), switches[3], switches[4]);
  24. setHardKeyboardStatus(switches[5] != 0, switches[6] != 0);
  25. // Set up the initial icon state
  26. int N = iconList.size();
  27. int viewIndex = 0;
  28. for (int i=0; i<N; i++) {
  29. StatusBarIcon icon = iconList.getIcon(i);
  30. if (icon != null) {
  31. addIcon(iconList.getSlot(i), i, viewIndex, icon);
  32. viewIndex++;
  33. }
  34. }
  35. // Set up the initial notification state
  36. N = notificationKeys.size();
  37. if (N == notifications.size()) {
  38. for (int i=0; i<N; i++) {
  39. addNotification(notificationKeys.get(i), notifications.get(i));
  40. }
  41. } else {
  42. Log.wtf(TAG, "Notification list length mismatch: keys=" + N
  43. + " notifications=" + notifications.size());
  44. }
  45. // Put up the view
  46. final int height = getStatusBarHeight();
  47. final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
  48. ViewGroup.LayoutParams.MATCH_PARENT,
  49. height,
  50. WindowManager.LayoutParams.TYPE_STATUS_BAR,
  51. WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
  52. | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
  53. | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
  54. PixelFormat.OPAQUE);
  55. // the status bar should be in an overlay if possible
  56. final Display defaultDisplay
  57. = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
  58. .getDefaultDisplay();
  59. if (ActivityManager.isHighEndGfx(defaultDisplay)) {
  60. lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
  61. }
  62. lp.gravity = getStatusBarGravity();
  63. lp.setTitle("StatusBar");
  64. lp.packageName = mContext.getPackageName();
  65. lp.windowAnimations = R.style.Animation_StatusBar;
  66. WindowManagerImpl.getDefault().addView(sb, lp);
  67. if (SPEW) {
  68. Slog.d(TAG, "Added status bar view: gravity=0x" + Integer.toHexString(lp.gravity)
  69. + " icons=" + iconList.size()
  70. + " disabled=0x" + Integer.toHexString(switches[0])
  71. + " lights=" + switches[1]
  72. + " menu=" + switches[2]
  73. + " imeButton=" + switches[3]
  74. );
  75. }
  76. mDoNotDisturb = new DoNotDisturb(mContext);
  77. }</span>

在这里,完成了SystemUI的整个初始化以及设置过程,并最终呈现到界面上。
启动过程中完成如下操作:
1、获取icon list,addIcon(iconList.getSlot(i), i, viewIndex, icon);
2、获取notification,addNotification(notificationKeys.get(i), notifications.get(i));
3、显示StatusBar,WindowManagerImpl.getDefault().addView(sb, lp);
   显示NavigationBar,WindowManagerImpl.getDefault().addView(
                mNavigationBarView, getNavigationBarLayoutParams());

时序图如下:

3、最近任务缩略图显示 


长按home键,列出最近启动过的任务缩略图,重要的两个类

// Recent apps
private RecentsPanelView mRecentsPanel;
private RecentTasksLoader mRecentTasksLoader;

SystemUI 获取按键事件,获取缩略图并将其显示出来,最后响应view上按键响应相应事件:

对于我们来说,关注点主要有如下几个:

1、缩略图如何获取

[java] view plain copy

print?

  1. <span style="font-size:14px">RecentsPanelView.java 中
  2. refreshRecentTasksList(recentTaskDescriptions);
  3. -->
  4. mRecentTaskDescriptions = mRecentTasksLoader.getRecentTasks();
  5. -->
  6. RecentTasksLoader.java 中
  7. // return a snapshot of the current list of recent apps
  8. ArrayList<TaskDescription> getRecentTasks() {
  9. cancelLoadingThumbnails();
  10. ArrayList<TaskDescription> tasks = new ArrayList<TaskDescription>();
  11. final PackageManager pm = mContext.getPackageManager();
  12. final ActivityManager am = (ActivityManager)
  13. mContext.getSystemService(Context.ACTIVITY_SERVICE);
  14. final List<ActivityManager.RecentTaskInfo> recentTasks =
  15. am.getRecentTasks(MAX_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE);
  16. ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
  17. .resolveActivityInfo(pm, 0);
  18. HashSet<Integer> recentTasksToKeepInCache = new HashSet<Integer>();
  19. int numTasks = recentTasks.size();
  20. // skip the first task - assume it‘s either the home screen or the current activity.
  21. final int first = 1;
  22. recentTasksToKeepInCache.add(recentTasks.get(0).persistentId);
  23. for (int i = first, index = 0; i < numTasks && (index < MAX_TASKS); ++i) {
  24. final ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(i);
  25. TaskDescription item = createTaskDescription(recentInfo.id,
  26. recentInfo.persistentId, recentInfo.baseIntent,
  27. recentInfo.origActivity, recentInfo.description, homeInfo);
  28. if (item != null) {
  29. tasks.add(item);
  30. ++index;
  31. }
  32. }
  33. // when we‘re not using the TaskDescription cache, we load the thumbnails in the
  34. // background
  35. loadThumbnailsInBackground(new ArrayList<TaskDescription>(tasks));
  36. return tasks;
  37. }
  38. </span>

这里利用 ActivityManager 中的方法:getRecentTasks 获取当前任务的列表,然后再利用 getTaskThumbnails 获取

按键View  就是几个按键相应的View

[java] view plain copy

print?

  1. <span style="font-size:14px">    public View getRecentsButton() {
  2. return mCurrentView.findViewById(R.id.recent_apps);
  3. }
  4. public View getMenuButton() {
  5. return mCurrentView.findViewById(R.id.menu);
  6. }
  7. public View getBackButton() {
  8. return mCurrentView.findViewById(R.id.back);
  9. }
  10. public View getHomeButton() {
  11. return mCurrentView.findViewById(R.id.home);
  12. }</span>

相应的应用缩略图,调用序列图如下:

2、显示缩略图
public void show(boolean show, boolean animate,
            ArrayList<TaskDescription> recentTaskDescriptions) {
        if (show) {
            // Need to update list of recent apps before we set visibility so this view‘s
            // content description is updated before it gets focus for TalkBack mode
            refreshRecentTasksList(recentTaskDescriptions);

// if there are no apps, either bring up a "No recent apps" message, or just
            // quit early
            boolean noApps = (mRecentTaskDescriptions.size() == 0);
            if (mRecentsNoApps != null) { // doesn‘t exist on large devices
                mRecentsNoApps.setVisibility(noApps ? View.VISIBLE : View.INVISIBLE);
            } else {
                if (noApps) {
                    if (DEBUG) Log.v(TAG, "Nothing to show");
                    return;
                }
            }
         }else {
            mRecentTasksLoader.cancelLoadingThumbnails();
            mRecentTasksDirty = true;
         }
         ...
}

如果 mRecentsNoApps 为空则表示没有任务,显示 "No recent apps" 否则显示应用列表
否则则显示任务的缩略图。时序图如下:

3、点击某个缩略图执行

这里分为点击某个缩略图执行程序及长按缩略图执行程序

这里直接继承了 View.OnItemClickListener 所以可以直接执行子项按键事件

[java] view plain copy

print?

  1. public class RecentsPanelView extends RelativeLayout implements OnItemClickListener, RecentsCallback,
  2. StatusBarPanel, Animator.AnimatorListener, View.OnTouchListener

处理点击事件方法:

[java] view plain copy

print?

  1. public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
  2. handleOnClick(view);
  3. }
  4. public void handleOnClick(View view) {
  5. TaskDescription ad = ((ViewHolder) view.getTag()).taskDescription;
  6. final Context context = view.getContext();
  7. final ActivityManager am = (ActivityManager)
  8. context.getSystemService(Context.ACTIVITY_SERVICE);
  9. if (ad.taskId >= 0) {
  10. // This is an active task; it should just go to the foreground.
  11. am.moveTaskToFront(ad.taskId, ActivityManager.MOVE_TASK_WITH_HOME);
  12. } else {
  13. Intent intent = ad.intent;
  14. intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
  15. | Intent.FLAG_ACTIVITY_TASK_ON_HOME
  16. | Intent.FLAG_ACTIVITY_NEW_TASK);
  17. if (DEBUG) Log.v(TAG, "Starting activity " + intent);
  18. context.startActivity(intent);
  19. }
  20. hide(true);
  21. }

注意代码:context.startActivity(intent);  这里就是执行对应的 Activity

处理长按键点击事件方法:

[java] view plain copy

print?

  1. public void handleLongPress(
  2. final View selectedView, final View anchorView, final View thumbnailView) {
  3. thumbnailView.setSelected(true);
  4. PopupMenu popup = new PopupMenu(mContext, anchorView == null ? selectedView : anchorView);
  5. popup.getMenuInflater().inflate(R.menu.recent_popup_menu, popup.getMenu());
  6. popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
  7. public boolean onMenuItemClick(MenuItem item) {
  8. if (item.getItemId() == R.id.recent_remove_item) {
  9. mRecentsContainer.removeViewInLayout(selectedView);
  10. } else if (item.getItemId() == R.id.recent_inspect_item) {
  11. ViewHolder viewHolder = (ViewHolder) selectedView.getTag();
  12. if (viewHolder != null) {
  13. final TaskDescription ad = viewHolder.taskDescription;
  14. startApplicationDetailsActivity(ad.packageName);
  15. mBar.animateCollapse();
  16. } else {
  17. throw new IllegalStateException("Oops, no tag on view " + selectedView);
  18. }
  19. } else {
  20. return false;
  21. }
  22. return true;
  23. }
  24. });
  25. popup.setOnDismissListener(new PopupMenu.OnDismissListener() {
  26. public void onDismiss(PopupMenu menu) {
  27. thumbnailView.setSelected(false);
  28. }
  29. });
  30. popup.show();
  31. }

这里弹出一个PopupMenu,分别是 A:"Remove from list" 及 B:"App Info"

其中A项表示将此任务移除出列表,执行 mRecentsContainer.removeViewInLayout(selectedView);

另外B是启动另外一个Acitivty列出应用信息:

[java] view plain copy

print?

  1. private void startApplicationDetailsActivity(String packageName) {
  2. Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
  3. Uri.fromParts("package", packageName, null));
  4. intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  5. getContext().startActivity(intent);
  6. }

总结:

这里详细的对SystemUI 的两个最重要的 StatusBar NavigationBar(SystemUIService) 及缩略图代码流程分析。
因此各家厂商根据自家的需求,需要定制SystemUI或者美化SystemUI,不同的平台也会有不同的修改,但大体框架是没有变的,
无非是在原有基础上的修修改改或者增加一些自己的类等等。

时间: 2024-08-26 04:17:18

【转】android SystemUI 流程分析的相关文章

Android启动流程分析(一)

############################################# 本文为极度寒冰原创,转载请注明出处 ############################################# Android的启动流程绝大部分人都知道,但是大多数人都是这样描述的: Android启动,首先是启动Bootloader,然后挂载kernel,挂载完kernel之后,会启动android的init进程,init进程会去孵化Zygote,Zygote是android的重要支柱之

Android系统启动流程分析

随着Android版本的升级,aosp项目中的代码也有了些变化,本文基于Android 7.0分析Android系统启动流程. 简单来说Android系统启动大体如下: init进程 和所有Linux系统一样,Android系统的启动同样是从init进程启动.init进程会解析init.rc文件(关于init.rc中的语法,可以参见我之前写的深入分析AIL语言及init.rc文件),加载相关目录,并启动相关服务 init进程在/system/core/init/init.c init.rc文件在

Android启动流程分析(十) action的执行和service的启动

############################################# 本文为极度寒冰原创,转载请注明出处 ############################################# 在前面的文章分析完init.rc的解析以后,我们知道现在action按照init.c的main函数中的秩序,维护了一条qlist的链表,listnode为action_qlist service也维护了一条链表,listnode为service_list. 那么,在android

Android SystemUI源代码分析和修改

1.在导航栏中添加音量加减button 一些Android音量调节button.或者从保护实体按键的角度考虑,就须要在导航栏的虚拟按键中加入音量加减调节按键. 效果例如以下图所看到的: 实现步骤例如以下: 1.首先在SystemUI中加入音量加减的资源文件.路径例如以下: frameworks/base/packages/SystemUI/res/ 将图片放入相应的drawable目录,包含音量+.和音量-,见上图. 2.改动导航栏的布局文件.路径: frameworks/base/packag

Android 启动流程分析

原文:https://www.jianshu.com/p/a5532ecc8377 作者曾经在高通的Android性能组工作,主要工作是优化Android Application的启动时间. APP基础理论 要想优化App启动时间, 第一步就是了解App启动进程的工作原理. 有几个基础理论: Android Application与其他移动平台有两个重大不同点: 每个Android App都在一个独立空间里,意味着其运行在一个单独的进程中,拥有自己的VM, 被系统分配一个唯一的user ID A

Android启动流程分析(九) 解析init.rc的service

############################################# 本文为极度寒冰原创,转载请注明出处 ############################################# 在分析完解析init.rc的action之后,剩下的一部分就是解析service了. 而解析service还是需要回到parse_config里面来.根据前面的知识,我们也可以很容易的知道在关键字为section的时候,会进入到parse_new_section. 这里会先执行p

Android启动流程分析(二) init进程的启动

############################################# 本文为极度寒冰原创,转载请注明出处 ############################################# 分析init进程,首先要分析init进程是如何启动的, init的源码位于(system/core/init),我们先来看看init进程的android.mk 下面是从system/core/init模块拿出来的一段code: LOCAL_MODULE:= init LOCAL_

Android启动流程分析(七) init.rc的解析

############################################# 本文为极度寒冰原创,转载请注明出处 ############################################# Init.rc的解析过程是笔者认为在android启动过程中,最复杂,最难理解的部分. 虽然它的内容很少,但是却包含了非常多的处理,接下来我们来慢慢的分析. 经过前面的分析,我们知道了read完init.rc的文件后,保存到了data的数组,传递到了parse_config的函数

Android启动流程分析(十二) SystemServer

############################################# 本文为极度寒冰原创,转载请注明出处 ############################################# SystemServer也是系统的一个重要的守护进程,从SystemServer的进程中,我们看到了系统的各种关键的Service的启动. 另外,根据前面的zygote的分析,我们知道了systemServer在android的启动过程中是肯定要启动的. 因为在init.rc里面