android SystemUI 流程分析

Posted CSlunatic

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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.   
  3.     public FlyingCat(Context context, AttributeSet as) {  
  4.       super(context, as);  
  5.       setImageResource(R.drawable.nyandroid_anim); // @@@  
  6.   
  7.       if (DEBUG) setBackgroundColor(0x80FF0000);  
  8.   }  
  9.   ...  
  10. }</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.   
  3. static final void startSystemUi(Context context) {  
  4.     Intent intent = new Intent();  
  5.     intent.setComponent(new ComponentName("com.android.systemui",  
  6.                 "com.android.systemui.SystemUIService"));  
  7.     Slog.d(TAG, "Starting service: " + intent);  
  8.     context.startService(intent);  
  9. }</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.   
  9. @Override  
  10. public void onCreate() {  
  11.     // Pick status bar or system bar.  
  12.     IWindowManager wm = IWindowManager.Stub.asInterface(  
  13.             ServiceManager.getService(Context.WINDOW_SERVICE));  
  14.     try {  
  15.         SERVICES[0] = wm.canStatusBarHide()  
  16.                 ? R.string.config_statusBarComponent  
  17.                 : R.string.config_systemBarComponent;  
  18.     } catch (RemoteException e) {  
  19.         Slog.w(TAG, "Failing checking whether status bar can hide", e);  
  20.     }  
  21.   
  22.     final int N = SERVICES.length;  
  23.     mServices = new SystemUI[N];  
  24.     for (int i=0; i<N; i++) {  
  25.         Class cl = chooseClass(SERVICES[i]);  
  26.         Slog.d(TAG, "loading: " + cl);  
  27.         try {  
  28.             mServices[i] = (SystemUI)cl.newInstance();  
  29.         } catch (IllegalAccessException ex) {  
  30.             throw new RuntimeException(ex);  
  31.         } catch (InstantiationException ex) {  
  32.             throw new RuntimeException(ex);  
  33.         }  
  34.         mServices[i].mContext = this;  
  35.         Slog.d(TAG, "running: " + mServices[i]);  
  36.         mServices[i].start();  
  37.     }  
  38. }</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.       
  5.     public void setInitialDisplaySize(int width, int height) {  
  6.     ...  
  7.         // Determine whether the status bar can hide based on the size  
  8.         // of the screen.  We assume sizes > 600dp are tablets where we  
  9.         // will use the system bar.  
  10.         int shortSizeDp = shortSize  
  11.                 * DisplayMetrics.DENSITY_DEFAULT  
  12.                 / DisplayMetrics.DENSITY_DEVICE;  
  13.         mStatusBarCanHide = shortSizeDp < 600;  
  14.         
  15.         }</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.   
  5.         // Connect in to the status bar manager service  
  6.         StatusBarIconList iconList = new StatusBarIconList();  
  7.         ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>();  
  8.         ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>();  
  9.         mCommandQueue = new CommandQueue(this, iconList);  
  10.         mBarService = IStatusBarService.Stub.asInterface(  
  11.                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));  
  12.         int[] switches = new int[7];  
  13.         ArrayList<IBinder> binders = new ArrayList<IBinder>();  
  14.         try {  
  15.             mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications,  
  16.                     switches, binders);  
  17.         } catch (RemoteException ex) {  
  18.             // If the system process isn‘t there we‘re doomed anyway.  
  19.         }  
  20.   
  21.         disable(switches[0]);  
  22.         setSystemUiVisibility(switches[1]);  
  23.         topAppWindowChanged(switches[2] != 0);  
  24.         // StatusBarManagerService has a back up of IME token and it‘s restored here.  
  25.         setImeWindowStatus(binders.get(0), switches[3], switches[4]);  
  26.         setHardKeyboardStatus(switches[5] != 0, switches[6] != 0);  
  27.   
  28.         // Set up the initial icon state  
  29.         int N = iconList.size();  
  30.         int viewIndex = 0;  
  31.         for (int i=0; i<N; i++) {  
  32.             StatusBarIcon icon = iconList.getIcon(i);  
  33.             if (icon != null) {  
  34.                 addIcon(iconList.getSlot(i), i, viewIndex, icon);  
  35.                 viewIndex++;  
  36.             }  
  37.         }  
  38.   
  39.         // Set up the initial notification state  
  40.         N = notificationKeys.size();  
  41.         if (N == notifications.size()) {  
  42.             for (int i=0; i<N; i++) {  
  43.                 addNotification(notificationKeys.get(i), notifications.get(i));  
  44.             }  
  45.         } else {  
  46.             Log.wtf(TAG, "Notification list length mismatch: keys=" + N  
  47.                     + " notifications=" + notifications.size());  
  48.         }  
  49.   
  50.         // Put up the view  
  51.         final int height = getStatusBarHeight();  
  52.   
  53.         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(  
  54.                 ViewGroup.LayoutParams.MATCH_PARENT,  
  55.                 height,  
  56.                 WindowManager.LayoutParams.TYPE_STATUS_BAR,  
  57.                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE  
  58.                     | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING  
  59.                     | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,  
  60.                 PixelFormat.OPAQUE);  
  61.           
  62.         // the status bar should be in an overlay if possible  
  63.         final Display defaultDisplay   
  64.             = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))  
  65.                 .getDefaultDisplay();  
  66.         if (ActivityManager.isHighEndGfx(defaultDisplay)) {  
  67.             lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;  
  68.         }  
  69.   
  70.         lp.gravity = getStatusBarGravity();  
  71.         lp.setTitle("StatusBar");  
  72.         lp.packageName = mContext.getPackageName();  
  73.         lp.windowAnimations = R.style.Animation_StatusBar;  
  74.         WindowManagerImpl.getDefault().addView(sb, lp);  
  75.   
  76.         if (SPEW) {  
  77.             Slog.d(TAG, "Added status bar view: gravity=0x" + Integer.toHexString(lp.gravity)   
  78.                    + " icons=" + iconList.size()  
  79.                    + " disabled=0x" + Integer.toHexString(switches[0])  
  80.                    + " lights=" + switches[1]  
  81.                    + " menu=" + switches[2]  
  82.                    + " imeButton=" + switches[3]  
  83.                    );  
  84.         }  
  85.   
  86.         mDoNotDisturb = new DoNotDisturb(mContext);  
  87.     }</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.   
  8.         // return a snapshot of the current list of recent apps  
  9.     ArrayList<TaskDescription> getRecentTasks() {  
  10.         cancelLoadingThumbnails();  
  11.   
  12.         ArrayList<TaskDescription> tasks = new ArrayList<TaskDescription>();  
  13.         final PackageManager pm = mContext.getPackageManager();  
  14.         final ActivityManager am = (ActivityManager)  
  15.                 mContext.getSystemService(Context.ACTIVITY_SERVICE);  
  16.   
  17.         final List<ActivityManager.RecentTaskInfo> recentTasks =  
  18.                 am.getRecentTasks(MAX_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE);  
  19.   
  20.         ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)  
  21.                     .resolveActivityInfo(pm, 0);  
  22.   
  23.         HashSet<Integer> recentTasksToKeepInCache = new HashSet<Integer>();  
  24.         int numTasks = recentTasks.size();  
  25.   
  26.         // skip the first task - assume it‘s either the home screen or the current activity.  
  27.         final int first = 1;  
  28.         recentTasksToKeepInCache.add(recentTasks.get(0).persistentId);  
  29.         for (int i = first, index = 0; i < numTasks && (index < MAX_TASKS); ++i) {  
  30.             final ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(i);  
  31.   
  32.             TaskDescription item = createTaskDescription(recentInfo.id,  
  33.                     recentInfo.persistentId, recentInfo.baseIntent,  
  34.                     recentInfo.origActivity, recentInfo.description, homeInfo);  
  35.   
  36.             if (item != null) {  
  37.                 tasks.add(item);  
  38.                 ++index;  
  39.             }  
  40.         }  
  41.   
  42.         // when we‘re not using the TaskDescription cache, we load the thumbnails in the  
  43.         // background  
  44.         loadThumbnailsInBackground(new ArrayList<TaskDescription>(tasks));  
  45.         return tasks;  
  46.     }  
  47. </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.   
  5.     public View getMenuButton() {  
  6.         return mCurrentView.findViewById(R.id.menu);  
  7.     }  
  8.   
  9.     public View getBackButton() {  
  10.         return mCurrentView.findViewById(R.id.back);  
  11.     }  
  12.   
  13.     public View getHomeButton() {  
  14.         return mCurrentView.findViewById(R.id.home);  
  15.     }</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.   
  5.   public void handleOnClick(View view) {  
  6.       TaskDescription ad = ((ViewHolder) view.getTag()).taskDescription;  
  7.       final Context context = view.getContext();  
  8.       final ActivityManager am = (ActivityManager)  
  9.               context.getSystemService(Context.ACTIVITY_SERVICE);  
  10.       if (ad.taskId >= 0) {  
  11.           // This is an active task; it should just go to the foreground.  
  12.           am.moveTaskToFront(ad.taskId, ActivityManager.MOVE_TASK_WITH_HOME);  
  13.       } else {  
  14.           Intent intent = ad.intent;  
  15.           intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY  
  16.                   | Intent.FLAG_ACTIVITY_TASK_ON_HOME  
  17.                   | Intent.FLAG_ACTIVITY_NEW_TASK);  
  18.           if (DEBUG) Log.v(TAG, "Starting activity " + intent);  
  19.           context.startActivity(intent);  
  20.       }  
  21.       hide(true);  
  22.   }  
  23.     


注意代码: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,不同的平台也会有不同的修改,但大体框架是没有变的,
无非是在原有基础上的修修改改或者增加一些自己的类等等。











































































































































































以上是关于android SystemUI 流程分析的主要内容,如果未能解决你的问题,请参考以下文章

Android 12系统源码_SystemUI系统状态栏StatusBar的创建流程

全网最详细的Android11.0长按power键关机流程分析

全网最详细的Android11.0长按power键关机流程分析

Android 8.1 SystemUI虚拟导航键加载流程解析

Android 10.0 原生SystemUI下拉通知栏UI背景设置为圆角背景的定制

源码分析篇 - Android绘制流程requestLayout()与invalidate()流程分析