美文网首页
通过修改sw来适配应用界面——源码修改

通过修改sw来适配应用界面——源码修改

作者: 我爱田Hebe | 来源:发表于2022-12-30 14:47 被阅读0次

    一、前言

    我遇到的问题主要就是在自研 PAD 横屏时,微信应用的登录界面中,“登录” 和 “注册” 按钮堆叠在一起了。这个明显就是微信应用没有适配对应 sw 的资源布局导致的,我看了一下我们自研 PAD 的 sw (最小宽度密度)是 800dp, 一般应用中会根据不同的 sw 值去寻找对应的资源布局。

    搜了好多资料,但是都没有能够解决问题的,但有很多基础知识可以帮我慢慢地摸索到答案。这么多条的搜索记录中只有这条给我提供了思路,但是非常遗憾,我按照博主的方法试了一下,没有成功。但不得不承认这位博主整理得不错,可惜文章是 Android10.0 的版本了,google 这两年应该做了一些修改。

    这位博主的修改方法:在 ResourcesManager 中修改 density 的值

    //frameworks/base/core/java/android/app/ResourcesManager.java
    private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) {
        Configuration config;
        final boolean isDefaultDisplay = (key.mDisplayId == Display.DEFAULT_DISPLAY);
        final boolean hasOverrideConfig = key.hasOverrideConfiguration();
        if (!isDefaultDisplay || hasOverrideConfig) {
            config = new Configuration(getConfiguration());
            if (!isDefaultDisplay) {
                applyNonDefaultDisplayMetricsToConfiguration(dm, config);
            }
            if (hasOverrideConfig) {
                config.updateFrom(key.mOverrideConfiguration);
                if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration);
            }
        } else {
            config = getConfiguration();
        }
        //add {
        String apkName = key.mResDir;
        int setDensity = Resources.getSystem().getInteger(R.integer.config_desity_switch_value);
        String needChangedensityApk = Resources.getSystem().getString(R.string.density_change_pacagename);
        //Slog.d(TAG, "generateConfig---->" + apkName + "--setDensity-->" + setDensity + "--needChangedensityApk---->" + needChangedensityApk);
        if (apkName != null && needChangedensityApk != null && needChangedensityApk.contains(apkName)) {
           config.densityDpi = setDensity;
        }
        //add }
        return config;
    }
    

    我试了这个方案发现并没有生效,这里的参数修改后面应该又被重新覆盖掉了,所以只能寻找另外的方案。

    二、解决方案

    这里我们想让微信去改基本不可能,用户拿到你的 PAD 肯定会觉得是你 PAD 的问题。于是我拿来了联想、oppo、vivi 和 三星的 PAD 来对比,发现只有联想在横屏的时候微信布局是正常的,其他厂商都是一样的问题,这说明联想肯定做了一些适配。

    我拿着联想 PAD 反编译它的 framework.jar , 遗憾的是并没有找到对应的修改方案。于是我拿着联想 PAD 继续研究。终于我发现它的 density 值并没发生改变,我用 wm density 命令查看,density 始终未变。然后我切换微信的界面发现 sw 的值发生了改变,于是有了通过改变 sw 的值来改变分辨率的思路。

    于是我去找能够改变 sw 值的地方,最后在 ActivityRecord#updateCompatDisplayInsets() 和 CompatibilityInfo# applyToConfiguration()处发现可以修改 smallestScreenWidthDp 的值来实现。

    1. ActivityRecord#updateCompatDisplayInsets()

    // frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
    8102      // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
    8103      private void updateCompatDisplayInsets() {
    8104          if (mCompatDisplayInsets != null || !shouldCreateCompatDisplayInsets()) {
    8105              // The override configuration is set only once in size compatibility mode.
    8106              return;
    8107          }
    8108  
    8109          Configuration overrideConfig = getRequestedOverrideConfiguration();
    8110          final Configuration fullConfig = getConfiguration();
    8111  
    8112          // Ensure the screen related fields are set. It is used to prevent activity relaunch
    8113          // when moving between displays. For screenWidthDp and screenWidthDp, because they
    8114          // are relative to bounds and density, they will be calculated in
    8115          // {@link Task#computeConfigResourceOverrides} and the result will also be
    8116          // relatively fixed.
    8117          overrideConfig.colorMode = fullConfig.colorMode;
    8118          overrideConfig.densityDpi = fullConfig.densityDpi;
    8119          // The smallest screen width is the short side of screen bounds. Because the bounds
    8120          // and density won't be changed, smallestScreenWidthDp is also fixed.
                  // 这里修改这个 smllestScreenWidthDp 就可以了
    8121          overrideConfig.smallestScreenWidthDp = fullConfig.smallestScreenWidthDp;
    8122          if (info.isFixedOrientation()) {
    8123              // lock rotation too. When in size-compat, onConfigurationChanged will watch for and
    8124              // apply runtime rotation changes.
    8125              overrideConfig.windowConfiguration.setRotation(
    8126                      fullConfig.windowConfiguration.getRotation());
    8127          }
    8128  
    8129          // The role of CompatDisplayInsets is like the override bounds.
    8130          mCompatDisplayInsets =
    8131                  new CompatDisplayInsets(
    8132                          mDisplayContent, this, mLetterboxBoundsForFixedOrientationAndAspectRatio);
    8133      }
    

    这个方法貌似行不通,实现的效果缺一次刷新,第一次进入应用时分辨率不改变,息屏或者把应用退到后台,再进入前台,这个时候分辨率可以更新成功。原因是这个方法在开机后会调用一次,mCompatDisplayInsets 会被赋值,后面再次进入时就直接 return 了,对应参数不会发生改变。而当我们点击微信应用强制改变参数时,mCompatDisplayInsets 里是之前初始化的初始值(所以第一次进入时分辨率不会改变),第二次进入后分辨率才有效果。

    我们没有办法做到在第一次调用这个方法的时候针对微信应用做参数适配。除非全局都适配,这样改波及太大。这里需要实现的效果是首次进入时分辨率就得及时更新,所以这个方案就不行了。我们看看另外一个方案。

    2. CompatibilityInfo# applyToConfiguration()

    这两处其实都是针对兼容模式来做处理的,具体流程逻辑后面再整理,先说处理方案。这个方法会被反复调用,不会存在缺一次刷新的问题。

    // frameworks/base/core/java/android/content/res/CompatibilityInfo.java
    550      public void applyToConfiguration(int displayDensity, Configuration inoutConfig) {
    551          if (!supportsScreen()) {
    552              // This is a larger screen device and the app is not
    553              // compatible with large screens, so we are forcing it to
    554              // run as if the screen is normal size.
    555              inoutConfig.screenLayout =
    556                      (inoutConfig.screenLayout&~Configuration.SCREENLAYOUT_SIZE_MASK)
    557                      | Configuration.SCREENLAYOUT_SIZE_NORMAL;
    558              inoutConfig.screenWidthDp = inoutConfig.compatScreenWidthDp;
    559              inoutConfig.screenHeightDp = inoutConfig.compatScreenHeightDp;
                      // 这里修改这个 smllestScreenWidthDp 就可以了
    560              inoutConfig.smallestScreenWidthDp = inoutConfig.compatSmallestScreenWidthDp;
    561          }
                 // add 
                 ...
    
    562          inoutConfig.densityDpi = displayDensity;
    563          if (isScalingRequired()) {
    564              float invertedRatio = applicationInvertedScale;
    565              inoutConfig.densityDpi = (int)((inoutConfig.densityDpi * invertedRatio) + .5f);
    566              inoutConfig.windowConfiguration.getMaxBounds().scale(invertedRatio);
    567              inoutConfig.windowConfiguration.getBounds().scale(invertedRatio);
    568              final Rect appBounds = inoutConfig.windowConfiguration.getAppBounds();
    569              if (appBounds != null) {
    570                  appBounds.scale(invertedRatio);
    571              }
    572          }
    573      }
    574
    

    三、基础知识补充

    屏幕尺寸、屏幕分辨率、屏幕像素密度

    1. 屏幕尺寸

    屏幕尺寸是指设备对角线的物理尺寸,常用单位为英寸

    2. 屏幕分辨率

    屏幕分辨率是指设备在横向、纵向上的像素总和,常用 宽 * 高 来描述。

    如480*600,则表示在横向上有480个像素点,在纵向上有600个像素点。

    3. 屏幕像素密度

    屏幕像素密度指的是设备每英寸的像素点数, 单位 dpi

    倍图对应关系:

    4. 屏幕适配方案

    Android 中常见的 UI 适配方案

    1. 多 layout 适配

    2. 屏幕分辨率限定符适配

    3. smallestWidth 限定符适配:创建多个 values 文件夹,系统根据限定符去寻找对应的 dimens.xml 文件,以确定在不同设备上的大小展示。

    sw 适配的优势

    1. Android 适配屏幕尺寸较多
      绝大多数设备的最小宽度都大于 360dp, 这样 sw 就不需要大量适配。

    2. 适配单位方便
      屏幕分辨率适配的单位是 px(像素), sw 单位是 dp

    3. 适配宽松
      sw 适配从大往小找,不需要匹配的十分准确。

    参考文献

    1. 基于Android10.0适配应用界面--修改系统源码:juejin.cn/post/693094…
    2. Android屏幕尺寸适配常见方案smallestWidth:blog.csdn.net/cat_is_so_c…

    作者:饭盒君
    链接:https://juejin.cn/post/7182844233491939388

    相关文章

      网友评论

          本文标题:通过修改sw来适配应用界面——源码修改

          本文链接:https://www.haomeiwen.com/subject/phwxcdtx.html