背景
最近学到 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。
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
AMS 收到 SYSPROPS_TRANSACTION 后,向所有 app 的 IApplicationThread 也做这个 transact ,因此每个 app 进程都收到了属性变化的回调。
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 执行相应的逻辑。