SystemUI插件化機(jī)制Plugin

1.SystemUI的啟動
在SystemServer中startOtherServices中調(diào)用startSystemUi方法啟動SystemUIService翠勉。
SystemUIService在onCreate中

((SystemUIApplication) getApplication()).startServicesIfNeeded();

進(jìn)一步調(diào)用

startServicesIfNeeded(SERVICES);

其中SERVICES是一個服務(wù)列表啤誊,會依次調(diào)用

mServices[i].start();

來啟動SystemUI的各種服務(wù)帮坚,也就是各種SystemUI的子類瓷耙。

2.加載插件模塊

Dependency.get(PluginManager.class).addPluginListener(
         new PluginListener<OverlayPlugin>() {
              private ArraySet<OverlayPlugin> mOverlays;

              @Override
              public void onPluginConnected(OverlayPlugin plugin, Context pluginContext) {
                  StatusBar statusBar = getComponent(StatusBar.class);
                  if (statusBar != null) {
                      plugin.setup(statusBar.getStatusBarWindow(),
                              statusBar.getNavigationBarView());
                  }
                 // Lazy init.
                 if (mOverlays == null) mOverlays = new ArraySet<>();
                 if (plugin.holdStatusBarOpen()) {
                     mOverlays.add(plugin);
                     Dependency.get(StatusBarWindowManager.class).setStateListener(b ->
                              mOverlays.forEach(o -> o.setCollapseDesired(b)));
                     Dependency.get(StatusBarWindowManager.class).setForcePluginOpen(
                             mOverlays.size() != 0);

                 }
            }

           @Override
            public void onPluginDisconnected(OverlayPlugin plugin) {
                mOverlays.remove(plugin);
                Dependency.get(StatusBarWindowManager.class).setForcePluginOpen(
                         mOverlays.size() != 0);
           }
        }, OverlayPlugin.class, true /* Allow multiple plugins */);
 mServicesStarted = true;

Dependency.get方法:

    public static <T> T get(Class<T> cls) {
        return sDependency.getDependency(cls);
    }
    protected final <T> T getDependency(Class<T> cls) {
        return getDependencyInner(cls);
    }
    private synchronized <T> T getDependencyInner(Object key) {
        @SuppressWarnings("unchecked")
        T obj = (T) mDependencies.get(key);
        if (obj == null) {
            obj = createDependency(key);
            mDependencies.put(key, obj);
        }
        return obj;
    }

先從mDependencies里去查找荒揣,找不到再創(chuàng)建一個Dependency出來尉间,看一下createDependency

    protected <T> T createDependency(Object cls) {
        Preconditions.checkArgument(cls instanceof DependencyKey<?> || cls instanceof Class<?>);

        @SuppressWarnings("unchecked")
        DependencyProvider<T> provider = mProviders.get(cls);
        if (provider == null) {
            throw new IllegalArgumentException("Unsupported dependency " + cls);
        }
        return provider.createDependency();
    }

這里從mProviders里面去找巍膘,存在就創(chuàng)建摊沉,不存在就拋出異常狐史。這個mProviders是在什么地方初始化的呢?前面講到mServices[i].start();其中Dependency.class也是在這個mServices中说墨,SystemUI啟動的時候會調(diào)用其start()@Dependency.java方法

@Override
    public void start() {
mProviders.put(TIME_TICK_HANDLER, () -> {
            HandlerThread thread = new HandlerThread("TimeTick");
            thread.start();
            return new Handler(thread.getLooper());
        });
        mProviders.put(BG_LOOPER, () -> {
            HandlerThread thread = new HandlerThread("SysUiBg",
                    Process.THREAD_PRIORITY_BACKGROUND);
            thread.start();
            return thread.getLooper();
        });
        mProviders.put(MAIN_HANDLER, () -> new Handler(Looper.getMainLooper()));
        mProviders.put(ActivityStarter.class, () -> new ActivityStarterDelegate());
        ......
        // 這里添加了很多class到mProviders中
        ......
      // Put all dependencies above here so the factory can override them if it wants.
      SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);

