Android 系统属性学习与使用

背景

最近学到 Android 系统属性相关的知识,学习了一下,做一下整理和记录。

日常开发中的应用

在开发过程中有时需要使用系统属性。

  • 例如获取系统软件版本,获取设备名名称。常见的就是使用 android.os.Build 类里面提供的字段了,本质上上通过 SystemProperties 进行读取相关的字段。
  • 通过开发者选项,控制部分调试功能。本质上也是通过各种系统属性进行控制的。

读写系统属性

读取系统属性,有下面3种方式。

adb 命令行

arduino 复制代码
//读取所有的prop,会输出所有系统属性的key和value
adb shell getprop
//读取key为propName的系统属性
adb shell getprop ${propName}
//修改key为propName的系统属性,新值为propValue
adb shell setprop ${propName} ${propValue}

java 代码

由于 SystemProperties.java 的 API 为系统 API ,普通应用无法直接使用,可以通过反射来调用 get 和 set prop。

SystemProperties 提供了多种不同值的类型的方法,可以根据值的类型修改下面的示例方法即可。

vbnet 复制代码
public class SystemPropertiesHelper {
    private static final String TAG = "SystemPropertiesHelper";

    public static boolean getBoolean(String key, boolean def) throws IllegalArgumentException {
        try {
            Class SystemPropertiesClass = Class.forName("android.os.SystemProperties");
            Method getBooleanMethod =
                    SystemPropertiesClass.getDeclaredMethod(
                            "getBoolean", String.class, boolean.class);
            getBooleanMethod.setAccessible(true);
            return (boolean) getBooleanMethod.invoke(SystemPropertiesClass, key, def);
        } catch (Exception e) {
            Log.e(TAG, "Failed to invoke SystemProperties.getBoolean()", e);
        }
        return def;
    }
}

c++代码

这里用 btrace 里面的代码,读取 sdk 版本号,对应 key 是 ro.build.version.sdk。

binary_trace.cpp

scss 复制代码
  char sdk_version_str[PROP_VALUE_MAX];
  __system_property_get("ro.build.version.sdk", sdk_version_str);
  auto api = strtol(sdk_version_str, nullptr, 10);

显示布局边界的原理

本质上就是,进程间信息共享机制。在开发者选项打开对应的设置后,大概的流程是这样的。

ShowLayoutBoundsPreferenceController

typescript 复制代码
public class ShowLayoutBoundsPreferenceController extends DeveloperOptionsPreferenceController
        implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin {

    private static final String DEBUG_LAYOUT_KEY = "debug_layout";

    public ShowLayoutBoundsPreferenceController(Context context) {
        super(context);
    }

    @Override
    public String getPreferenceKey() {
        return DEBUG_LAYOUT_KEY;
    }

    @Override
    public boolean onPreferenceChange(Preference preference, Object newValue) {
        final boolean isEnabled = (Boolean) newValue;
        DisplayProperties.debug_layout(isEnabled);
        SystemPropPoker.getInstance().poke();
        return true;
    }

    @Override
    public void updateState(Preference preference) {
        final boolean isEnabled = DisplayProperties.debug_layout().orElse(false);
        ((SwitchPreference) mPreference).setChecked(isEnabled);
    }

    @Override
    protected void onDeveloperOptionsSwitchDisabled() {
        super.onDeveloperOptionsSwitchDisabled();
        DisplayProperties.debug_layout(false);
        ((SwitchPreference) mPreference).setChecked(false);
    }
}
  • 当开关值发生变化的时候,执行onPreferenceChange 方法。
  • 调用 DisplayProperties 更新 debug_layout 的值。
  • SystemPropPoker 使用 Binder,通知所有的系统服务。

SystemPropPoker

typescript 复制代码
public class SystemPropPoker  {
    private static final String TAG = "SystemPropPoker";

    private static final SystemPropPoker sInstance = new SystemPropPoker();

    private boolean mBlockPokes = false;

    private SystemPropPoker() {}

    @NonNull
    public static SystemPropPoker getInstance() {
        return sInstance;
    }

    public void blockPokes() {
        mBlockPokes = true;
    }

    public void unblockPokes() {
        mBlockPokes = false;
    }

    public void poke() {
        if (!mBlockPokes) {
            createPokerTask().execute();
        }
    }

    @VisibleForTesting
    PokerTask createPokerTask() {
        return new PokerTask();
    }

    public static class PokerTask extends AsyncTask<Void, Void, Void> {

        @VisibleForTesting
        String[] listServices() {
            return ServiceManager.listServices();
        }

        @VisibleForTesting
        IBinder checkService(String service) {
            return ServiceManager.checkService(service);
        }

        @Override
        protected Void doInBackground(Void... params) {
            String[] services = listServices();
            if (services == null) {
                Log.e(TAG, "There are no services, how odd");
                return null;
            }
            for (String service : services) {
                IBinder obj = checkService(service);
                if (obj != null) {
                    Parcel data = Parcel.obtain();
                    try {
                        obj.transact(IBinder.SYSPROPS_TRANSACTION, data, null, 0);
                    } catch (RemoteException e) {
                        // Ignore
                    } catch (Exception e) {
                        Log.i(TAG, "Someone wrote a bad service '" + service
                                + "' that doesn't like to be poked", e);
                    }
                    data.recycle();
                }
            }
            return null;
        }
    }
}
  • SystemPropPoker 会创建一个 AsyncTask 任务,往所有的系统服务,发送一个 transact ,code 为 IBinder.SYSPROPS_TRANSACTION。
  • 这个 code 值为 1599295570 ,看起来是专门用于通知系统属性改变的。
  • 这个操作,可以让系统属性prop 的改动立马生效,不需要重启 App。

