Android 如何优雅地监听SystemProperties属性值变化

引言

我司项目中会频繁用到persist.sys.xxx的属性值,系统中预埋接口,通过属性值控制,以应对客户多样化的需求定制。
以往都是先设置属性值,再重启设备使能生效,抽空研究一下实时监听属性值变化,最后在csdn上查到监听SystemProperties变化 这篇文章。
博主的实现方法给了我很大的启发,在该基础上,分别在第三方应用的ActivityService中实现了SystemProperties属性值的实时监听,app需要系统签名

注:阅读本博客前,请先阅读监听SystemProperties变化这篇博客。

Activity

		Parcel _data = Parcel.obtain();
        try {
            final Class<?> systemPropertyClass = Class.forName("android.os.SystemProperties");
            final Method addChangeCallback = systemPropertyClass.getDeclaredMethod(
                    "addChangeCallback", Runnable.class);
            final Method getMethod = systemPropertyClass.getDeclaredMethod(
                    "get", String.class, String.class);
            final Method setMethod = systemPropertyClass.getDeclaredMethod(
                    "set", String.class, String.class);
            addChangeCallback.invoke(null, new Runnable() {
                @Override
                public void run() {
                    Log.e("DebugUtil",  "SystemProperties change");
                    try {
                        String result = (String) getMethod.invoke(
                                null, "persist.sys.test", "77777");
                        Log.e("DebugUtil",  "SystemProperties change to " + result);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
            });
            
            setMethod.invoke(null, "persist.sys.test", "88888");

            IBinder amsBinder = getService(Context.ACTIVITY_SERVICE);
            _data.writeInterfaceToken("android.app.IActivityManager");
            Boolean result = amsBinder.transact(
                    ('_'<<24)|('S'<<16)|('P'<<8)|'R'/*IBinder.SYSPROPS_TRANSACTION*/,
                    _data, null, 0);
            Log.e("DebugUtil", "result = " + result);
        } catch (RemoteException | IllegalAccessException
                | InvocationTargetException | ClassNotFoundException
                | NoSuchMethodException e) {
            e.printStackTrace();
        } finally {
            _data.recycle();
        }

Service

		try {
            Class<?> systemPropertyClass = Class.forName("android.os.SystemProperties");
            Method addChangeCallback = systemPropertyClass.getDeclaredMethod(
                    "addChangeCallback", Runnable.class);
            Method getMethod = systemPropertyClass.getDeclaredMethod(
                    "get", String.class, String.class);
            Method setMethod = systemPropertyClass.getDeclaredMethod(
                    "set", String.class, String.class);
            addChangeCallback.invoke(null, new Runnable() {
                @Override
                public void run() {
                    DebugUtil.e(TAG, "SystemProperties change");
                    try {
                        String result = (String) getMethod.invoke(null, "persist.sys.test", "123");
                        DebugUtil.e(TAG,  "SystemProperties change to " + result);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
            });

            setMethod.invoke(null, "persist.sys.test", "456");

            String[] services = DeviceManagerUtil.listServices();
            if (services == null) {
                DebugUtil.e(TAG, "There are no services, how odd");
            }
            for (String service : services) {
                IBinder obj = DeviceManagerUtil.checkService(service);
                if (obj != null) {
                    Parcel data = Parcel.obtain();
                    try {
                        obj.transact(
                                ('_'<<24)|('S'<<16)|('P'<<8)|'R'/*IBinder.SYSPROPS_TRANSACTION*/,
                                data, null, 0);
                    } catch (RemoteException e) {
                        // Ignore
                    } catch (Exception e) {
                        DebugUtil.e(TAG, e.getMessage());
                    }
                    data.recycle();
                }
            }
        } catch (IllegalAccessException | InvocationTargetException
                | ClassNotFoundException | NoSuchMethodException e) {
            e.printStackTrace();
        }

DeviceManagerUtil.java

	public static String[] listServices() {
        try {
            Class<?> serviceManagerClass = Class.forName("android.os.ServiceManager");
            Method listServices = serviceManagerClass.getDeclaredMethod("listServices");
            return (String[]) listServices.invoke(null);
        } catch (ClassNotFoundException | NoSuchMethodException
                | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static IBinder checkService(String service) {
        try {
            Class<?> serviceManagerClass = Class.forName("android.os.ServiceManager");
            Method listServices = serviceManagerClass.getDeclaredMethod(
                    "checkService", String.class);
            return (IBinder) listServices.invoke(null, service);
        } catch (ClassNotFoundException | NoSuchMethodException
                | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }

总结+分析

1.监听方需要添加回调函数
SystemProperties.addChangeCallback()设置监听回调函数:非源码环境下,通过反射实现;源码环境下,直接调用addChangeCallback()方法即可。
2.设置属性值后,需要通知监听方
通知监听方的方式其实大同小异,遍历所有的Activity和Service,获取到它们的IBinder对象,调用onTransact()方法,通过Binder实现跨进程通信,最终调用SystemProperties的callChangeCallbacks()方法,遍历所有的callbacks的run()方法,通知监听方。
IBinder.SYSPROPS_TRANSACTION的值请查找自己的项目源码获取。
此处有两点需要说明:一是这里涉及到的Binder原理,博主的理解程度还达不到完全讲解清楚的程度(仅意会不会言传),请自行查阅Binder相关原理;二是addChangeCallback()不可执行多次,否则回调也会回调多次。

ActivityManagerService的onTransact()方法如下:

@Override
    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
            throws RemoteException {
        if (code == SYSPROPS_TRANSACTION) {
            // We need to tell all apps about the system property change.
            ArrayList<IBinder> procs = new ArrayList<IBinder>();
            synchronized (this) {
                final int NP = mProcessList.mProcessNames.getMap().size();
                for (int ip = 0; ip < NP; ip++) {
                    SparseArray<ProcessRecord> apps =
                            mProcessList.mProcessNames.getMap().valueAt(ip);
                    final int NA = apps.size();
                    for (int ia = 0; ia < NA; ia++) {
                        ProcessRecord app = apps.valueAt(ia);
                        if (app.thread != null) {
                            procs.add(app.thread.asBinder());
                        }
                    }
                }
            }

            int N = procs.size();
            for (int i=0; i<N; i++) {
                Parcel data2 = Parcel.obtain();
                try {
                    procs.get(i).transact(IBinder.SYSPROPS_TRANSACTION, data2, null,
                            Binder.FLAG_ONEWAY);
                } catch (RemoteException e) {
                }
                data2.recycle();
            }
        }
        try {
            return super.onTransact(code, data, reply, flags);
        } catch (RuntimeException e) {
            // The activity manager only throws certain exceptions intentionally, so let's
            // log all others.
            if (!(e instanceof SecurityException
                    || e instanceof IllegalArgumentException
                    || e instanceof IllegalStateException)) {
            /// M: Enhance wtf excetpion @{
                if (mAmsExt.IsBuildInApp()) {
                    Slog.wtf(TAG, "Activity Manager Crash."
                        + " UID:" + Binder.getCallingUid()
                        + " PID:" + Binder.getCallingPid()
                        + " TRANS:" + code, e);
                } else {
                    Slog.e(TAG, "Activity Manager Crash."
                        + " UID:" + Binder.getCallingUid()
                        + " PID:" + Binder.getCallingPid()
                        + " TRANS:" + code, e);
                }
            /// @}
            }
            throw e;
        }
    }

SystemPropPoker.java

/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settingslib.development;

import android.os.AsyncTask;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;

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;
        }
    }
}
  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值