mProviders 是一個ArrayMap<Object, DependencyProvider>

private final ArrayMap<Object, DependencyProvider> mProviders = new ArrayMap<>();

所以上面的startServicesIfNeeded方法中Dependency.get(PluginManager.class)得到的是PluginManagerImpl對象骏全,調(diào)用了void addPluginListener(PluginListener<T> listener, Class<?> cls,
boolean allowMultiple)方法,實際調(diào)用到addPluginListener(String action, PluginListener<T> listener,
Class cls, boolean allowMultiple)方法

    public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
            Class cls, boolean allowMultiple) {
        if (!isDebuggable) {
            // Never ever ever allow these on production builds, they are only for prototyping.
            return;
        }
        mPluginPrefs.addAction(action);
        PluginInstanceManager p = mFactory.createPluginInstanceManager(mContext, action, listener,
                allowMultiple, mLooper, cls, this);
        p.loadAll();
        mPluginMap.put(listener, p);
        startListening();
    }

這個action是調(diào)用PluginManager.getAction(cls)得到的
getAction@PluginManager

static <P> String getAction(Class<P> cls) {
        ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class);
        if (info == null) {
            throw new RuntimeException(cls + " doesn't provide an interface");
        }
        if (TextUtils.isEmpty(info.action())) {
            throw new RuntimeException(cls + " doesn't provide an action");
        }
        return info.action();
    }

實際上是獲取類的注解
PluginManagerImpl將listener尼斧,PluginInstanceManager 以鍵值對保存起來姜贡,存到mPluginMap中。
看一下startListening()@PluginManagerImpl

    private void startListening() {
        if (mListening) return;
        mListening = true;
        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
        filter.addAction(PLUGIN_CHANGED);
        filter.addAction(DISABLE_PLUGIN);
        filter.addDataScheme("package");
        mContext.registerReceiver(this, filter);
        filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
        mContext.registerReceiver(this, filter);
    }

注冊了廣播監(jiān)聽器棺棵,PluginManagerImpl本身就是一個BroadcastReceiver楼咳,看看其onReceive方法

@Override
    public void onReceive(Context context, Intent intent) {
        if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
            for (PluginInstanceManager manager : mPluginMap.values()) {
                // mPluginMap保存的是listener,PluginInstanceManage鍵值對
                manager.loadAll();
            }
        } else if (DISABLE_PLUGIN.equals(intent.getAction())) {
            Uri uri = intent.getData();
            ComponentName component = ComponentName.unflattenFromString(
                    uri.toString().substring(10));
            mContext.getPackageManager().setComponentEnabledSetting(component,
                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                    PackageManager.DONT_KILL_APP);
            mContext.getSystemService(NotificationManager.class).cancel(component.getClassName(),
                    SystemMessage.NOTE_PLUGIN);
        } else {
            Uri data = intent.getData();
            String pkg = data.getEncodedSchemeSpecificPart();
            if (mOneShotPackages.contains(pkg)) {
                int icon = mContext.getResources().getIdentifier("tuner", "drawable",
                        mContext.getPackageName());
                int color = Resources.getSystem().getIdentifier(
                        "system_notification_accent_color", "color", "android");
                String label = pkg;
                try {
                    PackageManager pm = mContext.getPackageManager();
                    label = pm.getApplicationInfo(pkg, 0).loadLabel(pm).toString();
                } catch (NameNotFoundException e) {
                }
                // Localization not required as this will never ever appear in a user build.
                final Notification.Builder nb =
                        new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
                                .setSmallIcon(icon)
                                .setWhen(0)
                                .setShowWhen(false)
                                .setPriority(Notification.PRIORITY_MAX)
                                .setVisibility(Notification.VISIBILITY_PUBLIC)
                                .setColor(mContext.getColor(color))
                                .setContentTitle("Plugin \"" + label + "\" has updated")
                                .setContentText("Restart SysUI for changes to take effect.");
                Intent i = new Intent("com.android.systemui.action.RESTART").setData(
                            Uri.parse("package://" + pkg));
                PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i, 0);
                nb.addAction(new Action.Builder(null, "Restart SysUI", pi).build());
                mContext.getSystemService(NotificationManager.class).notifyAsUser(pkg,
                        SystemMessage.NOTE_PLUGIN, nb.build(), UserHandle.ALL);
            }
            if (clearClassLoader(pkg)) {
                Toast.makeText(mContext, "Reloading " + pkg, Toast.LENGTH_LONG).show();
            }
            if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
                for (PluginInstanceManager manager : mPluginMap.values()) {
                    // plugin發(fā)生變化
                    manager.onPackageChange(pkg);
                }
            } else {
                for (PluginInstanceManager manager : mPluginMap.values()) {
                    // plugin被移除
                    manager.onPackageRemoved(pkg);
                }
            }
        }
    }

