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:

  1. @Override
  2. public SharedPreferences getSharedPreferences(String name, int mode) {
  3. Log.w("Launcher", "contextImpl: " + this, new RuntimeException("getSp").fillInStackTrace());
  4. SharedPreferencesImpl sp;
  5. synchronized (ContextImpl.class) {
  6. if (sSharedPrefs == null) {
  7. Log.e("Launcher", "all init");
  8. sSharedPrefs = new ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>>();
  9. }
  10.  
  11. final String packageName = getPackageName();
  12. ArrayMap<String, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName);
  13. if (packagePrefs == null) {
  14. Log.e("Launcher", "package init");
  15. packagePrefs = new ArrayMap<String, SharedPreferencesImpl>();
  16. sSharedPrefs.put(packageName, packagePrefs);
  17. }
  18.  
  19. // At least one application in the world actually passes in a null
  20. // name. This happened to work because when we generated the file name
  21. // we would stringify it to "null.xml". Nice.
  22. if (mPackageInfo.getApplicationInfo().targetSdkVersion <
  23. Build.VERSION_CODES.KITKAT) {
  24. if (name == null) {
  25. name = "null";
  26. }
  27. }
  28.  
  29. sp = packagePrefs.get(name);
  30.  
  31. if (sp == null) {
  32. File prefsFile = getSharedPrefsFile(name);
  33. sp = new SharedPreferencesImpl(prefsFile, mode);
  34. packagePrefs.put(name, sp);
  35. Log.e("Launcher", "new sp");
  36. return sp;
  37. }
  38. Log.e("Launcher", "old sp");
  39. }
  40.  
  41. if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||
  42. getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
  43. // If somebody else (some other process) changed the prefs
  44. // file behind our back, we reload it. This has been the
  45. // historical (if undocumented) behavior.
  46. sp.startReloadIfChangedUnexpectedly();
  47. Log.e("Launcher", "reload");
  48. }
  49. return sp;
  50. }

里面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提示:

  1. 09-11 08:47:19.599: E/Launcher(4628): launcher:com.android.launcher3.Launcher@42392ac8
  2. 09-11 08:47:19.599: E/Launcher(4628): mBase:android.app.ContextImpl@424c1898
  3. 09-11 08:47:19.608: E/Launcher(4628): old sp
  4. 09-11 08:47:19.727: E/Launcher(4628): old sp
  5. 09-11 08:47:19.731: E/Launcher(4628): reload
  6. 09-11 08:47:19.932: E/Launcher(4628): old sp

force stop的提示:

  1. 09-11 08:48:29.456: E/Launcher(5271): launcher:com.android.launcher3.Launcher@4238f220
  2. 09-11 08:48:29.456: E/Launcher(5271): mBase:android.app.ContextImpl@42391ab8
  3. 09-11 08:48:29.489: E/Launcher(5271): all init
  4. 09-11 08:48:29.489: E/Launcher(5271): package init
  5. 09-11 08:48:29.493: E/Launcher(5271): new sp
  6. 09-11 08:48:29.679: E/Launcher(5271): new sp
  7. 09-11 08:48:29.766: E/Launcher(5271): old sp
  8. 09-11 08:48:29.767: E/Launcher(5271): old sp
  9. 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中进行反射,这里用的是反射方法:

  1. try {
  2. Log.e("Launcher", "launcher:" + this);
  3. java.lang.reflect.Field f = Activity.class.getSuperclass().getDeclaredField("mBase");
  4. f.setAccessible(true);
  5. Context s = (Context) f.get(this);
  6. Log.e("Launcher", "mBase:" + s);
  7. } catch(Exception e) {
  8. e.printStackTrace();
  9. }

contextImpl不一样了,但是sSharedPrefs数据还保存着,且sSharedPrefs不能通过其他方法赋值,只能猜测在旋转的时候通过原型模式把原来的context传进去了。Launcher3的旋转处理,我只找到ACTION_CONFIGURATION_CHANGE这个广播,但是我把里面的代码注释了,依然可以正常工作。估计是更底层的代码控制的。

具体代码还没找到,因为还要搬砖。有空再研究吧!

版权所有,转载请注明出处:

http://www.cnblogs.com/sickworm/p/3966857.html

