1. 程式人生 > >Android4.4 SystemUI分析之PowerUI

Android4.4 SystemUI分析之PowerUI

以下分析是基於MTK Android4.4原生的SystemUI與Google 的SystemUI有微小的區別,但兩者的整體框架是差不多的。

這一篇是分析SystemUI的第一篇,先從最簡單的PowerUI著手,原始碼路徑:/frameworks/base/packages/SystemUI  程式目錄結構如下:

我匯入Eclipse編輯,報錯的原因是因為找不到Framework上的一些包和資源,這個沒有關係;修改完後在使用mmm模組編譯,再push到手機(eng版本)上進行除錯,push後需要重啟才能生效。

在AndroidManifest.xml上沒有Activity註冊

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

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
所以整個程式啟動是從外部啟動SystemUIService的。那麼是如何啟動SystemUIService的呢?看下這個檔案:/frameworks/base/services/java/com/android/server/SystemServer.java在這個檔案中可以找到
    static final void startSystemUi(Context context) {
        Intent intent = new Intent();
        intent.setComponent(new ComponentName("com.android.systemui",
                    "com.android.systemui.SystemUIService"));
        //Slog.d(TAG, "Starting service: " + intent);
        context.startServiceAsUser(intent, UserHandle.OWNER);
    }
所以SystemUI在SystemService啟動時就被呼叫了。

SystemUIService的關鍵程式碼如下:

package com.android.systemui;

import android.app.Service;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.IBinder;
import android.util.Log;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.HashMap;

public class SystemUIService extends Service {
    private static final String TAG = "SystemUIService";

    /**
     * The classes of the stuff to start.
     */
    private final Class<?>[] SERVICES = new Class[] {
            com.android.systemui.recent.Recents.class,
            com.android.systemui.statusbar.SystemBars.class,
            com.android.systemui.usb.StorageNotification.class,
            com.android.systemui.power.PowerUI.class,
            com.android.systemui.media.RingtonePlayer.class,
            com.android.systemui.settings.SettingsUI.class,
        };

    /**
     * Hold a reference on the stuff we start.
     */
    private final SystemUI[] mServices = new SystemUI[SERVICES.length];

    @Override
    public void onCreate() {
        HashMap<Class<?>, Object> components = new HashMap<Class<?>, Object>();
        final int N = SERVICES.length;
        for (int i=0; i<N; i++) {
            Class<?> cl = SERVICES[i];
            Log.d(TAG, "loading: " + cl);
            Log.d("dzt", "loading: " + cl);
            try {
                mServices[i] = (SystemUI)cl.newInstance();
            } catch (IllegalAccessException ex) {
                throw new RuntimeException(ex);
            } catch (InstantiationException ex) {
                throw new RuntimeException(ex);
            }
            mServices[i].mContext = this;
            mServices[i].mComponents = components;
            Log.d(TAG, "running: " + mServices[i]);
            Log.d("dzt", "running: " + mServices[i]);
            mServices[i].start();
        }
    }
}

這些類
com.android.systemui.recent.Recents.class,
            com.android.systemui.statusbar.SystemBars.class,
            com.android.systemui.usb.StorageNotification.class,
            com.android.systemui.power.PowerUI.class,
            com.android.systemui.media.RingtonePlayer.class,
            com.android.systemui.settings.SettingsUI.class,

都是繼承於:SystemUI,在SystemUIService的OnCreate()函式中會建立例項,並呼叫mServices[i].start();方法。

下面就分析最簡單的com.android.systemui.power.PowerUI.class

在PowerUI的start()方法中,註冊一些監聽器