看一下manager.loadAll()

    public void loadAll() {
        if (DEBUG) Log.d(TAG, "startListening");
        mPluginHandler.sendEmptyMessage(PluginHandler.QUERY_ALL);
    }
public void handleMessage(Message msg) {
            switch (msg.what) {
                case QUERY_ALL:
                    if (DEBUG) Log.d(TAG, "queryAll " + mAction);
                    for (int i = mPlugins.size() - 1; i >= 0; i--) {
                        PluginInfo<T> plugin = mPlugins.get(i);
                        mListener.onPluginDisconnected(plugin.mPlugin);
                        if (!(plugin.mPlugin instanceof PluginFragment)) {
                            // Only call onDestroy for plugins that aren't fragments, as fragments
                            // will get the onDestroy as part of the fragment lifecycle.
                            plugin.mPlugin.onDestroy();
                        }
                    }
                    mPlugins.clear();
                    handleQueryPlugins(null);
                    break;
private void handleQueryPlugins(String pkgName) {
            // This isn't actually a service and shouldn't ever be started, but is
            // a convenient PM based way to manage our plugins.
            Intent intent = new Intent(mAction);
            if (pkgName != null) {
                intent.setPackage(pkgName);
            }
            // plugin在AndroidManifest中必須聲明為<service>烛恤,packageManager讀取到所有的service信息
            List<ResolveInfo> result =
                    mPm.queryIntentServices(intent, 0);
            if (DEBUG) Log.d(TAG, "Found " + result.size() + " plugins");
            if (result.size() > 1 && !mAllowMultiple) {
                // TODO: Show warning.
                Log.w(TAG, "Multiple plugins found for " + mAction);
                return;
            }
            for (ResolveInfo info : result) {
                ComponentName name = new ComponentName(info.serviceInfo.packageName,
                        info.serviceInfo.name);
                // 通過ComponentName創(chuàng)建出插件
                PluginInfo<T> t = handleLoadPlugin(name);
                if (t == null) continue;
                mMainHandler.obtainMessage(mMainHandler.PLUGIN_CONNECTED, t).sendToTarget();
                mPlugins.add(t);
            }
        }
 public void handleMessage(Message msg) {
            switch (msg.what) {
                case PLUGIN_CONNECTED:
                    if (DEBUG) Log.d(TAG, "onPluginConnected");
                    PluginPrefs.setHasPlugins(mContext);
                    PluginInfo<T> info = (PluginInfo<T>) msg.obj;
                    mManager.handleWtfs();
                    if (!(msg.obj instanceof PluginFragment)) {
                        // Only call onDestroy for plugins that aren't fragments, as fragments
                        // will get the onCreate as part of the fragment lifecycle.
                        info.mPlugin.onCreate(mContext, info.mPluginContext);
                    }
                    mListener.onPluginConnected(info.mPlugin, info.mPluginContext);
                    break;

終于看到了onPluginConnected的回調(diào)母怜。
這里有個關(guān)鍵的方法handleLoadPlugin,這個方法創(chuàng)建出插件并且返回PluginInfo

protected PluginInfo<T> handleLoadPlugin(ComponentName component) {
            // This was already checked, but do it again here to make extra extra sure, we don't
            // use these on production builds.
            if (!isDebuggable) {
                // Never ever ever allow these on production builds, they are only for prototyping.
                Log.d(TAG, "Somehow hit second debuggable check");
                return null;
            }
            String pkg = component.getPackageName();
            String cls = component.getClassName();
            try {
                ApplicationInfo info = mPm.getApplicationInfo(pkg, 0);
                // TODO: This probably isn't needed given that we don't have IGNORE_SECURITY on
                if (mPm.checkPermission(PLUGIN_PERMISSION, pkg)
                        != PackageManager.PERMISSION_GRANTED) {
                    Log.d(TAG, "Plugin doesn't have permission: " + pkg);
                    return null;
                }
                // Create our own ClassLoader so we can use our own code as the parent.
                // 構(gòu)造類加載器和Context缚柏,類加載器加載插件的代碼苹熏,Context加載插件的資源。
                ClassLoader classLoader = mManager.getClassLoader(info.sourceDir, info.packageName);
                Context pluginContext = new PluginContextWrapper(
                        mContext.createApplicationContext(info, 0), classLoader);
                Class<?> pluginClass = Class.forName(cls, true, classLoader);
                // TODO: Only create the plugin before version check if we need it for
                // legacy version check.
                T plugin = (T) pluginClass.newInstance();
                try {
                    VersionInfo version = checkVersion(pluginClass, plugin, mVersion);
                    if (DEBUG) Log.d(TAG, "createPlugin");
                    return new PluginInfo(pkg, cls, plugin, pluginContext, version);
                } catch (InvalidVersionException e) {
                    final int icon = mContext.getResources().getIdentifier("tuner", "drawable",
                            mContext.getPackageName());
                    final int color = Resources.getSystem().getIdentifier(
                            "system_notification_accent_color", "color", "android");
                    final Notification.Builder nb = new Notification.Builder(mContext,
                            PluginManager.NOTIFICATION_CHANNEL_ID)
                                    .setStyle(new Notification.BigTextStyle())
                                    .setSmallIcon(icon)
                                    .setWhen(0)
                                    .setShowWhen(false)
                                    .setVisibility(Notification.VISIBILITY_PUBLIC)
                                    .setColor(mContext.getColor(color));
                    String label = cls;
                    try {
                        label = mPm.getServiceInfo(component, 0).loadLabel(mPm).toString();
                    } catch (NameNotFoundException e2) {
                    }
                    if (!e.isTooNew()) {
                        // Localization not required as this will never ever appear in a user build.
                        nb.setContentTitle("Plugin \"" + label + "\" is too old")
                                .setContentText("Contact plugin developer to get an updated"
                                        + " version.\n" + e.getMessage());
                    } else {
                        // Localization not required as this will never ever appear in a user build.
                        nb.setContentTitle("Plugin \"" + label + "\" is too new")
                                .setContentText("Check to see if an OTA is available.\n"
                                        + e.getMessage());
                    }
                    Intent i = new Intent(PluginManagerImpl.DISABLE_PLUGIN).setData(
                            Uri.parse("package://" + component.flattenToString()));
                    PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i, 0);
                    nb.addAction(new Action.Builder(null, "Disable plugin", pi).build());
                    mContext.getSystemService(NotificationManager.class)
                            .notifyAsUser(cls, SystemMessage.NOTE_PLUGIN, nb.build(),
                                    UserHandle.ALL);
                    // TODO: Warn user.
                    Log.w(TAG, "Plugin has invalid interface version " + plugin.getVersion()
                            + ", expected " + mVersion);
                    return null;
                }
            } catch (Throwable e) {
                Log.w(TAG, "Couldn't load plugin: " + pkg, e);
                return null;
            }
        }

比較重要的兩行

 // 構(gòu)造類加載器和Context,類加載器加載插件的代碼轨域,Context加載插件的資源袱耽。
ClassLoader classLoader = mManager.getClassLoader(info.sourceDir, info.packageName);
Context pluginContext = new PluginContextWrapper(
                        mContext.createApplicationContext(info, 0), classLoader);

這里的mManager就是PluginManagerImpl,看看它的getClassLoader

    public ClassLoader getClassLoader(String sourceDir, String pkg) {
        if (mClassLoaders.containsKey(pkg)) {
            return mClassLoaders.get(pkg);
        }
        ClassLoader classLoader = new PathClassLoader(sourceDir, getParentClassLoader());
        mClassLoaders.put(pkg, classLoader);
        return classLoader;
    }

默認(rèn)采用的是PathClassLoader來加載插件的代碼

    @Override
    public Context createApplicationContext(ApplicationInfo application, int flags)
            throws NameNotFoundException {
        LoadedApk pi = mMainThread.getPackageInfo(application, mResources.getCompatibilityInfo(),
                flags | CONTEXT_REGISTER_PACKAGE);
        if (pi != null) {
            ContextImpl c = new ContextImpl(this, mMainThread, pi, null, mActivityToken,
                    new UserHandle(UserHandle.getUserId(application.uid)), flags, null);

            final int displayId = mDisplay != null
                    ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;

            c.setResources(createResources(mActivityToken, pi, null, displayId, null,
                    getDisplayAdjustments(displayId).getCompatibilityInfo()));
            if (c.mResources != null) {
                return c;
            }
        }

        throw new PackageManager.NameNotFoundException(
                "Application package " + application.packageName + " not found");
    }

c.setResources干发,可以看到Context中包含了插件的資源信息朱巨。最后封裝成一個PluginContextWrapper,構(gòu)造出pluginContext铐然,然后再和plugin一起封裝成PluginInfo返回蔬崩。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市搀暑,隨后出現(xiàn)的幾起案子沥阳,更是在濱河造成了極大的恐慌,老刑警劉巖自点,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件桐罕,死亡現(xiàn)場離奇詭異,居然都是意外死亡桂敛,警方通過查閱死者的電腦和手機(jī)功炮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來术唬,“玉大人薪伏,你說我怎么就攤上這事〈植郑” “怎么了嫁怀?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長借浊。 經(jīng)常有香客問我塘淑,道長,這世上最難降的妖魔是什么蚂斤? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任存捺,我火速辦了婚禮,結(jié)果婚禮上曙蒸,老公的妹妹穿的比我還像新娘捌治。我一直安慰自己,他們只是感情好纽窟,可當(dāng)我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布具滴。 她就那樣靜靜地躺著,像睡著了一般师倔。 火紅的嫁衣襯著肌膚如雪构韵。 梳的紋絲不亂的頭發(fā)上周蹭,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機(jī)與錄音疲恢,去河邊找鬼凶朗。 笑死,一個胖子當(dāng)著我的面吹牛显拳,可吹牛的內(nèi)容都是我干的棚愤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼杂数,長吁一口氣:“原來是場噩夢啊……” “哼宛畦!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起揍移,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤次和,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后那伐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體踏施,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年罕邀,在試婚紗的時候發(fā)現(xiàn)自己被綠了畅形。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡诉探,死狀恐怖日熬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情肾胯,我是刑警寧澤竖席,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站阳液,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏揣炕。R本人自食惡果不足惜帘皿,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望畸陡。 院中可真熱鬧鹰溜,春花似錦、人聲如沸丁恭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽牲览。三九已至墓陈,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背贡必。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工兔港, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人仔拟。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓衫樊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親利花。 傳聞我的和親對象是個殘疾皇子科侈,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容