• 【转】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>  


    定义在 frameworksasepackagesSystemUI esdrawable yandroid_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>  


    相关图片在: frameworksasepackagesSystemUI esdrawable-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: com.android.systemui.statusbar.tablet.TabletStatusBar@415b8b20
    D/SystemUIService( 1382): running: com.android.systemui.power.PowerUI@416b5ae8
    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,不同的平台也会有不同的修改,但大体框架是没有变的,
    无非是在原有基础上的修修改改或者增加一些自己的类等等。

  • 相关阅读:
    leetcode算法题(JavaScript实现)
    使用git submodule管理一个需要多个分立开发或者第三方repo的项目
    linux下从源代码安装git
    git项目实战常用workflow和命令
    如何在linux console中显示当前你在的branch?
    git plumbing 更加底层命令解析-深入理解GIT
    如何直接在github网站上更新你fork的repo?
    git remotes
    git和其他版本控制系统的区别
    Git server安装和配置
  • 原文地址:https://www.cnblogs.com/cslunatic/p/6870066.html
Copyright © 2020-2023  润新知