最近在项目中遇到一个初看比较奇怪的问题:在Setting中进行中英文切换,比如由中文切换到英文,这时别的界面字符都已经由中文变成了英文,但是,在打开最近应用界面的时候,却发现里面的提示字符还是中文,但是其中的最近应用的apk的名字已经变成了中文,而且,关机后开机,显示的字符串就正常了。第一眼看到的感觉是:哇塞(yu men),这怎么可能,一个界面中怎么有的字符会不切换!!!
没办法,出现问题了,肯定是存在问题。只能静下心来仔细查看代码。
下面就记录下心路历程。
首先,查看这部分的源码在哪里。
源码路径:
frameworks\base\policy\src\com\android\internal\policy\impl\
RecentApplicationsDialog.java
然后,查看与这个有相似功能的代码是否有问题。
我查看了之前在Framework层弹出Toast的是否有问题。经查看,弹出的Toast中的字符是会跟随Setting的中英文切换而变的。然后去查看了弹出Toast的代码:
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
public void run(){
Resources res = Resources.getSystem();
Toast.makeText(getContext(), res.getText(com.android.internal.R.string.input_again), Toast.LENGTH_SHORT).show();
}
});
然后查看了RecentApplicationsDialog中获取字符的部分:
getContext ().getResources ().getString (com.android.internal.R.string.tipinfo);
想着会不会是由于字符的获取问题导致的,于是改了
RecentApplicationsDialog中获取字符串的方式,发现毫无影响。也是自己太相信自己(傻逼)了一回,竟然没有第一时间打印。以为问题应该出在这里。最后看到真相的我眼泪掉下来。
接着,立刻开始了打印字符、打印周期函数是否执行等。
结果发现,
RecentApplicationsDialog的onCreate只会在第一次显示最近应用的时候执行一次,后面再弹出Dialog,都不会执行
onCreate,直接从onStart开始执行。
这样就知道为什么
提示字符不会跟随切换了,因为我们是在onCreate中设置布局,然后显示相应的字符。而onCreate不是每次都执行,所以里面的提示字符就没有切换。
/**
* We create the recent applications dialog just once, and it stays around
* (hidden) until activated by the user.
* 从这里其实就可以看出来端倪了
* @see PhoneWindowManager#showRecentAppsDialog
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("zmq","RecentApplicationDialog onCreate");
Context context = getContext();
if (sStatusBar == null) {
sStatusBar = (StatusBarManager) context
.getSystemService(Context.STATUS_BAR_SERVICE);
}
Window window = getWindow();
window.requestFeature(Window.FEATURE_NO_TITLE);
window.setType(WindowManager.LayoutParams.TYPE_RECENTS_OVERLAY);
window.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
window.setTitle("Recents");
setContentView(com.android.internal.R.layout.recent_apps_dialog); //设置布局
mpPackageManager = getContext().getPackageManager();
maActivityManager = (ActivityManager) getContext().getSystemService(
Context.ACTIVITY_SERVICE);
mActivityInfo = new Intent(Intent.ACTION_MAIN).addCategory(
Intent.CATEGORY_HOME).resolveActivityInfo(mpPackageManager, 0);
mIconUtilities = new IconUtilities(getContext());
final WindowManager.LayoutParams params = window.getAttributes();
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.height = WindowManager.LayoutParams.MATCH_PARENT;
window.setAttributes(params);
window.setFlags(0, WindowManager.LayoutParams.FLAG_DIM_BEHIND);
mIcons[0] = (TextView)findViewById(com.android.internal.R.id.button0);
mIcons[1] = (TextView)findViewById(com.android.internal.R.id.button1);
mIcons[2] = (TextView)findViewById(com.android.internal.R.id.button2);
mIcons[3] = (TextView)findViewById(com.android.internal.R.id.button3);
mIcons[4] = (TextView)findViewById(com.android.internal.R.id.button4);
mIcons[5] = (TextView)findViewById(com.android.internal.R.id.button5);
mIcons[6] = (TextView)findViewById(com.android.internal.R.id.button6);
mIcons[7] = (TextView)findViewById(com.android.internal.R.id.button7);
mNoAppsText = findViewById(com.android.internal.R.id.no_applications_message);
tipinfo = (TextView)findViewById(com.android.internal.R.id.tipinfo); //要显示提示的控件TextView
ActivityManager activityManager = (ActivityManager) getContext()
.getSystemService(Context.ACTIVITY_SERVICE);
final List<ActivityManager.RunningTaskInfo> list = activityManager
.getRunningTasks(1);
mGridView = (GridView) findViewById(com.android.internal.R.id.recent_gridview);
mResolveInfos = getResolveInfo();
mRecentAdapter = new RecentAdapter(mResolveInfos, context);
mGridView.setAdapter(mRecentAdapter);
if(list.get(0).topActivity.getPackageName().equals(
"com.android.zmq")){
tipinfo.setText (Resources.getSystem().getText(com.android.internal.R.string.tipsinfo));
}
mGridView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
long arg3) {
// TODO Auto-generated method stub
try {
if (mRecentAdapter.getmResolveInfos() != null) {
RecentTag recentTag = getTag(mRecentAdapter
.getmResolveInfos().get(arg2));
if (recentTag != null)
{
if(list.get(0).topActivity.getPackageName().equals(
"com.android.zmq"))
{
isForceStopOne = true;
forceStopPackage(recentTag);
mRecentAdapter.setmResolveInfos(getResolveInfo());
mRecentAdapter.notifyDataSetChanged();
isForceStopOne = false;
}else{
switchTo(recentTag);
dismiss();
}
}
}
} catch (Exception e) {
// TODO: handle exception
}
}
});
}
知道了原因,问题就比较好解决了。既然每次都会跑onStart,那么我们就把字符的显示放在onStart中。
/**
* Set up and show the recent activities dialog.
*/
@Override
public void onStart() {
super.onStart();
Log.d("zmq","RecentApplicationDialog onStart");
mRecentAdapter.setmResolveInfos(getResolveInfo());
mRecentAdapter.notifyDataSetChanged();
if (sStatusBar != null) {
sStatusBar.disable(StatusBarManager.DISABLE_EXPAND);
}
ActivityManager am = (ActivityManager) getContext()
.getSystemService(Context.ACTIVITY_SERVICE);
final List<ActivityManager.RunningTaskInfo> runList = am.getRunningTasks(1);
//显示字符,这样就可以每次都切换到啦。
if(runList.get(0).topActivity.getPackageName().equals(
"com.android.zmq")){
tipinfo.setText (Resources.getSystem().getText(com.android.internal.R.string.tipsinfo));
}
else{
tipinfo.setText(Resources.getSystem().getText(com.android.internal.R.string.recent_remove_message_pb));
}
// receive broadcasts
getContext().registerReceiver(mBroadcastReceiver, mBroadcastIntentFilter);
mHandler.removeCallbacks(mCleanup);
mGridView.setFocusable(true);
mGridView.setFocusableInTouchMode(true);
mGridView.requestFocus();
}
问题是解决了。但是我们还是要找出根源:即为什么onCreate只会执行一次。
首先看到
RecentApplicationsDialog是继承Dialog,在
RecentApplicationsDialog中,除了构造函数,还有onCreate、onStart、onStop等函数。
Protected Methods | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
void |
onCreate(
Bundle savedInstanceState)
Similar to
onCreate(Bundle) , you should initialize your dialog in this method, including calling
setContentView(View) .
| ||||||||||
void |
onStart()
Called when the dialog is starting.
| ||||||||||
void |
onStop()
Called to tell you that you're stopping.
|
RecentApplicationsDialog 中的
onCreate只会执行一次,而且在退出dialog的时候会执行onStop,并且并没有看到相关的控制。于是开始在Dialog的源码中寻找根源。
在打开最近应用这个对话框的时候,会先调用
RecentApplicationsDialog
的构造函数(如果已经打开过一次,则不会再运行),然后判断这个对话框是否处于showing模式,如果不是的话,则show,否则,dismiss。
void showOrHideRecentAppsDialog(final int behavior) {
mHandler.post(new Runnable() {
@Override
public void run() {
if (mRecentAppsDialog == null) {
mRecentAppsDialog = new RecentApplicationsDialog(mContext);
}
if (mRecentAppsDialog.isShowing()) {
switch (behavior) {
case RECENT_APPS_BEHAVIOR_SHOW_OR_DISMISS:
case RECENT_APPS_BEHAVIOR_DISMISS:
mRecentAppsDialog.dismiss();
break;
case RECENT_APPS_BEHAVIOR_DISMISS_AND_SWITCH:
mRecentAppsDialog.dismissAndSwitch();
break;
case RECENT_APPS_BEHAVIOR_EXIT_TOUCH_MODE_AND_SHOW:
default:
break;
}
} else {
switch (behavior) {
case RECENT_APPS_BEHAVIOR_SHOW_OR_DISMISS:
mRecentAppsDialog.show();
break;
case RECENT_APPS_BEHAVIOR_EXIT_TOUCH_MODE_AND_SHOW:
try {
mWindowManager.setInTouchMode(false);
} catch (RemoteException e) {
}
mRecentAppsDialog.show();
break;
case RECENT_APPS_BEHAVIOR_DISMISS:
case RECENT_APPS_BEHAVIOR_DISMISS_AND_SWITCH:
default:
break;
}
}
}
});
}
在Dialog中,show方法:
/**
* Start the dialog and display it on screen. The window is placed in the
* application layer and opaque. Note that you should not override this
* method to do initialization when the dialog is shown, instead implement
* that in {@link #onStart}.
*/
public void show() {
Log.d("zmq","Dialog show");
if (mShowing) {
if (mDecor != null) {
if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
}
mDecor.setVisibility(View.VISIBLE);
}
return;
}
mCanceled = false;
//mCreated初始值为false,即第一次是会执行的
if (!mCreated) {
dispatchOnCreate(null);
}
onStart();
mDecor = mWindow.getDecorView();
if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
final ApplicationInfo info = mContext.getApplicationInfo();
mWindow.setDefaultIcon(info.icon);
mWindow.setDefaultLogo(info.logo);
mActionBar = new ActionBarImpl(this);
}
WindowManager.LayoutParams l = mWindow.getAttributes();
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
nl.copyFrom(l);
nl.softInputMode |=
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
l = nl;
}
try {
mWindowManager.addView(mDecor, l);
mShowing = true;
sendShowMessage();
} finally {
}
}
可以看到,只有第一次的时候,mCreated为false,才会运行dispatchOnCreate,在
dispatchOnCreate中:
// internal method to make sure mcreated is set properly without requiring
// users to call through to super in onCreate
void dispatchOnCreate(Bundle savedInstanceState) {
Log.d("zmq","Dialog dispatchOnCreate");
Log.d("zmq","Dialog dispatchOnCreate mCreated = "+mCreated);
if(savedInstanceState!=null){
Log.d("zmq","Dialog dispatchOnCreate savedInstanceState != null");
}
else{
Log.d("zmq","Dialog dispatchOnCreate savedInstanceState == null");
}
//mCreated第一次是false
if (!mCreated) {
//执行RecentApplicationsDialog中的onCreate
onCreate(savedInstanceState);
mCreated = true; //设置为true,以后就不再执行。这里就是根源啦
}
}
这样我们就找到RecentApplicationsDialog中onCreate只执行一次根源啦。
执行完
onCreate ,回到show方法,继续执行
onStart,然后回到show方法,最后显示出dialog。
dismiss掉Dialog的时候,最终会调用dimissDialog方法,然后会调用onStop。
/**
* Dismiss this dialog, removing it from the screen. This method can be
* invoked safely from any thread. Note that you should not override this
* method to do cleanup when the dialog is dismissed, instead implement
* that in {@link #onStop}.
*/
@Override
public void dismiss() {
Log.d("zmq","Dialog dismiss");
if (Looper.myLooper() == mHandler.getLooper()) {
dismissDialog();
} else {
mHandler.post(mDismissAction);
}
}
void dismissDialog() {
Log.d("zmq","Dialog dismissDialog");
if (mDecor == null || !mShowing) {
return;
}
if (mWindow.isDestroyed()) {
Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!");
return;
}
try {
mWindowManager.removeViewImmediate(mDecor);
} finally {
if (mActionMode != null) {
mActionMode.finish();
}
mDecor = null;
mWindow.closeAllPanels();
onStop();
mShowing = false;
sendDismissMessage();
}
}
最后总结一句话就是:
因为在Dialog这个类中,show方法中会有标志位mCreated做判断,mCreated初始值为false。在第一次创建这个Dialog的时候会执行onCreate ,后然mCreated被设置为true。再次调用show方法时,就不会去跑onCreate,只会去跑onStart了。
也算是知其所以然了~