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 执行相应的逻辑。
相关推荐
十幺卜入1 天前
Unity3d C# 基于安卓真机调试日志抓取拓展包(Android Logcat)
android·c#·unity 安卓调试·unity 安卓模拟·unity排查问题
frontend_frank1 天前
脱离 Electron autoUpdater:uni-app跨端更新:Windows+Android统一实现方案
android·前端·javascript·electron·uni-app
薛晓刚1 天前
MySQL的replace使用分析
android·adb
DengDongQi1 天前
Jetpack Compose 滚轮选择器
android
stevenzqzq1 天前
Android Studio Logcat 基础认知
android·ide·android studio·日志
代码不停1 天前
MySQL事务
android·数据库·mysql
朝花不迟暮1 天前
使用Android Studio生成apk,卡在Running Gradle task ‘assembleDebug...解决方法
android·ide·android studio
yngsqq1 天前
使用VS(.NET MAUI)开发第一个安卓APP
android·.net
Android-Flutter1 天前
android compose LazyVerticalGrid上下滚动的网格布局 使用
android·kotlin
Android-Flutter1 天前
android compose LazyHorizontalGrid水平滚动的网格布局 使用
android·kotlin