需求描述:客户需要在状态栏下拉列表里添加更改屏幕密度density开关按钮
Android版本:android8.1


这是一个长故事:

public class StatusBar extends SystemUI  //Statusbar是继承自SystemUI的

StatusBar是个很大很大的类,里面加载了太多的对象了,在StatusBar开始的时候执行了start()方法

    @Override    public void start() {    ....    createAndAddWindows();   //创建并添加窗口    ....    }

看看createAndAddWindows里

    public void createAndAddWindows() {        addStatusBarWindow();    }    -------------------------private void addStatusBarWindow() {        makeStatusBarView();        mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);        mRemoteInputController = new RemoteInputController(mHeadsUpManager);        mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());    }

makeStatusBarView里有上千行代码,其中有一段

   final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this,                    mIconController);

SystemUIFactory.getInstance().createQSTileHost
SystemUIFactory去创建QuickStatubar

    public QSTileHost createQSTileHost(Context context, StatusBar statusBar,            StatusBarIconController iconController) {        return new QSTileHost(context, statusBar, iconController);    }

接着就进入了QSTileHost的构造方法里

    public QSTileHost(Context context, StatusBar statusBar,            StatusBarIconController iconController) {        mIconController = iconController;        mContext = context;        mStatusBar = statusBar;        mServices = new TileServices(this, Dependency.get(Dependency.BG_LOOPER));        mQsFactories.add(new QSFactoryImpl(this)); //创建新的实体        Dependency.get(PluginManager.class).addPluginListener(this, QSFactory.class, true);        Dependency.get(TunerService.class).addTunable(this, TILES_SETTING);        // AutoTileManager can modify mTiles so make sure mTiles has already been initialized.        mAutoTiles = new AutoTileManager(context, this);    }

创建新的QSFactoryImpl实体 new QSFactoryImpl(this)
接着在QSTileHost里的回调**onTuningChanged(String key, String newValue)**里调用了createTile

    public QSTile createTile(String tileSpec) {        for (int i = 0; i < mQsFactories.size(); i++) {            QSTile t = mQsFactories.get(i).createTile(tileSpec);            if (t != null) {                return t;            }        }        return null;    }

这里就去调用了QSFactoryImpl实体的createTile

public QSTile createTile(String tileSpec) {        if (tileSpec.equals("wifi")) return new WifiTile(mHost);        else if (tileSpec.equals("bt")) return new BluetoothTile(mHost);        else if (tileSpec.equals("cell")) return new CellularTile(mHost);        else if (tileSpec.equals("dnd")) return new DndTile(mHost);        else if (tileSpec.equals("inversion")) return new ColorInversionTile(mHost);        else if (tileSpec.equals("airplane")) return new AirplaneModeTile(mHost);        else if (tileSpec.equals("work")) return new WorkModeTile(mHost);        else if (tileSpec.equals("rotation")) return new RotationLockTile(mHost);        else if (tileSpec.equals("flashlight")) return new FlashlightTile(mHost);        else if (tileSpec.equals("density")) return new DensityTile(mHost);        else if (tileSpec.equals("location")) return new LocationTile(mHost);        else if (tileSpec.equals("cast")) return new CastTile(mHost);        else if (tileSpec.equals("hotspot")) return new HotspotTile(mHost);        else if (tileSpec.equals("user")) return new UserTile(mHost);        else if (tileSpec.equals("battery")) return new BatterySaverTile(mHost);        else if (tileSpec.equals("saver")) return new DataSaverTile(mHost);        else if (tileSpec.equals("night")) return new NightDisplayTile(mHost);        else if (tileSpec.equals("nfc")) return new NfcTile(mHost);        else if (tileSpec.equals("dataconnection") && !SIMHelper.isWifiOnlyDevice())            return new MobileDataTile(mHost);        else if (tileSpec.equals("simdataconnection") && !SIMHelper.isWifiOnlyDevice() &&                quickSettingsPlugin.customizeAddQSTile(new SimDataConnectionTile(mHost)) != null) {            return (SimDataConnectionTile) quickSettingsPlugin.customizeAddQSTile(                    new SimDataConnectionTile(mHost));        } else if (tileSpec.equals("dulsimsettings") && !SIMHelper.isWifiOnlyDevice() &&                quickSettingsPlugin.customizeAddQSTile(new DualSimSettingsTile(mHost)) != null) {            return (DualSimSettingsTile) quickSettingsPlugin.customizeAddQSTile(                    new DualSimSettingsTile(mHost));        } else if (tileSpec.equals("apnsettings") && !SIMHelper.isWifiOnlyDevice() &&                quickSettingsPlugin.customizeAddQSTile(new ApnSettingsTile(mHost)) != null) {            return (ApnSettingsTile) quickSettingsPlugin.customizeAddQSTile(                    new ApnSettingsTile(mHost));        }      }

这里都是根据tileSpec创建Tile,比如:flashlight就对应的创建 new FlashlightTile(mHost)
而这里的tileSpecs 来自于前面的回调onTuningChanged(String key, String newValue)

final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
protected List<String> loadTileSpecs(Context context, String tileList) {        final Resources res = context.getResources();        String defaultTileList = res.getString(R.string.quick_settings_tiles_default);        Log.d(TAG, "loadTileSpecs() default tile list: " + defaultTileList);        if (tileList == null) {            tileList = res.getString(R.string.quick_settings_tiles);            if (DEBUG) Log.d(TAG, "Loaded tile specs from config: " + tileList);        } else {            if (DEBUG) Log.d(TAG, "Loaded tile specs from setting: " + tileList);        }        final ArrayList<String> tiles = new ArrayList<String>();        boolean addedDefault = false;        for (String tile : tileList.split(",")) {            tile = tile.trim();            if (tile.isEmpty()) continue;            if (tile.equals("default")) {                if (!addedDefault) {                    tiles.addAll(Arrays.asList(defaultTileList.split(",")));                    addedDefault = true;                }            } else {                tiles.add(tile);            }        }        return tiles;    }

发现 原来这个tilespace的字符来源是R.string.quick_settings_tiles_default
全局所属xml,发现是这样的

    <string name="quick_settings_tiles_default" translatable="false">        wifi,bt,dnd,flashlight,density,rotation,battery,cell,airplane,cast    string>

这里每一个item对应着下拉列表的一个item项
所以,我们就可以依葫芦画瓢的,在这里添加一个。
然后再createTile里,加一行我们自己的
剩下的内容就是抄袭系统是如何添加item的,但还是得了解那个QSTile对象是什么

##下面以AirplaneModeTile为例来分析怎么添加自己的QSTile

进入AirplaneModeTile,发现

public class AirplaneModeTile extends QSTileImpl<BooleanState>

它继承了QSTileImpl 以及泛型 BooleanState
实际上,createTile里,所有的tile,都是QSTileImpl的子类,分别实现了不同的功能
在AirplaneModeTile 里,我们会发现很多的重写方法,看不到调用轨迹

第一个成员变量,

    private final Icon mIcon =            ResourceIcon.get(R.drawable.ic_signal_airplane);

里面的xml是svg矢量图
预览效果

这不就是下拉列表里的飞行模式开关吗。

接着分析成员方法

重写方法

    @Override    public void handleClick() {        setEnabled(!mState.value);    }

处理图标点击事件

    private void setEnabled(boolean enabled) {        final ConnectivityManager mgr =                (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);        mgr.setAirplaneMode(enabled);    }

调用系统的connectivityManager去设置飞行模式开关~~
呵呵,写的这么简单

    @Override    public CharSequence getTileLabel() {        return mContext.getString(R.string.airplane_mode);    }

返回tile名字,直接从string里找

    @Override    protected void handleUpdateState(BooleanState state, Object arg) {        final int value = arg instanceof Integer ? (Integer)arg : mSetting.getValue();        final boolean airplaneMode = value != 0;        state.value = airplaneMode;        state.label = mContext.getString(R.string.airplane_mode);        state.icon = mIcon;        if (state.slash == null) {            state.slash = new SlashState();        }        state.slash.isSlashed = !airplaneMode;        state.state = airplaneMode ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;        state.contentDescription = state.label;        state.expandedAccessibilityClassName = Switch.class.getName();    }

处理状态更新,state来自于booleanState 以及arg

    @Override    public int getMetricsCategory() {        return MetricsEvent.QS_AIRPLANEMODE;    }

得到getMetricsCategory, 这个是系统必须写的,至于什么作用,猜着估计是用来标识用的,在metrics_constants.proto 这个文件里有定义,我们需要给自己的tile加一个值即可。
然后就是

    @Override    protected String composeChangeAnnouncement() {        if (mState.value) {            return mContext.getString(R.string.accessibility_quick_settings_airplane_changed_on);        } else {            return mContext.getString(R.string.accessibility_quick_settings_airplane_changed_off);        }    }

很明显,就是简单的得到string

然后,, 没了,一个简单的AirplanmodeTile就写完了,抽象出来的方法只有这么几个需要重写,其他构造和处理逻辑都写在了父类里,它的父类QSTileImpl是个抽象类,不过我们不需要去重新写父类,这大概在设计者设计的时候,就已经考虑好了解耦,设计思想很明显。

然后,我们只需要重新几个自己的方法就可以了,在handleClick里就是去处理点击事件的开关,我们只需要去做自己想做的开关即可。


客户的需求是添加控制系统density的开关,
我就照着重写了个DensityTile,重写里面的方法,
在click的里面借来了开发者选项里改写系统density的代码

        try {            final Resources res = mContext.getResources();            final DisplayMetrics metrics = res.getDisplayMetrics();            final int newSwDp = Math.max(enable?480:320, 320);            final int minDimensionPx = Math.min(metrics.widthPixels, metrics.heightPixels);            final int newDensity = DisplayMetrics.DENSITY_MEDIUM * minDimensionPx / newSwDp;            final int densityDpi = Math.max(newDensity, 120);            DisplayDensityUtils.setForcedDisplayDensity(Display.DEFAULT_DISPLAY, densityDpi);        } catch (Exception e) {            // TODO: display a message instead of silently failing.            Slog.e(TAG, "Couldn't save density", e);        }

setforceDisplayDensity是个新收获,

    public static void setForcedDisplayDensity(final int displayId, final int density) {        final int userId = UserHandle.myUserId();        AsyncTask.execute(() -> {            try {                final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();                wm.setForcedDisplayDensityForUser(displayId, density, userId);            } catch (RemoteException exc) {                Log.w(LOG_TAG, "Unable to save forced display density setting");            }        });    }

可以看到,调用的是IWindowManager接口对象,而且是直接从Global获取,而得到的对象实体,实际就是
WindowManagerService ,—public class WindowManagerService extends IWindowManager.Stub
而WindowManagerService里面还有许多其他设置系统显示的方法,所以,我们可以用这种方式,很容易的从其他地方调用windowservice里的方法,不得不感叹这是系统做的好。


最后效果:

这个开关就是设置系统density显示分辨率的开关,打开就是7201280,关闭就是480854,任务完成。

更多相关文章

  1. Android状态栏透明方法,与工具栏颜色一致
  2. Android手机应用开发(八) | 制作简单音乐播放器
  3. Android(安卓)Jetpack 之 LifeCycle
  4. Android(安卓)获取判断是否有悬浮窗权限的方法
  5. Android简单记录和恢复ListView滚动位置的方法
  6. Android(安卓)使用PDF.js浏览pdf的方法示例
  7. Android中Bitmap用法实例分析
  8. 浅谈Android(安卓)onTouchEvent 与 onInterceptTouchEvent的区别
  9. android 倒计时的控件,以动画的形式平滑的完成数字的过度

随机推荐

  1. Android默认输入法语言的修改以及Setting
  2. android 中的 ViewFlipper 的简单使用
  3. Android(安卓)自定义时间轴
  4. :用i-jetty把web项目发布到Android手机上
  5. Android项目开发技术总结 by wellsoho
  6. Android输入事件InputReader和InputDispa
  7. Android(安卓)通话处理流程【转】
  8. 自定义alertDialog
  9. Android(安卓)使用ViewModelProvider时th
  10. Window下Android(安卓)SDK安装