Settings: android 组件如何响应语言变化

这里所说的 android 组件,主要是指 android 中 Activity、Service、ContentProvider 以及 BroadcastReceiver.


在 android 源码开发的过程中,大家拿到手的都是一样的 android 源码,但是硬件平台却是大相径庭,所以会引发各种各样的问题,于是乎,android 开发越发精彩!


这篇博客主要是在研究 Settings 源码时所激发的,把自己的经验拿出来分享一番!


我在设置语言之后,发现有些地方的语言还是没有改变,这个时候想起了 onConfigurationChanged 方法,先来看看这个方法。


public interface ComponentCallbacks


这个接口包括两个方法,其中一个就是onConfigurationChanged 方法。

Activity、Service、ContentProvider 都实现了这个接口,所以在代码中,我们可以重写这个方法,便于回调处理。那麽,这个方法何时才会被回调呢?


abstract voidonConfigurationChanged(Configuration newConfig)
Called by the system when the device configuration changes while your component is running.


设备配置发生变化的时候,就会回调。你可能实在憋不住要问,设备配置指哪些?

android:configChanges=["mcc", "mnc", "locale",
                      "touchscreen", "keyboard", "keyboardHidden",
                      "navigation", "screenLayout", "fontScale", "uiMode",
                      "orientation", "screenSize", "smallestScreenSize"]

这里需要提醒一下,如果使用 Activity 配合 onConfigurationChanged 方法,需要在其 menifest.xml 中添加:

android:configChanges 属性。


所以,如果你有需要可以在上面的三大组件中重写该方法,做你自己的逻辑处理!


如果,在 Settings 里面改变语言之后,在我们其它的 App 中可以注册某个广播就可以接收到这种变化,就更好了!

恩,当然可以!


注册一个广播:
IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
        registerReceiver(receiver, filter);

接收广播:

private BroadcastReceiver receiver = new BroadcastReceiver() {
        
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if("android.intent.action.CONFIGURATION_CHANGED".equals(action)) {
                // to do
            }
        }
    };

别忘记在合适的位置取消注册:

unregisterReceiver(receiver);


这样做的话,要比直接重写onConfigurationChanged 方法,更加灵活,因为有些没有实现这个接口的android组件一大把,但是接收广播对于大多数组件来说还还是比较简单的!在以后的博客中,我和大家交流一下关于自定义的View如何接收广播!


关于 Intent.ACTION_CONFIGURATION_CHANGED,可以参考 sdk 文档。


有的人说,我现在只关心设备语言变化的那个 action,到底有木有?有!


Intent.ACTION_LOCALE_CHANGED


如果你有兴趣,可以参考 /frameworks/base/services/java/com/android/server/am/ActivityManagerService.java,关键代码如下:

 /**
     * Do either or both things: (1) change the current configuration, and (2)
     * make sure the given activity is running with the (now) current
     * configuration.  Returns true if the activity has been left running, or
     * false if <var>starting</var> is being destroyed to match the new
     * configuration.
     */
    public boolean updateConfigurationLocked(Configuration values,
            ActivityRecord starting) {
        int changes = 0;
        
        boolean kept = true;
        
        if (values != null) {
            Configuration newConfig = new Configuration(mConfiguration);
            changes = newConfig.updateFrom(values);
            if (changes != 0) {
                if (DEBUG_SWITCH || DEBUG_CONFIGURATION) {
                    Slog.i(TAG, "Updating configuration to: " + values);
                }
                
                EventLog.writeEvent(EventLogTags.CONFIGURATION_CHANGED, changes);

                if (values.locale != null) {
                    saveLocaleLocked(values.locale, 
                                     !values.locale.equals(mConfiguration.locale),
                                     values.userSetLocale);
                }

                mConfigurationSeq++;
                if (mConfigurationSeq <= 0) {
                    mConfigurationSeq = 1;
                }
                newConfig.seq = mConfigurationSeq;
                mConfiguration = newConfig;
                Slog.i(TAG, "Config changed: " + newConfig);
                
                AttributeCache ac = AttributeCache.instance();
                if (ac != null) {
                    ac.updateConfiguration(mConfiguration);
                }

                if (Settings.System.hasInterestingConfigurationChanges(changes)) {
                    Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG);
                    msg.obj = new Configuration(mConfiguration);
                    mHandler.sendMessage(msg);
                }
        
                for (int i=mLruProcesses.size()-1; i>=0; i--) {
                    ProcessRecord app = mLruProcesses.get(i);
                    try {
                        if (app.thread != null) {
                            if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending to proc "
                                    + app.processName + " new config " + mConfiguration);
                            app.thread.scheduleConfigurationChanged(mConfiguration);
                        }
                    } catch (Exception e) {
                    }
                }
                Intent intent = new Intent(Intent.ACTION_CONFIGURATION_CHANGED);
                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                        | Intent.FLAG_RECEIVER_REPLACE_PENDING);
                broadcastIntentLocked(null, null, intent, null, null, 0, null, null,
                        null, false, false, MY_PID, Process.SYSTEM_UID);
                if ((changes&ActivityInfo.CONFIG_LOCALE) != 0) {
                    broadcastIntentLocked(null, null,
                            new Intent(Intent.ACTION_LOCALE_CHANGED),
                            null, null, 0, null, null,
                            null, false, false, MY_PID, Process.SYSTEM_UID);
                }
            }
        }
        
        if (changes != 0 && starting == null) {
            // If the configuration changed, and the caller is not already
            // in the process of starting an activity, then find the top
            // activity to check if its configuration needs to change.
            starting = mMainStack.topRunningActivityLocked(null);
        }
        
        if (starting != null) {
            kept = mMainStack.ensureActivityConfigurationLocked(starting, changes);
            if (kept) {
                // If this didn't result in the starting activity being
                // destroyed, then we need to make sure at this point that all
                // other activities are made visible.
                if (DEBUG_SWITCH) Slog.i(TAG, "Config didn't destroy " + starting
                        + ", ensuring others are correct.");
                mMainStack.ensureActivitiesVisibleLocked(starting, changes);
            }
        }
        
        if (values != null && mWindowManager != null) {
            mWindowManager.setNewConfiguration(mConfiguration);
        }
        
        return kept;
    }

代码中有关于,上面提到的两个 Intent Action.


另外,保存设置 locale 信息的源码:

  /**
     * Save the locale.  You must be inside a synchronized (this) block.
     */
    private void saveLocaleLocked(Locale l, boolean isDiff, boolean isPersist) {
        if(isDiff) {
            SystemProperties.set("user.language", l.getLanguage());
            SystemProperties.set("user.region", l.getCountry());
        } 

        if(isPersist) {
            SystemProperties.set("persist.sys.language", l.getLanguage());
            SystemProperties.set("persist.sys.country", l.getCountry());
            SystemProperties.set("persist.sys.localevar", l.getVariant());
        }
    }


android 系统会发出很多广播,但是这麽多 action,如何记住?不需要记,好好看看 Intent 类的常量定义,那里面讲的很清楚,如果你足够勤奋和钻研,这些就不是问题了!







©️2020 CSDN 皮肤主题: 酷酷鲨 设计师:CSDN官方博客 返回首页