ActivityManagerService

ActivityManagerService

AMS 收到 SYSPROPS_TRANSACTION 后,向所有 app 的 IApplicationThread 也做这个 transact ,因此每个 app 进程都收到了属性变化的回调。

WindowManagerGlobal

WindowManagerGlobal

View,最终是通过 WindowMangerGlobal#addView 方法添加到屏幕上。

WindowManagerGlobal 中通过 SystemProperties.addChangeCallback 设置了监听器,当 prop 发生变化的时候,会调用 ViewRootImpl 的 loadSystemProperties 方法。

ViewRootImpl

  • loadSystemProperties 方法,会去加载各种系统属性的字段。
  • 例如读取 debug_layout 的结果,并赋值到 AttachInfo 的 mDebugLayout 字段。
  • 执行 invalidateWorld 方法,调用 invalidate(),自身以及其子View全部都重新绘制。 ViewRootImpl
scss 复制代码
public void loadSystemProperties() {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                // Profiling
                mProfileRendering = SystemProperties.getBoolean(PROPERTY_PROFILE_RENDERING, false);
                profileRendering(mAttachInfo.mHasWindowFocus);

                // Hardware rendering
                if (mAttachInfo.mThreadedRenderer != null) {
                    if (mAttachInfo.mThreadedRenderer.loadSystemProperties()) {
                        invalidate();
                    }
                }

                // Layout debugging
                boolean layout = DisplayProperties.debug_layout().orElse(false);
                if (layout != mAttachInfo.mDebugLayout) {
                    mAttachInfo.mDebugLayout = layout;
                    if (!mHandler.hasMessages(MSG_INVALIDATE_WORLD)) {
                        mHandler.sendEmptyMessageDelayed(MSG_INVALIDATE_WORLD, 200);
                    }
                }
            }
        });
    }
    
//处理 MSG_INVALIDATE_WORLD 消息,
	case MSG_INVALIDATE_WORLD: {
                    if (mView != null) {
                        invalidateWorld(mView);
                    }
                }
//invalidateWorld 方法,递归执行
    void invalidateWorld(View view) {
        view.invalidate();
        if (view instanceof ViewGroup) {
            ViewGroup parent = (ViewGroup) view;
            for (int i = 0; i < parent.getChildCount(); i++) {
                invalidateWorld(parent.getChildAt(i));
            }
        }
    }

View

  • View 的 draw 方法,会去读取刚刚保存的 AttachInfo 的 mDebugLayout 字段。
  • 如果为true,通过 debugDrawFocus 绘制边界。

应用场景

简单了解了一下调试布局边界开关的实现过程,主要是清楚这个过程是怎样工作的。

日常的开发过程中,其实基本不会接触到这块实现。

但是学习这些知识后,还是有点用的。

应用场景一

日常开发中,会经常用到开发者选项,比如打开布局边界显示,打开展示过程绘制情况等等。

  • 常规操作:打开手机设置页,打开开发者选项页,滑动页面,找到相应的控制项,点击控制项,再回来App。

这个操作,个人觉得是比较繁琐的,打开和关闭,每次都要这样去重复的操作。

  • 优化操作:编写一些自定义脚本,或者编写可视化的UI。在电脑上,执行adb setprop命令,一键修改prop,实时生效。这样就不需要去手动去操作开发者选项了。
  • 大部分实现,可以通过类似这两个命令去执行就可以了。
arduino 复制代码
//修改debug.layout属性
adb shell setprop debug.layout true
//通过其他
adb shell service call activity 1599295570

应用场景二

自定义系统属性,动态修改系统属性,方便动态调试代码。

  • 类似 btrace,自定义各种 debug 类型的 prop,控制不同类型的插桩开关。
  • 在桌面脚本通过 adb setprop 给手机设置参数,App 通过 __system_property_get 来读取参。
  • 挺方便的一种实现方式,这样就相当于可以在 pc 端,控制 App 执行相应的逻辑。
相关推荐
HerayChen19 分钟前
HbuildderX运行到手机或模拟器的Android App基座识别不到设备 mac
android·macos·智能手机
顾北川_野20 分钟前
Android 手机设备的OEM-unlock解锁 和 adb push文件
android·java
hairenjing112322 分钟前
在 Android 手机上从SD 卡恢复数据的 6 个有效应用程序
android·人工智能·windows·macos·智能手机
小黄人软件1 小时前
android浏览器源码 可输入地址或关键词搜索 android studio 2024 可开发可改地址
android·ide·android studio
dj15402252031 小时前
group_concat配置影响程序出bug
android·bug
周全全2 小时前
MySQL报错解决:The user specified as a definer (‘root‘@‘%‘) does not exist
android·数据库·mysql
- 羊羊不超越 -2 小时前
App渠道来源追踪方案全面分析(iOS/Android/鸿蒙)
android·ios·harmonyos
wk灬丨3 小时前
Android Kotlin Flow 冷流 热流
android·kotlin·flow
千雅爸爸3 小时前
Android MVVM demo(使用DataBinding,LiveData,Fresco,RecyclerView,Room,ViewModel 完成)
android