首页 > 代码库 > android 实现输入法的国际化
android 实现输入法的国际化
项目中有这么一个bug,即在切换语言后输入法没有实现国际化,只有重启设备输入法中的语言才会变过来即为正确的语言,后来经过下面的分析发现只要自己重启输入发服务就ok了,那么为什么会ok呢?下面已经说明
先看我们怎么实现所有Activity展示的国际化,正常我们不会在原生态的setting中去实现,因为多数现在都是定制,我们也是,下面是我们自己的设置应用的语言切换实现功能代码:
try { Class activityManagerNative = Class.forName("android.app.ActivityManagerNative"); Object am = activityManagerNative.getMethod("getDefault").invoke(activityManagerNative); Object config = am.getClass().getMethod("getConfiguration").invoke(am); config.getClass().getDeclaredField("locale").set(config, language); config.getClass().getDeclaredField("userSetLocale").setBoolean(config, true); am.getClass().getMethod("updateConfiguration", android.content.res.Configuration.class).invoke(am, config); } catch (Exception e) { }
从上面可以看出,我们是通过反射的方法来调用了如下方法:
IActivityManager am = ActivityManagerNative.getDefault(); Configuration config = am.getConfiguration(); config.locale = locale; config.userSetLocale = true; am.updateConfiguration(config);我们先获取到ActivityManagerNative的getDefault()对象,跟踪下这个代码
/**
* Retrieve the system‘s default/global activity manager.
*/
static public IActivityManager getDefault() {
return gDefault.get();
}
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() { protected IActivityManager create() { IBinder b = ServiceManager.getService("activity"); if (false) { Log.v("ActivityManager", "default service binder = " + b); } IActivityManager am = asInterface(b); if (false) { Log.v("ActivityManager", "default service = " + am); } return am; } };
那么ServiceManager.getService("activity");获取的是什么对象呢?
在ActivityManagerService中有如下的定义:
public static void setSystemProcess() { try { ActivityManagerService m = mSelf; ServiceManager.addService("activity", m);
从上面来看一个去addService一个去get,而get的对象就是ActivityManagerService,当然这里我是省事直接找到的,其实这个ActivityManagerService从启动到更改进程名称有个流程的,好吧,我们来看看ActivityManagerService的updateConfiguration(Configuration values)方法
public void updateConfiguration(Configuration values) { enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION, "updateConfiguration()"); synchronized(this) { if (values == null && mWindowManager != null) { // sentinel: fetch the current configuration from the window manager values = mWindowManager.computeNewConfiguration(); } if (mWindowManager != null) { mProcessList.applyDisplaySize(mWindowManager); } final long origId = Binder.clearCallingIdentity(); if (values != null) { Settings.System.clearConfiguration(values); } updateConfigurationLocked(values, null, false, false); Binder.restoreCallingIdentity(origId); } }上面的enforceCallingPermission方法进行权限验证,重点看updateConfigurationLocked(values, null, false, false);
/** * 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. * @param persistent TODO */ public boolean updateConfigurationLocked(Configuration values, ActivityRecord starting, boolean persistent, boolean initLocale) { 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 && !initLocale) { 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); final Configuration configCopy = new Configuration(mConfiguration); AttributeCache ac = AttributeCache.instance(); if (ac != null) { ac.updateConfiguration(configCopy); } // Make sure all resources in our process are updated // right now, so that anyone who is going to retrieve // resource values after we return will be sure to get // the new ones. This is especially important during // boot, where the first config change needs to guarantee // all resources have that config before following boot // code is executed. mSystemThread.applyConfigurationToResources(configCopy); if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) { Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG); msg.obj = new Configuration(configCopy); 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(configCopy); } } 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); // And we need to make sure at this point that all other activities // are made visible with the correct configuration. mMainStack.ensureActivitiesVisibleLocked(starting, changes); } if (values != null && mWindowManager != null) { mWindowManager.setNewConfiguration(mConfiguration); } return kept; }
上面这个方法有点代码行比较多,我们先来看一下的注释,翻译下,呵呵,如下
(1)更改当前配置,通俗讲就是让改变的configuration更新到当前configuration
(2)确保所有正在运行的activity都能更新改变后的configuration
注释还是比较清晰的,我们重点看下面这个方法
for (int i=mLruProcesses.size()-1; i>=0; i--) { ProcessRecord app = mLruProcesses.get(i); try { if (app.thread != null) { if (DEBUG_CONFIGURATION) Slog.v("PateoConfig", "Sending to proc " + app.processName + " new config " + mConfiguration); app.thread.scheduleConfigurationChanged(configCopy); } } catch (Exception e) { } }
/** * List of running applications, sorted by recent usage. * The first entry in the list is the least recently used. * It contains ApplicationRecord objects. This list does NOT include * any persistent application records (since we never want to exit them). */ final ArrayList<ProcessRecord> mLruProcesses = new ArrayList<ProcessRecord>();
从上面的注释来看,mLruProcesses保存所有运行过的进程. 一个apk文件运行时会对应一个进程. app.thread. 此处的thread代表的是什么呢?
IApplicationThread thread;
public abstract class ApplicationThreadNative extends Binder implements IApplicationThread {
从上面的实现来看是ApplicationThreadNative,好吧,我们进入该ApplicationThreadNative的scheduleConfigurationChanged方法:
public final void scheduleConfigurationChanged(Configuration config) throws RemoteException { Parcel data = http://www.mamicode.com/Parcel.obtain();>看看这个msg消息的处理
case SCHEDULE_CONFIGURATION_CHANGED_TRANSACTION: { data.enforceInterface(IApplicationThread.descriptor); Configuration config = Configuration.CREATOR.createFromParcel(data); scheduleConfigurationChanged(config); return true; }void scheduleConfigurationChanged(Configuration config) throws RemoteException;找到它的实现:private class ApplicationThread extends ApplicationThreadNative { ...... public void scheduleConfigurationChanged(Configuration config) { updatePendingConfiguration(config); queueOrSendMessage(H.CONFIGURATION_CHANGED, config); } ...... }
来看看CONFIGURATION_CHANGED消息是怎么处理的case CONFIGURATION_CHANGED: handleConfigurationChanged((Configuration)msg.obj, null); break;final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) { ArrayList<ComponentCallbacks2> callbacks = null; synchronized (mPackages) { if (mPendingConfiguration != null) { if (!mPendingConfiguration.isOtherSeqNewer(config)) { config = mPendingConfiguration; } mPendingConfiguration = null; } if (config == null) { return; } if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle configuration changed: " + config); applyConfigurationToResourcesLocked(config, compat); if (mConfiguration == null) { mConfiguration = new Configuration(); } if (!mConfiguration.isOtherSeqNewer(config) && compat == null) { return; } mConfiguration.updateFrom(config); config = applyCompatConfiguration(); callbacks = collectComponentCallbacksLocked(false, config); } // Cleanup hardware accelerated stuff WindowManagerImpl.getDefault().trimLocalMemory(); if (callbacks != null) { final int N = callbacks.size(); for (int i=0; i<N; i++) { performConfigurationChanged(callbacks.get(i), config); } } }
我们先来看applyConfigurationToResourcesLocked(config, compat);final boolean applyConfigurationToResourcesLocked(Configuration config, CompatibilityInfo compat) { if (DEBUG_CONFIGURATION) Slog.v("PateoConfig","ActivityThread class ,applyConfigurationToResourcesLocked coming"); if (mResConfiguration == null) { mResConfiguration = new Configuration(); } if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) { if (DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq=" + mResConfiguration.seq + ", newSeq=" + config.seq); return false; } int changes = mResConfiguration.updateFrom(config); DisplayMetrics dm = getDisplayMetricsLocked(null, true); if (compat != null && (mResCompatibilityInfo == null || !mResCompatibilityInfo.equals(compat))) { mResCompatibilityInfo = compat; changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_SCREEN_SIZE | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; } // set it for java, this also affects newly created Resources if (config.locale != null) { Locale.setDefault(config.locale); } Resources.updateSystemConfiguration(config, dm, compat); ApplicationPackageManager.configurationChanged(); //Slog.i(TAG, "Configuration changed in " + currentPackageName()); Iterator<WeakReference<Resources>> it = mActiveResources.values().iterator(); //Iterator<Map.Entry<String, WeakReference<Resources>>> it = // mActiveResources.entrySet().iterator(); while (it.hasNext()) { WeakReference<Resources> v = it.next(); Resources r = v.get(); if (r != null) { if (DEBUG_CONFIGURATION) Slog.v("PateoConfig", "ActivityThread class ,Changing resources " + r + " config to: " + config); r.updateConfiguration(config, dm, compat); //Slog.i(TAG, "Updated app resources " + v.getKey() // + " " + r + ": " + r.getConfiguration()); } else { //Slog.i(TAG, "Removing old resources " + v.getKey()); it.remove(); } } return changes != 0; }
Resources.updateSystemConfiguration()清除一部分系统资源, 并且将config更新到Resources, 而Resources包含了一个AssetManager对象, 该对象的核心实现是在AssetManager.cpp中完成的. 然后循环清空mActivityResources资源. 再回到handleConfigurationChanged()函数,执行下面的方法callbacks = collectComponentCallbacksLocked(false, config);ArrayList<ComponentCallbacks2> collectComponentCallbacksLocked( boolean allActivities, Configuration newConfig) { ArrayList<ComponentCallbacks2> callbacks = new ArrayList<ComponentCallbacks2>(); if (mActivities.size() > 0) { Iterator<ActivityClientRecord> it = mActivities.values().iterator(); while (it.hasNext()) { ActivityClientRecord ar = it.next(); Activity a = ar.activity; if (a != null) { Configuration thisConfig = applyConfigCompatMainThread(newConfig, ar.packageInfo.mCompatibilityInfo.getIfNeeded()); if (!ar.activity.mFinished && (allActivities || (a != null && !ar.paused))) { // If the activity is currently resumed, its configuration // needs to change right now. callbacks.add(a); } else if (thisConfig != null) { // Otherwise, we will tell it about the change // the next time it is resumed or shown. Note that // the activity manager may, before then, decide the // activity needs to be destroyed to handle its new // configuration. if (DEBUG_CONFIGURATION) Slog.v(TAG, "Setting activity " + ar.activityInfo.name + " newConfig=" + thisConfig); ar.newConfig = thisConfig; } } } } if (mServices.size() > 0) { Iterator<Service> it = mServices.values().iterator(); while (it.hasNext()) { callbacks.add(it.next()); } } synchronized (mProviderMap) { if (mLocalProviders.size() > 0) { Iterator<ProviderClientRecord> it = mLocalProviders.values().iterator(); while (it.hasNext()) { callbacks.add(it.next().mLocalProvider); } } } final int N = mAllApplications.size(); for (int i=0; i<N; i++) { callbacks.add(mAllApplications.get(i)); } return callbacks; }
从上来看callbacks是Activity等被作为参数传入if (callbacks != null) { final int N = callbacks.size(); for (int i=0; i<N; i++) { performConfigurationChanged(callbacks.get(i), config); } }
进入performConfigurationChanged方法private final void performConfigurationChanged( ComponentCallbacks2 cb, Configuration config) { // Only for Activity objects, check that they actually call up to their // superclass implementation. ComponentCallbacks2 is an interface, so // we check the runtime type and act accordingly. Activity activity = (cb instanceof Activity) ? (Activity) cb : null; if (activity != null) { activity.mCalled = false; } boolean shouldChangeConfig = false; if ((activity == null) || (activity.mCurrentConfig == null)) { shouldChangeConfig = true; } else { // If the new config is the same as the config this Activity // is already running with then don‘t bother calling // onConfigurationChanged int diff = activity.mCurrentConfig.diff(config); if (diff != 0) { // If this activity doesn‘t handle any of the config changes // then don‘t bother calling onConfigurationChanged as we‘re // going to destroy it. if ((~activity.mActivityInfo.getRealConfigChanged() & diff) == 0) { shouldChangeConfig = true; } } } if (DEBUG_CONFIGURATION) Slog.v(TAG, "Config callback " + cb + ": shouldChangeConfig=" + shouldChangeConfig); if (shouldChangeConfig) { cb.onConfigurationChanged(config); if (activity != null) { if (!activity.mCalled) { throw new SuperNotCalledException( "Activity " + activity.getLocalClassName() + " did not call through to super.onConfigurationChanged()"); } activity.mConfigChangeFlags = 0; activity.mCurrentConfig = new Configuration(config); } } }上面判断configuration是否改变, 如果改变那么shouldChangeConfig为true. 然后调用activity的onConfigurationChange(config);/** * Called by the system when the device configuration changes while your * activity is running. Note that this will <em>only</em> be called if * you have selected configurations you would like to handle with the * {@link android.R.attr#configChanges} attribute in your manifest. If * any configuration change occurs that is not selected to be reported * by that attribute, then instead of reporting it the system will stop * and restart the activity (to have it launched with the new * configuration). * * <p>At the time that this function has been called, your Resources * object will have been updated to return resource values matching the * new configuration. * * @param newConfig The new device configuration. */ public void onConfigurationChanged(Configuration newConfig) { mCalled = true; mFragments.dispatchConfigurationChanged(newConfig); if (mWindow != null) { // Pass the configuration changed event to the window mWindow.onConfigurationChanged(newConfig); } if (mActionBar != null) { // Do this last; the action bar will need to access // view changes from above. mActionBar.onConfigurationChanged(newConfig); } }
看上面的注释意思:如果你的activity运行 , 设备信息有改变(即configuration改变)时由系统调用. 如果你在manifest.xml中配置了configChnages属性则表示有你自己来处理configuration change. 否则就重启当前这个activity. 而重启之前, 旧的resources已经被清空, 那么就会装载新的资源, 整个过程就完成了语言切换后 , 能够让所有app使用新的语言.
那么我们要问真正实现重启Activity的代码在哪呢,我们回到ActivityManagerService的updateConfigurationLocked方法,在我们分析完app.thread.scheduleConfigurationChanged(configCopy);的代码后,其实在app.thread.scheduleConfigurationChanged(configCopy);该代码下面即在updateConfigurationLocked方法内,有这么一行代码
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);
主要是kept = mMainStack.ensureActivityConfigurationLocked(starting, changes);我们进入该方法/** * Make sure the given activity matches the current configuration. Returns * false if the activity had to be destroyed. Returns true if the * configuration is the same, or the activity will remain running as-is * for whatever reason. Ensures the HistoryRecord is updated with the * correct configuration and all other bookkeeping is handled. */ final boolean ensureActivityConfigurationLocked(ActivityRecord r, int globalChanges) { if (mConfigWillChange) { if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping config check (will change): " + r); return true; } if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, "Ensuring correct configuration: " + r); // Short circuit: if the two configurations are the exact same // object (the common case), then there is nothing to do. Configuration newConfig = mService.mConfiguration; if (r.configuration == newConfig && !r.forceNewConfig) { if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, "Configuration unchanged in " + r); return true; } // We don‘t worry about activities that are finishing. if (r.finishing) { if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, "Configuration doesn‘t matter in finishing " + r); r.stopFreezingScreenLocked(false); return true; } // Okay we now are going to make this activity have the new config. // But then we need to figure out how it needs to deal with that. Configuration oldConfig = r.configuration; r.configuration = newConfig; // Determine what has changed. May be nothing, if this is a config // that has come back from the app after going idle. In that case // we just want to leave the official config object now in the // activity and do nothing else. final int changes = oldConfig.diff(newConfig); if (changes == 0 && !r.forceNewConfig) { if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, "Configuration no differences in " + r); return true; } // If the activity isn‘t currently running, just leave the new // configuration and it will pick that up next time it starts. if (r.app == null || r.app.thread == null) { if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, "Configuration doesn‘t matter not running " + r); r.stopFreezingScreenLocked(false); r.forceNewConfig = false; return true; } // Figure out how to handle the changes between the configurations. if (DEBUG_SWITCH || DEBUG_CONFIGURATION) { Slog.v(TAG, "Checking to restart " + r.info.name + ": changed=0x" + Integer.toHexString(changes) + ", handles=0x" + Integer.toHexString(r.info.getRealConfigChanged()) + ", newConfig=" + newConfig); } if ((changes&(~r.info.getRealConfigChanged())) != 0 || r.forceNewConfig) { // Aha, the activity isn‘t handling the change, so DIE DIE DIE. r.configChangeFlags |= changes; r.startFreezingScreenLocked(r.app, globalChanges); r.forceNewConfig = false; if (r.app == null || r.app.thread == null) { if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, "Switch is destroying non-running " + r); destroyActivityLocked(r, true, false, "config"); } else if (r.state == ActivityState.PAUSING) { // A little annoying: we are waiting for this activity to // finish pausing. Let‘s not do anything now, but just // flag that it needs to be restarted when done pausing. if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, "Switch is skipping already pausing " + r); r.configDestroy = true; return true; } else if (r.state == ActivityState.RESUMED) { // Try to optimize this case: the configuration is changing // and we need to restart the top, resumed activity. // Instead of doing the normal handshaking, just say // "restart!". if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, "Switch is restarting resumed " + r); relaunchActivityLocked(r, r.configChangeFlags, true); r.configChangeFlags = 0; } else { if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, "Switch is restarting non-resumed " + r); relaunchActivityLocked(r, r.configChangeFlags, false); r.configChangeFlags = 0; } // All done... tell the caller we weren‘t able to keep this // activity around. return false; } // Default case: the activity can handle this new configuration, so // hand it over. Note that we don‘t need to give it the new // configuration, since we always send configuration changes to all // process when they happen so it can just use whatever configuration // it last got. if (r.app != null && r.app.thread != null) { try { if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending new config to " + r); r.app.thread.scheduleActivityConfigurationChanged(r.appToken); } catch (RemoteException e) { // If process died, whatever. } } r.stopFreezingScreenLocked(false); return true; }
我们会发现上面的代码有日志输出V/ActivityManager( 1265): Switch is restarting resumed ActivityRecord{41ad1e90 com.pateo.as.settings/.activity.SettingActivity} V/ActivityManager( 1265): Relaunching: ActivityRecord{41ad1e90 com.pateo.as.settings/.activity.SettingActivity} with results=null newIntents=null andResume=true I/ActivityManager( 1265): Switch is restarting resumed ActivityRecord{41ad1e90 com.pateo.as.settings/.activity.SettingActivity}
根据输出日志我们来看下,是走入了下面这个方法:relaunchActivityLocked(r, r.configChangeFlags, true);private final boolean relaunchActivityLocked(ActivityRecord r, int changes, boolean andResume) { List<ResultInfo> results = null; List<Intent> newIntents = null; if (andResume) { results = r.results; newIntents = r.newIntents; } if (DEBUG_SWITCH) Slog.v(TAG, "Relaunching: " + r + " with results=" + results + " newIntents=" + newIntents + " andResume=" + andResume); EventLog.writeEvent(andResume ? EventLogTags.AM_RELAUNCH_RESUME_ACTIVITY : EventLogTags.AM_RELAUNCH_ACTIVITY, System.identityHashCode(r), r.task.taskId, r.shortComponentName); r.startFreezingScreenLocked(r.app, 0); try { if (DEBUG_SWITCH) Slog.i(TAG, "Switch is restarting resumed " + r); r.forceNewConfig = false; r.app.thread.scheduleRelaunchActivity(r.appToken, results, newIntents, changes, !andResume, new Configuration(mService.mConfiguration)); // Note: don‘t need to call pauseIfSleepingLocked() here, because // the caller will only pass in ‘andResume‘ if this activity is // currently resumed, which implies we aren‘t sleeping. } catch (RemoteException e) { return false; } if (andResume) { r.results = null; r.newIntents = null; if (mMainStack) { mService.reportResumedActivityLocked(r); } } return true; }
上面主要的调用了r.app.thread.scheduleRelaunchActivity(r.appToken, results, newIntents, changes, !andResume, new Configuration(mService.mConfiguration));
我们来看看这个scheduleRelaunchActivity方法,是public interface IApplicationThread类中,其实现在ActivityThread中public final void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults, List<Intent> pendingNewIntents, int configChanges, boolean notResumed, Configuration config) { requestRelaunchActivity(token, pendingResults, pendingNewIntents, configChanges, notResumed, config, true); }public final void requestRelaunchActivity(IBinder token, List<ResultInfo> pendingResults, List<Intent> pendingNewIntents, int configChanges, boolean notResumed, Configuration config, boolean fromServer) { ActivityClientRecord target = null; synchronized (mPackages) { for (int i=0; i<mRelaunchingActivities.size(); i++) { ActivityClientRecord r = mRelaunchingActivities.get(i); if (r.token == token) { target = r; if (pendingResults != null) { if (r.pendingResults != null) { r.pendingResults.addAll(pendingResults); } else { r.pendingResults = pendingResults; } } if (pendingNewIntents != null) { if (r.pendingIntents != null) { r.pendingIntents.addAll(pendingNewIntents); } else { r.pendingIntents = pendingNewIntents; } } break; } } if (target == null) { target = new ActivityClientRecord(); target.token = token; target.pendingResults = pendingResults; target.pendingIntents = pendingNewIntents; if (!fromServer) { ActivityClientRecord existing = mActivities.get(token); if (existing != null) { target.startsNotResumed = existing.paused; } target.onlyLocalRequest = true; } mRelaunchingActivities.add(target); queueOrSendMessage(H.RELAUNCH_ACTIVITY, target); } if (fromServer) { target.startsNotResumed = notResumed; target.onlyLocalRequest = false; } if (config != null) { target.createdConfig = config; } target.pendingConfigChanges |= configChanges; } }
接着看下消息的处理case RELAUNCH_ACTIVITY: { ActivityClientRecord r = (ActivityClientRecord)msg.obj; handleRelaunchActivity(r); } break;private void handleRelaunchActivity(ActivityClientRecord tmp) { // If we are getting ready to gc after going to the background, well // we are back active so skip it. unscheduleGcIdler(); Configuration changedConfig = null; int configChanges = 0; // First: make sure we have the most recent configuration and most // recent version of the activity, or skip it if some previous call // had taken a more recent version. synchronized (mPackages) { int N = mRelaunchingActivities.size(); IBinder token = tmp.token; tmp = null; for (int i=0; i<N; i++) { ActivityClientRecord r = mRelaunchingActivities.get(i); if (r.token == token) { tmp = r; configChanges |= tmp.pendingConfigChanges; mRelaunchingActivities.remove(i); i--; N--; } } if (tmp == null) { if (DEBUG_CONFIGURATION) Slog.v(TAG, "Abort, activity not relaunching!"); return; } if (DEBUG_CONFIGURATION) Slog.v(TAG, "Relaunching activity " + tmp.token + " with configChanges=0x" + Integer.toHexString(configChanges)); if (mPendingConfiguration != null) { changedConfig = mPendingConfiguration; mPendingConfiguration = null; } } if (tmp.createdConfig != null) { // If the activity manager is passing us its current config, // assume that is really what we want regardless of what we // may have pending. if (mConfiguration == null || (tmp.createdConfig.isOtherSeqNewer(mConfiguration) && mConfiguration.diff(tmp.createdConfig) != 0)) { if (changedConfig == null || tmp.createdConfig.isOtherSeqNewer(changedConfig)) { changedConfig = tmp.createdConfig; } } } if (DEBUG_CONFIGURATION) Slog.v(TAG, "Relaunching activity " + tmp.token + ": changedConfig=" + changedConfig); // If there was a pending configuration change, execute it first. if (changedConfig != null) { handleConfigurationChanged(changedConfig, null); } ActivityClientRecord r = mActivities.get(tmp.token); if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handling relaunch of " + r); if (r == null) { return; } r.activity.mConfigChangeFlags |= configChanges; r.onlyLocalRequest = tmp.onlyLocalRequest; Intent currentIntent = r.activity.mIntent; r.activity.mChangingConfigurations = true; // Need to ensure state is saved. if (!r.paused) { performPauseActivity(r.token, false, r.isPreHoneycomb()); } if (r.state == null && !r.stopped && !r.isPreHoneycomb()) { r.state = new Bundle(); r.state.setAllowFds(false); mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state); } handleDestroyActivity(r.token, false, configChanges, true); r.activity = null; r.window = null; r.hideForNow = false; r.nextIdle = null; // Merge any pending results and pending intents; don‘t just replace them if (tmp.pendingResults != null) { if (r.pendingResults == null) { r.pendingResults = tmp.pendingResults; } else { r.pendingResults.addAll(tmp.pendingResults); } } if (tmp.pendingIntents != null) { if (r.pendingIntents == null) { r.pendingIntents = tmp.pendingIntents; } else { r.pendingIntents.addAll(tmp.pendingIntents); } } r.startsNotResumed = tmp.startsNotResumed; handleLaunchActivity(r, currentIntent); }
上面我们梳理下,调用了如下重要的三个方法1、performPauseActivity(r.token, false, r.isPreHoneycomb());
2、handleDestroyActivity(r.token, false, configChanges, true);
3、handleLaunchActivity(r, currentIntent);
先来看第一个方法performPauseActivity
final Bundle performPauseActivity(IBinder token, boolean finished, boolean saveState) { ActivityClientRecord r = mActivities.get(token); return r != null ? performPauseActivity(r, finished, saveState) : null; }final Bundle performPauseActivity(ActivityClientRecord r, boolean finished, boolean saveState) { if (r.paused) { if (r.activity.mFinished) { // If we are finishing, we won‘t call onResume() in certain cases. // So here we likewise don‘t want to call onPause() if the activity // isn‘t resumed. return null; } RuntimeException e = new RuntimeException( "Performing pause of activity that is not resumed: " + r.intent.getComponent().toShortString()); Slog.e(TAG, e.getMessage(), e); } Bundle state = null; if (finished) { r.activity.mFinished = true; } try { // Next have the activity save its current state and managed dialogs... if (!r.activity.mFinished && saveState) { state = new Bundle(); state.setAllowFds(false); mInstrumentation.callActivityOnSaveInstanceState(r.activity, state); r.state = state; } // Now we are idle. r.activity.mCalled = false; mInstrumentation.callActivityOnPause(r.activity); EventLog.writeEvent(LOG_ON_PAUSE_CALLED, r.activity.getComponentName().getClassName()); if (!r.activity.mCalled) { throw new SuperNotCalledException( "Activity " + r.intent.getComponent().toShortString() + " did not call through to super.onPause()"); } } catch (SuperNotCalledException e) { throw e; } catch (Exception e) { if (!mInstrumentation.onException(r.activity, e)) { throw new RuntimeException( "Unable to pause activity " + r.intent.getComponent().toShortString() + ": " + e.toString(), e); } } r.paused = true; // Notify any outstanding on paused listeners ArrayList<OnActivityPausedListener> listeners; synchronized (mOnPauseListeners) { listeners = mOnPauseListeners.remove(r.activity); } int size = (listeners != null ? listeners.size() : 0); for (int i = 0; i < size; i++) { listeners.get(i).onPaused(r.activity); } return state; }
上面最重要的是调用了如下:mInstrumentation.callActivityOnPause(r.activity);
进入该方法/** * Perform calling of an activity‘s {@link Activity#onPause} method. The * default implementation simply calls through to that method. * * @param activity The activity being paused. */ public void callActivityOnPause(Activity activity) { activity.performPause(); }final void performPause() { mFragments.dispatchPause(); mCalled = false; onPause(); mResumed = false; if (!mCalled && getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.GINGERBREAD) { throw new SuperNotCalledException( "Activity " + mComponent.toShortString() + " did not call through to super.onPause()"); } mResumed = false; }从上面来看最终调用了生命周期中的onPause方法我们再来看第二个方法 handleDestroyActivity(r.token, false, configChanges, true);
private void handleDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance) { ActivityClientRecord r = performDestroyActivity(token, finishing, configChanges, getNonConfigInstance); if (r != null) { cleanUpPendingRemoveWindows(r); WindowManager wm = r.activity.getWindowManager(); View v = r.activity.mDecor; if (v != null) { if (r.activity.mVisibleFromServer) { mNumVisibleActivities--; } IBinder wtoken = v.getWindowToken(); if (r.activity.mWindowAdded) { if (r.onlyLocalRequest) { // Hold off on removing this until the new activity‘s // window is being added. r.mPendingRemoveWindow = v; r.mPendingRemoveWindowManager = wm; } else { wm.removeViewImmediate(v); } } if (wtoken != null && r.mPendingRemoveWindow == null) { WindowManagerImpl.getDefault().closeAll(wtoken, r.activity.getClass().getName(), "Activity"); } r.activity.mDecor = null; } if (r.mPendingRemoveWindow == null) { // If we are delaying the removal of the activity window, then // we can‘t clean up all windows here. Note that we can‘t do // so later either, which means any windows that aren‘t closed // by the app will leak. Well we try to warning them a lot // about leaking windows, because that is a bug, so if they are // using this recreate facility then they get to live with leaks. WindowManagerImpl.getDefault().closeAll(token, r.activity.getClass().getName(), "Activity"); } // Mocked out contexts won‘t be participating in the normal // process lifecycle, but if we‘re running with a proper // ApplicationContext we need to have it tear down things // cleanly. Context c = r.activity.getBaseContext(); if (c instanceof ContextImpl) { ((ContextImpl) c).scheduleFinalCleanup( r.activity.getClass().getName(), "Activity"); } } if (finishing) { try { ActivityManagerNative.getDefault().activityDestroyed(token); } catch (RemoteException ex) { // If the system process has died, it‘s game over for everyone. } } }ActivityClientRecord r = performDestroyActivity(token, finishing, configChanges, getNonConfigInstance);public final ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing) { return performDestroyActivity(token, finishing, 0, false); } private ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance) { ActivityClientRecord r = mActivities.get(token); Class activityClass = null; if (localLOGV) Slog.v(TAG, "Performing finish of " + r); if (r != null) { activityClass = r.activity.getClass(); r.activity.mConfigChangeFlags |= configChanges; if (finishing) { r.activity.mFinished = true; } if (!r.paused) { try { r.activity.mCalled = false; mInstrumentation.callActivityOnPause(r.activity); EventLog.writeEvent(LOG_ON_PAUSE_CALLED, r.activity.getComponentName().getClassName()); if (!r.activity.mCalled) { throw new SuperNotCalledException( "Activity " + safeToComponentShortString(r.intent) + " did not call through to super.onPause()"); } } catch (SuperNotCalledException e) { throw e; } catch (Exception e) { if (!mInstrumentation.onException(r.activity, e)) { throw new RuntimeException( "Unable to pause activity " + safeToComponentShortString(r.intent) + ": " + e.toString(), e); } } r.paused = true; } if (!r.stopped) { try { r.activity.performStop(); } catch (SuperNotCalledException e) { throw e; } catch (Exception e) { if (!mInstrumentation.onException(r.activity, e)) { throw new RuntimeException( "Unable to stop activity " + safeToComponentShortString(r.intent) + ": " + e.toString(), e); } } r.stopped = true; } if (getNonConfigInstance) { try { r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances(); } catch (Exception e) { if (!mInstrumentation.onException(r.activity, e)) { throw new RuntimeException( "Unable to retain activity " + r.intent.getComponent().toShortString() + ": " + e.toString(), e); } } } try { r.activity.mCalled = false; mInstrumentation.callActivityOnDestroy(r.activity); if (!r.activity.mCalled) { throw new SuperNotCalledException( "Activity " + safeToComponentShortString(r.intent) + " did not call through to super.onDestroy()"); } if (r.window != null) { r.window.closeAllPanels(); } } catch (SuperNotCalledException e) { throw e; } catch (Exception e) { if (!mInstrumentation.onException(r.activity, e)) { throw new RuntimeException( "Unable to destroy activity " + safeToComponentShortString(r.intent) + ": " + e.toString(), e); } } } mActivities.remove(token); StrictMode.decrementExpectedActivityCount(activityClass); return r; }
上面代码重要的调用了如下:r.activity.performStop();和 mInstrumentation.callActivityOnDestroy(r.activity);
这两个方法重点的是stop当前的Activity并且destroy Activity
我们回到第三个方法handleLaunchActivity
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) { // If we are getting ready to gc after going to the background, well // we are back active so skip it. unscheduleGcIdler(); if (r.profileFd != null) { mProfiler.setProfiler(r.profileFile, r.profileFd); mProfiler.startProfiling(); mProfiler.autoStopProfiler = r.autoStopProfiler; } // Make sure we are running with the most recent config. handleConfigurationChanged(null, null); if (localLOGV) Slog.v( TAG, "Handling launch of " + r); Activity a = performLaunchActivity(r, customIntent); if (a != null) { r.createdConfig = new Configuration(mConfiguration); Bundle oldState = r.state; handleResumeActivity(r.token, false, r.isForward); if (!r.activity.mFinished && r.startsNotResumed) { // The activity manager actually wants this one to start out // paused, because it needs to be visible but isn‘t in the // foreground. We accomplish this by going through the // normal startup (because activities expect to go through // onResume() the first time they run, before their window // is displayed), and then pausing it. However, in this case // we do -not- need to do the full pause cycle (of freezing // and such) because the activity manager assumes it can just // retain the current state it has. try { r.activity.mCalled = false; mInstrumentation.callActivityOnPause(r.activity); // We need to keep around the original state, in case // we need to be created again. r.state = oldState; if (!r.activity.mCalled) { throw new SuperNotCalledException( "Activity " + r.intent.getComponent().toShortString() + " did not call through to super.onPause()"); } } catch (SuperNotCalledException e) { throw e; } catch (Exception e) { if (!mInstrumentation.onException(r.activity, e)) { throw new RuntimeException( "Unable to pause activity " + r.intent.getComponent().toShortString() + ": " + e.toString(), e); } } r.paused = true; } } else { // If there was an error, for any reason, tell the activity // manager to stop us. try { ActivityManagerNative.getDefault() .finishActivity(r.token, Activity.RESULT_CANCELED, null); } catch (RemoteException ex) { // Ignore } } }
假设,我们把handleLaunchActivity方法注释掉,则我们应该会看到当前界面销毁,而又没有起Activity,则会黑屏,呵呵上面重要的两个方法如下:
1、 Activity a = performLaunchActivity(r, customIntent);
2、 handleResumeActivity(r.token, false, r.isForward);
先来看performLaunchActivity方法
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")"); ActivityInfo aInfo = r.activityInfo; if (r.packageInfo == null) { r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo, Context.CONTEXT_INCLUDE_CODE); } ComponentName component = r.intent.getComponent(); if (component == null) { component = r.intent.resolveActivity( mInitialApplication.getPackageManager()); r.intent.setComponent(component); } if (r.activityInfo.targetActivity != null) { component = new ComponentName(r.activityInfo.packageName, r.activityInfo.targetActivity); } Activity activity = null; try { java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); StrictMode.incrementExpectedActivityCount(activity.getClass()); r.intent.setExtrasClassLoader(cl); if (r.state != null) { r.state.setClassLoader(cl); } } catch (Exception e) { if (!mInstrumentation.onException(activity, e)) { throw new RuntimeException( "Unable to instantiate activity " + component + ": " + e.toString(), e); } } try { Application app = r.packageInfo.makeApplication(false, mInstrumentation); if (localLOGV) Slog.v(TAG, "Performing launch of " + r); if (localLOGV) Slog.v( TAG, r + ": app=" + app + ", appName=" + app.getPackageName() + ", pkg=" + r.packageInfo.getPackageName() + ", comp=" + r.intent.getComponent().toShortString() + ", dir=" + r.packageInfo.getAppDir()); if (activity != null) { ContextImpl appContext = new ContextImpl(); appContext.init(r.packageInfo, r.token, this); appContext.setOuterContext(activity); CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); Configuration config = new Configuration(mCompatConfiguration); if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity " + r.activityInfo.name + " with config " + config); activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config); if (customIntent != null) { activity.mIntent = customIntent; } r.lastNonConfigurationInstances = null; activity.mStartedActivity = false; int theme = r.activityInfo.getThemeResource(); if (theme != 0) { activity.setTheme(theme); } activity.mCalled = false; mInstrumentation.callActivityOnCreate(activity, r.state); if (!activity.mCalled) { throw new SuperNotCalledException( "Activity " + r.intent.getComponent().toShortString() + " did not call through to super.onCreate()"); } r.activity = activity; r.stopped = true; if (!r.activity.mFinished) { activity.performStart(); r.stopped = false; } if (!r.activity.mFinished) { if (r.state != null) { mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state); } } if (!r.activity.mFinished) { activity.mCalled = false; mInstrumentation.callActivityOnPostCreate(activity, r.state); if (!activity.mCalled) { throw new SuperNotCalledException( "Activity " + r.intent.getComponent().toShortString() + " did not call through to super.onPostCreate()"); } } } r.paused = true; mActivities.put(r.token, r); } catch (SuperNotCalledException e) { throw e; } catch (Exception e) { if (!mInstrumentation.onException(activity, e)) { throw new RuntimeException( "Unable to start activity " + component + ": " + e.toString(), e); } } return activity; }
上面重要的一句代码:mInstrumentation.callActivityOnCreate(activity, r.state);public void callActivityOnCreate(Activity activity, Bundle icicle) { if (mWaitingActivities != null) { synchronized (mSync) { final int N = mWaitingActivities.size(); for (int i=0; i<N; i++) { final ActivityWaiter aw = mWaitingActivities.get(i); final Intent intent = aw.intent; if (intent.filterEquals(activity.getIntent())) { aw.activity = activity; mMessageQueue.addIdleHandler(new ActivityGoing(aw)); } } } } activity.performCreate(icicle); if (mActivityMonitors != null) { synchronized (mSync) { final int N = mActivityMonitors.size(); for (int i=0; i<N; i++) { final ActivityMonitor am = mActivityMonitors.get(i); am.match(activity, activity, activity.getIntent()); } } } }
进入方法performCreatefinal void performCreate(Bundle icicle) { onCreate(icicle); mVisibleFromClient = !mWindow.getWindowStyle().getBoolean( com.android.internal.R.styleable.Window_windowNoDisplay, false); mFragments.dispatchActivityCreated(); }
我们看到了调用了onCreate启动了再回到第二个方法handleResumeActivity,同上面一步步跟踪下去调用了Resume
从上面来看,我们有几点需要总结下:
说明:输入法的国际化在cb中有Activity也有service,而输入法是服务,在InputMethodService的继承类中加入了onConfigurationChanged方法,所以会回调onConfigurationChanged方法,而我在这个方法里面做了kill动作,类似于上面的代码的Activity的重启
cb.onConfigurationChanged(config);public void KillPinyin(){ android.os.Process.killProcess(android.os.Process.myPid()); final ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE); am.restartPackage(getPackageName()); }
现在有个疑问,即如果我不去重新覆盖 onConfigurationChanged方法会怎么样?呵呵,按规矩不去覆盖该方法应该像其他应用一样会自动实现国家化,会吗?它是不会的,不信你试试,为什么呢?这又是另一个课题了,呵呵,我也很想有人和我交流这个,即为什么不去覆盖此方法也不行呢?我现在没有时间写博客了,要做项目了