Launcher3自定义壁纸旋转后拉伸无法恢复
MTK8382/8121平臺。
?
描述:將自定義圖片設置成壁紙后,橫屏顯示時,旋轉為豎屏,圖片由于分辨率過小,會拉伸;再旋轉為橫屏,拉伸不恢復。
這兩天正在解這個問題,研究了很久,走了不少彎路,最后發現是Launcher讀取SharePreferences時的一個bug。
?
bug是這樣產生的:
Launcher3設置完自定義壁紙(系統自帶壁紙不會記錄)的時候,會在com.android.launcher3.WallpaperCropActivity.xml中記錄被設置壁紙的分辨率,并提交分辨率給WallpaperManager(通過suggestWallpaperDimension())。具體函數是:WallpaperCropActivity.java中的updateWallpaperDimensions(),它被WallpaperCropActivity.java的setWallpaper()調用;
Launcher3每次旋轉后會重新執行onCreate(),同時會提交當前壁紙的分辨率給WallpaperManager,提交分辨率的函數在Workspace.java中的setWallpaperDimension()中。問題在這里:setWallpaperDimension()無法獲取之前updateWallpaperDimensions()修改的SharedPreferences,導致它提交的是默認的壁紙分辨率1920x1080,從而導致低分辨率的壁紙拉伸。
解決此問題的方法是:修改Workspace.java中的setWallpaperDimension()中的getSharedPreferences()的flag,把MODE_PRIVATE改為MODE_MULTI_PROCESS。修改后成功訪問。
?
我的問題是:
根據Android Developer的解釋:
MODE_PRIVATE:
File creation mode: the default mode, where the created file can only be accessed by the calling application (or all applications sharing the same user ID).
即MODEL_PRIVATE只能被同一個application或者同一個userID的application調用。按這個說法,這兩個Activity應該是可以共同訪問的。(Workspace.java使用的是Launcher.java的context,兩個Activity pid不一樣,uid一樣)
同時我還看了MODE_MULTI_PROCESS的解釋:
MODE_MULTI_PROCESS:
SharedPreference loading flag: when set, the file on disk will be checked for modification even if the shared preferences instance is already loaded in this process. This behavior is sometimes desired in cases where the application has multiple processes, all writing to the same SharedPreferences file. Generally there are better forms of communication between processes, though.
This was the legacy (but undocumented) behavior in and before Gingerbread (Android 2.3) and this flag is implied when targetting such releases. For applications targetting SDK versions?greater than?Android 2.3, this flag must be explicitly set if desired.
意思是這個flag用于給擁有多個進程的application共同訪問同一個SharedPreferences使用的。按照這個說法,似乎又確實應該使用MODEL_MULTI_PROCESS。
?
于是我想找到getSharedPreference實現代碼,看看它怎么處理這幾個flag。可惡的是,從Activity父類一級一級往上找,都找不到實現的方法,直到找到這篇文章:
http://blog.csdn.net/qinjuning/article/details/7310620
才知道ContextImpl實現了Context的具體方法,進而找到了答案:
ContextImpl類中有getSharedPreferences的實現。里面說明了在MODE_MULTI_PROCESS標志中,getSharedPreferences會進行reload。換言之MODE_PRIVATE不會重新讀取SharedPreferences。
這里終于搞懂他的意思:在之前的bug,并不是SharedPreferences獲取失敗,而是因為沒有reload所以沒有獲取到新寫入的分辨率信息。因為之前沒有注意到這個問題,所以走了彎路。
不過還有問題:每次Launcher旋轉的時候都會重新啟動Activity調用onCreate,為什么我getSharePreferences還是舊的呢?繼續觀察android.app.ContextImpl的getSharedPreferences:
@Override public SharedPreferences getSharedPreferences(String name, int mode) { Log.w("Launcher", "contextImpl: " + this, new RuntimeException("getSp").fillInStackTrace()); SharedPreferencesImpl sp; synchronized (ContextImpl.class) { if (sSharedPrefs == null) { Log.e("Launcher", "all init"); sSharedPrefs = new ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>>(); }final String packageName = getPackageName(); ArrayMap<String, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName); if (packagePrefs == null) { Log.e("Launcher", "package init"); packagePrefs = new ArrayMap<String, SharedPreferencesImpl>(); sSharedPrefs.put(packageName, packagePrefs); }// At least one application in the world actually passes in a null // name. This happened to work because when we generated the file name // we would stringify it to "null.xml". Nice. if (mPackageInfo.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.KITKAT) { if (name == null) { name = "null"; } }sp = packagePrefs.get(name);if (sp == null) { File prefsFile = getSharedPrefsFile(name); sp = new SharedPreferencesImpl(prefsFile, mode); packagePrefs.put(name, sp); Log.e("Launcher", "new sp"); return sp; } Log.e("Launcher", "old sp"); }if ((mode & Context.MODE_MULTI_PROCESS) != 0 || getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) { // If somebody else (some other process) changed the prefs // file behind our back, we reload it. This has been the // historical (if undocumented) behavior. sp.startReloadIfChangedUnexpectedly(); Log.e("Launcher", "reload"); } return sp; }里面Log.e("Launcher", ...);是我自己加的調試信息。首先會判斷sSharedPrefs是否有內容,然后從中獲取對應package的prefsFile。如果sSharedPrefs找不到,才從xml文件中重新讀取。最后加了一個判斷,如果設置了MODE_MULTI_PROCESS變量,或者Android 2.2以下的系統,會默認從xml文件中重新reload,以保持最新的SharedPreferences數據。
注意這里的sSharedPrefs變量,它只在getSharedPreferences中有賦值,也就是說SharedPreferences的數據一直跟隨著ContextImpl實例走,只從getSharedPreferences()方法中獲取數據。也就是說,當旋轉屏幕的時候,我們調用getSharedPreferences()獲取的數據都是從這個sSharedPrefs變量中取出來的。實際從下面Log信息也可以看到,旋轉并不會有"new sp"的Log打印,只有對Launcher3在設置中force stop和clear data的時候才會出現"new sp"。
旋轉的Log提示:
09-11 08:47:19.599: E/Launcher(4628): launcher:com.android.launcher3.Launcher@42392ac8 09-11 08:47:19.599: E/Launcher(4628): mBase:android.app.ContextImpl@424c1898 09-11 08:47:19.608: E/Launcher(4628): old sp 09-11 08:47:19.727: E/Launcher(4628): old sp 09-11 08:47:19.731: E/Launcher(4628): reload 09-11 08:47:19.932: E/Launcher(4628): old spforce stop的提示:
09-11 08:48:29.456: E/Launcher(5271): launcher:com.android.launcher3.Launcher@4238f220 09-11 08:48:29.456: E/Launcher(5271): mBase:android.app.ContextImpl@42391ab8 09-11 08:48:29.489: E/Launcher(5271): all init 09-11 08:48:29.489: E/Launcher(5271): package init 09-11 08:48:29.493: E/Launcher(5271): new sp 09-11 08:48:29.679: E/Launcher(5271): new sp 09-11 08:48:29.766: E/Launcher(5271): old sp 09-11 08:48:29.767: E/Launcher(5271): old sp 09-11 08:48:29.790: E/Launcher(5271): old sp里面三個連著的old sp,只有第二個是讀取分辨率的,其他兩個分辨是LauncherAppState的SharedPreferences。因為此時Launcher3代碼已經被我修改為MODE_MULTI_PROCESS,所以旋轉會打出"reload"信息。
?
也就是說,旋轉的時候sSharedPrefs的值是一直保存著的。可是通過Log打印信息,我們發現,每一次的getSharedPreferences()的contextImpl都是不一樣的!
這里要注意的是,執行方法的context是Activity的父類ContextThemeWrapper的mBase私有成員執行的,獲取mBase可以在getSharedPreferences()中打印this出來,也可以在Activity中進行反射,這里用的是反射方法:
try { Log.e("Launcher", "launcher:" + this); java.lang.reflect.Field f = Activity.class.getSuperclass().getDeclaredField("mBase"); f.setAccessible(true); Context s = (Context) f.get(this); Log.e("Launcher", "mBase:" + s); } catch(Exception e) { e.printStackTrace(); }contextImpl不一樣了,但是sSharedPrefs數據還保存著,且sSharedPrefs不能通過其他方法賦值,只能猜測在旋轉的時候通過原型模式把原來的context傳進去了。Launcher3的旋轉處理,我只找到ACTION_CONFIGURATION_CHANGE這個廣播,但是我把里面的代碼注釋了,依然可以正常工作。估計是更底層的代碼控制的。
?
具體代碼還沒找到,因為還要搬磚。有空再研究吧!
版權所有,轉載請注明出處:
http://www.cnblogs.com/sickworm/p/3966857.html?
轉載于:https://www.cnblogs.com/sickworm/p/3966857.html
總結
以上是生活随笔為你收集整理的Launcher3自定义壁纸旋转后拉伸无法恢复的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 前两章总结
- 下一篇: zabbix之使用proxy实现分布式监