public void start() {

        mLowBatteryAlertCloseLevel = mContext.getResources().getInteger(
                com.android.internal.R.integer.config_lowBatteryCloseWarningLevel);
        mLowBatteryReminderLevels[0] = mContext.getResources().getInteger(
                com.android.internal.R.integer.config_lowBatteryWarningLevel);
        mLowBatteryReminderLevels[1] = mContext.getResources().getInteger(
                com.android.internal.R.integer.config_criticalBatteryWarningLevel);

        final PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
        mScreenOffTime = pm.isScreenOn() ? -1 : SystemClock.elapsedRealtime();

        // Register for Intent broadcasts for...
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_BATTERY_CHANGED);
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        filter.addAction(Intent.ACTION_SCREEN_ON);
        /// M: Support show battery level when configuration changed. @{
        filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
        /// M: Support show battery level when configuration changed. @}
        /// M: Hide low battery dialog when PowerOffAlarm ring. @{
        filter.addAction("android.intent.action.normal.boot");
        filter.addAction("android.intent.action.ACTION_SHUTDOWN_IPO");
        /// M: Hide low battery dialog when PowerOffAlarm ring. @}
        mContext.registerReceiver(mIntentReceiver, filter, null, mHandler);
    }
例如監聽ACTION_BATTERY_CHANGED判斷電量的大小,如果低電給一個提示給使用者,其它的ACTION也都是根據廣播來處理一些特定的事情,如果跟電源相關功能需要定製或新增新的監聽器都可以在這個類中修改。

SystemUI上的電池是如何畫上去的呢?看下圖


在status_bar.xml這個佈局檔案中可以找到

<com.android.systemui.BatteryMeterView
                    android:id="@+id/battery"
                    android:layout_height="16dp"
                    android:layout_width="10.5dp"
                    android:layout_marginBottom="0.33dp"
                    android:layout_marginStart="4dip"
                    />
這個就是顯示電池圖示的View,類定義在/frameworks/base/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java


我在這裡把BatteryMeterView.java拿出來寫了一個Demo就是上圖在Activity上顯示的電池小圖示

在BatteryMeterView類中有兩個重要的函式來註冊廣播和登出廣播

@Override
	public void onAttachedToWindow() {
		super.onAttachedToWindow();

		IntentFilter filter = new IntentFilter();
		filter.addAction(Intent.ACTION_BATTERY_CHANGED);
		filter.addAction(ACTION_LEVEL_TEST);
		final Intent sticky = getContext().registerReceiver(mTracker, filter);
		if (sticky != null) {
			// preload the battery level
			mTracker.onReceive(getContext(), sticky);
		}
	}

	@Override
	public void onDetachedFromWindow() {
		super.onDetachedFromWindow();

		getContext().unregisterReceiver(mTracker);
	}
在廣播上監聽電池的改變呼叫postInvalidate() 去繪製小圖示
private class BatteryTracker extends BroadcastReceiver {

		@Override
		public void onReceive(Context context, Intent intent) {
			final String action = intent.getAction();
			if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
				if (testmode && !intent.getBooleanExtra("testmode", false))
					return;

				level = (int) (100f * intent.getIntExtra(
						BatteryManager.EXTRA_LEVEL, 0) / intent.getIntExtra(
						BatteryManager.EXTRA_SCALE, 100));

				plugType = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
				plugged = plugType != 0;
				health = intent.getIntExtra(BatteryManager.EXTRA_HEALTH,
						BatteryManager.BATTERY_HEALTH_UNKNOWN);
				status = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
						BatteryManager.BATTERY_STATUS_UNKNOWN);
				technology = intent
						.getStringExtra(BatteryManager.EXTRA_TECHNOLOGY);
				voltage = intent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, 0);
				temperature = intent.getIntExtra(
						BatteryManager.EXTRA_TEMPERATURE, 0);
				Log.i(TAG, "onReceive ----- level = " + level + "  plugType = "
						+ plugType + "  health = " + health + "  status = "
						+ status + " technology = " + technology
						+ "  voltage = " + voltage + "  temperature = "
						+ temperature);
				setContentDescription(context.getString(
						R.string.accessibility_battery_level, level));
				postInvalidate();
			} 
		}
	}

	BatteryTracker mTracker = new BatteryTracker();
呼叫postInvalidate()會回撥View的public void draw(Canvas c)去重繪圖示。

整個小圖示的繪製都是在public void draw(Canvas c)中完成的,沒有用到圖片,詳細的細節就需要好好分析原始碼

log的TAG

private static final String TAG = TestSystemUI.class.getSimpleName();

得到的TAG就是TestSystemUI