Launcher3自定义壁纸旋转后拉伸无法恢复的更多相关文章

  1. [转]Android UI:看看Google官方自定义带旋转动画的ImageView-----RotateImageView怎么写(附 图片淡入淡出效果)

    http://blog.csdn.net/yanzi1225627/article/details/22439119 众所周知,想要让ImageView旋转的话,可以用setRotation()让其围 ...

  2. Android UI:看看Google官方自定义带旋转动画的ImageView-----RotateImageView怎么写(附 图片淡入淡...)

    众所周知,想要让ImageView旋转的话,可以用setRotation()让其围绕中心点旋转,但这个旋转是不带动画的,也就是旋转屏幕时图片噌的一下就转过去了,看不到旋转的过程,此UI体验不大好,为此 ...

  3. 重装系统后QQ聊天记录恢复方法

    重装系统后QQ聊天记录恢复方法 近日又一次安装了系统,又一次安装了腾讯的.TM,TM也是安装在之前的文件夹底下,可是聊天记录和之前的自己定义表情都不见了,看来没有自己主动恢复回来. 我这里另一个特殊的 ...

  4. [图形学] 习题8.6 线段旋转后使用Cohen-Sutherland算法裁剪

    习题8.6 生成一条比观察窗口对角线还长的线段动画,线段重点位于观察窗口中心,每一帧的线段在上一帧基础上顺时针旋转一点,旋转后用Cohen-Sutherland线段裁剪算法进行裁剪. 步骤: 1 视口 ...

  5. spring security使用自定义登录界面后,不能返回到之前的请求界面的问题

    昨天因为集成spring security oauth2,所以对之前spring security的配置进行了一些修改,然后就导致登录后不能正确跳转回被拦截的页面,而是返回到localhost根目录. ...

  6. MySQL误操作删除后,怎么恢复数据?

    MySQL误操作删除后,怎么恢复数据?登陆查数据库mysql> select * from abc.stad;+----+-----------+| id | name |+----+----- ...

  7. 如何保证修改resolv.conf后重启不恢复?

    如何保证修改resolv.conf后重启不恢复? 修改/etc/resolv.conf,重启网卡后,/etc/resolv.conf恢复到原来的状态. CentOS.redhat下面直接修改/etc/ ...

  8. 分享:Windows2008重启后提示系统恢复选项的解决办法

    如题:WINdows2008服务器. 重启后提示系统恢复选项的解决办法 使用windows 2008后,不能启动的问题,重启后出现 修复系统选项 采用下面帖子中的部分命令搞定之. 我自己是直接使用:选 ...

  9. 如何使用JW Player来播放Flash并隐藏控制按钮和自定义播放完成后执行的JS

    在一个客户项目中播放的flash需要进行定制如不显示控制按钮,flash播放完成后执行特定的js等,在用过了N多的JQery插件和播放器后最终JW Player插件可以满足我的以上要求 因为JW Pl ...

随机推荐

  1. python基础篇 07set集合 深浅拷贝

    本节主要内容:1. 基础数据类型补充2. set集合3. 深浅拷⻉ " ".join方法 循环删除列表中的内容:   错误的  原因:在for循环中,循环到第一个,然后删除,删除之 ...

  2. 可以完成99%的静态页面的HTML标签

    HTML:一套浏览器认知的规则HTML分为两个部分,头和身体.一个完整的网页相当于一个裸体的人,我们利用HTML给它穿上衣服,使它更好看.下面我将为大家介绍一下HTML一些基本的标签,而这些基本的标签 ...

  3. day-11 python自带库实现2层简单神经网络算法

    深度神经网络算法,是基于神经网络算法的一种拓展,其层数更深,达到多层,本文以简单神经网络为例,利用梯度下降算法进行反向更新来训练神经网络权重和偏向参数,文章最后,基于Python 库实现了一个简单神经 ...

  4. f3d源码解读

    Fomo3D 源码解析, 部署指南 https://www.meiwen.com.cn/subject/efntbftx.html 原文链接 Fomo3D 合约源码分析 准备工作 环境准备 (用于调试 ...

  5. 并查集——poj1182(带权并查集高阶)

    题目链接:食物链 题解:点击 说一声:这题关系推导值得学习.

  6. Packet filtering with Linux & NAT

    http://www.linuxfocus.org/ChineseGB/May2003/article289.shtml Gateway, Proxy-Arp 和 Ethernet Bridge ? ...

  7. To Chromium之浏览器外框UI(2)

    之前一些文章本来是草稿状态,一次性全release出来了,排版上可能看上去不太舒服,等哪一天研究下改改排版. Here继续chromium的UI,看看,浏览器的外壳是怎么被画出来的:) 可以先关注下几 ...

  8. windows下Memcached 架设及java应用(转)

    1 Memcache是什么 Memcache是danga.com的一个项目,最早是为 LiveJournal 服务的,目前全世界不少人使用这个缓存项目来构建自己大负载的网站,来分担数据库的压力. 它可 ...

  9. Java中输入输出流

    InputStream:所有字节输入流的所有类的超类. read(byte[] b)从输入流中读取一定数量的字节,并将其存储在缓冲数组b中 reset()将此流重新定位到最后一次对此流调用mark方法 ...

  10. EF to linq 左连接

    如果连接的数据不存在用 null 表示,则可以左连接查询,但是如果数据类型为 int 则会出错. var ng = (from g in _db.NET_NEWS_GROUP join z in _d ...