精华内容
下载资源
问答
  • 接口及面向接口编程 接口 ...接口即声明,声明哪些方法对外提供。 Java8中接口可以拥有方法体(类似抽象类) 接口:只能声明,不能实现 抽象类:既可以声明也可以实现。 类:...

    接口及面向接口编程

    接口

    • 用于沟通的中介物的抽象化。

    • 实体把自己提供给外界的一种抽象化说明,用以由内部操作分离出外部沟通方法。使其能被修改内部而不影响外部其他实体的与其交互的方式(对外的一种说明,说明会提供哪些功能,内部实现对外不公开)

    • 接口即声明,声明哪些方法对外提供。

    • Java8中接口可以拥有方法体(类似抽象类)

       接口:(1.8以前)只能有声明,不能有实现。(1.8)静态方法与default方法可以有方法体!
       抽象类:既可以有声明也可以有实现。(抽象方法只能声明,不能实现;非抽象方法必须实现)
       类:实现
      

    面向接口编程

    • 结构设计中,分清层次及调用关系,每层只向外(上层)提供一组功能的接口,各层仅依赖接口而非实现类

    • 接口内部的实现变动不影响各层之间的调用,这一点在公共服务中尤为重要

    • “面向接口编程”中的“接口”是用于【隐藏具体的实现】和【实现多态性】的组件

    IOC控制反转

    控制反转:控制权的转移,应用程序本身不负责依赖对象的创建与维护,而是由外部容器负责创建和维护。
    依赖注入(DI):是一种实现方式,由IOC容器在运行期间,动态的将某种依赖关系注入到对象之中
    目的:创建对象并且组装对象之间的关系

    这里写图片描述

    获得依赖对象的过程被反转了。(不是由自己创建,而是由外部容器负责创建和维护。)【获得依赖对象的过程由自身管理变为了由IOC主动注入】
    所谓依赖注入,就是IOC容器在运行期间,动态的将某种依赖关系注入到对象中。

    单元测试

    springIOC及Bean容器(2)

    创建UnitTestBase类,完成对Spring配置文件的加载/销毁
    所有的单元测试类都继承自UnitTestBase,通过它的getBean方法获取想要得到的对象
    子类(具体执行单元测试的类)加注解:@RunWit(BlockJUnit4ClassRunner.class)

    
    package com.zjx.interfaces.test;
    
    import org.junit.After;
    import org.junit.Before;
    import org.springframework.beans.BeansException;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import org.springframework.util.StringUtils;
    
    
    public class UnitTestBase {
        /**
         * 相对路径应用上下文
         */
        private ClassPathXmlApplicationContext context;
    
        /**
         * XML文件的存放路径
         */
        private String springXmlPath;
    
        public UnitTestBase() {
    
        }
    
        public UnitTestBase(String springXmlPath){
            this.springXmlPath = springXmlPath;
        }
        @Before
        public void before(){
            if (StringUtils.isEmpty(springXmlPath)) {
                springXmlPath = "classpath*:spring-*.xml";//加载spring配置文件
            }
            try {
                // xml文件用逗号或者空格符隔开,均可加载
                context = new ClassPathXmlApplicationContext(springXmlPath.split("[,\\s]+"));
                context.start();
            } catch (BeansException e) {
                e.printStackTrace();
            }
        }
        @After
        public void after(){
            context.destroy();
        }
    
        @SuppressWarnings("unchecked")
        protected <T extends Object> T getBean(String beanId){
            try {
                return(T)context.getBean(beanId);
            } catch (BeansException e) {
                e.printStackTrace();
                return null;
            }
        }
    
        protected <T extends Object> T getBean(Class<T> clazz){
            try {
                return context.getBean(clazz);
            } catch (BeansException e) {
                e.printStackTrace();
                return null;
            }
        }
    }
    
    

    Spring框架中都用的

    设计模式

    代理模式:在AOP和remoting中被用的比较多。
    单例模式:在spring配置文件中定义的bean默认为单例模式。
    模板方法模式:用来解决代码重复的问题。
    前端控制器模式:Spring提供了DispatcherServlet来对请求进行分发。
    依赖注入模式:贯穿于BeanFactory / ApplicationContext接口的核心理念。
    工厂模式:BeanFactory用来创建对象的实例。

    展开全文
  • 开放API接口安全设计

    千次阅读 2019-09-04 14:19:26
    随着项目前后端分离的火热,后台开发的重点主要是对外提供接口,那么API接口的安全就是要考虑的问题。前后端分离和传统的开发模式很大的差异,本文将针对以下问题进行探讨: 前后分离和传统项目的区别 为什么...

    前言

    随着项目前后端分离的火热,后台开发的重点主要是对外提供接口,那么API接口的安全就是要考虑的问题。前后端分离和传统的开发模式有很大的差异,本文将针对以下问题进行探讨:

    1. 前后分离和传统项目的区别
    2. 为什么前后分离需要关注接口安全问题
    3. 攻击方式有哪些
    4. 如何保障接口的安全

    一、前后分离和传统项目的区别

    1:前端渲染方式不同

    传统项目是前后端不分离的,后端通过模板渲染引擎在后端渲染前端页面然后发送到浏览器展现。不同的语言有不同的模板渲染引擎比如JAVA有jsp、beetl、hanldlebars,PHP有Smarty、Twig、Haml,nodejs有ejs、jade等。然而前后端分离之后前端的渲染工作将交给前端项目自行渲染,后台只通过API接口来提供数据。

    2:客户端状态维护方式不同

    传统项目通过session来维护后端和客户端的状态,然而前后分离后接口请求(ajax)是无状态请求(当然也可以修改配置来达到有状态,但是这种方式对于分布式部署来说是不可取的,当然配置session共享后也可以达到目的,但是这样对负载均衡有一定的副作用)。前后分离后一般通过token的方式来维护请求状态。

    二、为什么前后分离需要关注接口安全问题

    接口是通过http请求的方式来请求和获取数据的,这样的请求是可以通过抓包工具来拦截请求的,如果不处理的话会有很大的安全隐患。比如获取短信的接口被拦截那么别人就可以恶意刷你的短信流量,上传文件接口被拦截别人可以恶意上传从而导致服务器崩溃。黑客还可以通过Dos或CSRF来攻击服务器,所以接口安全还是很重要的。

    三、攻击方式有哪些

    常见的web攻击方式有:XSS、CSRF、SQL注入、DDOS、重放。

    XSS(跨站脚本攻击):对所有用户提交内容进行可靠的输入验证对“<”,“>”,“;”,“””等字符做过滤。

    CSRF(跨站请求伪造 cross site request forgery):

    通过伪装来自受信任用户的请求来利用受信任的网站,可以利用你的身份发邮件、发短信、进行交易转账等,甚至盗取你的账号

    例如:你在A网站登录了账号,在没有退出的情况下访问了B网站,B网站有黑客上传的图片且地址为A网站的接口。如果A网站存在CSRF漏洞那么你在B网站时相当于你时A的登录用户,那么B的图片地址请求的接口就被伪造成你的合法请求。

    防御:尽量使用POST;cookie设置为HttpOnly;增加token;通过Referer识别。

    重放攻击:利用抓包工具将请求重复多次发送的方式来达到攻击服务器的目的。可以通过请求时效性来防御。

    四、如何保障接口的安全

    1:调用身份认证token(防CSRF攻击)

    用户登录时根据用户生成token用来后期识别用户身份

    2:参数签名(防篡改)

    对appkey(客户端标识),token,url,参数,timestamp,rand进行MD5签名。

    请求到达服务器时需要用相同的方法进行签名并和发送过来的签名进行对比,如果不同就说明请求参数被篡改过。

    3:时效性(防重放和DDos攻击)

    通过timestamp和redis来限制请求的时效

    首先根据项目情况设定一个有效时长比如设为60s

    当请求到达服务器时首先拿timestamp和系统时间对比,如果在60s内那么timestamp校验通过,如果大于60s那么请求过期。

    如果只通过timestamp来防重放那么在60s内还是可以重放请求的,所以这样还是不够的。

    我们还需要设置一个nonce。

    请求到达服务器时去redis中查找key为nonce:{sign}的string,如果没有就创建这个key并把失效期设置为timestamp的失效期比如是60s,如果有说明在60s内这个请求已经请求过那么这个请求可以判断为重放请求。

    因为客户端和服务器的timestamp可能存在误差如果误差大于60s那么所有的请求就都被拦截了,如何解决这个问题呢?我们可以在每个接口返回时都返回一个timestamp,客户端通过这个timestamp来调整本地的timestamp这样timestamp就已服务端为准了。

    校验流程图如下:

    展开全文
  • 随着项目前后端分离的火热,后台开发的重点主要是对外提供接口,那么API接口的安全就是要考虑的问题。本文将针对api安全问题进行探讨。 目录 前言: 为什么前后分离需要关注接口安全问题 攻击方式有哪些 如何...

    前言:

    随着项目前后端分离的火热,后台开发的重点主要是对外提供接口,那么API接口的安全就是要考虑的问题。本文将针对api安全问题进行探讨。

    目录

    前言:

    为什么前后分离需要关注接口安全问题

    攻击方式有哪些

    如何保障接口的安全

    1. 数据加密

    2. 数据加签

    3. 时间戳机制

    4. 白名单机制

    5. AppId 机制

    6. 黑名单机制

    7. 限流机制

    总结:


    为什么前后分离需要关注接口安全问题

    接口是通过http请求的方式来请求和获取数据的,这样的请求是可以通过抓包工具来拦截请求的,如果不处理的话会有很大的安全隐患。比如获取短信的接口被拦截那么别人就可以恶意刷你的短信流量,上传文件接口被拦截别人可以恶意上传从而导致服务器崩溃。黑客还可以通过Dos或CSRF来攻击服务器,

    个人觉得安全措施大体来看主要在两个方面,一方面就是如何保证数据在传输过程中的安全性,另一个方面是数据已经到达服务器端,服务器端如何识别数据,如何不被攻击;所以接口安全还是很重要的。下面具体看看都有哪些安全措施。

    攻击方式有哪些

    1. 常见的web攻击方式有:XSS、CSRF、SQL注入、DDOS、重放。
    2. XSS(跨站脚本攻击):对所有用户提交内容进行可靠的输入验证对“<”,“>”,“;”,“””等字符做过滤。
    3. CSRF(跨站请求伪造 cross site request forgery):
    4. 通过伪装来自受信任用户的请求来利用受信任的网站,可以利用你的身份发邮件、发短信、进行交易转账等,甚至盗取你的账号
    5. 例如:你在A网站登录了账号,在没有退出的情况下访问了B网站,B网站有黑客上传的图片且地址为A网站的接口。如果A网站存在CSRF漏洞那么你在B网站时相当于你时A的登录用户,那么B的图片地址请求的接口就被伪造成你的合法请求。
    6. 防御:尽量使用POST;cookie设置为HttpOnly;增加token;通过Referer识别。
    7. 重放攻击:利用抓包工具将请求重复多次发送的方式来达到攻击服务器的目的。可以通过请求时效性来防御。

    如何保障接口的安全

    1. 数据加密

    我们知道数据在传输过程中是很容易被抓包的,如果直接传输比如通过 http 协议,那么用户传输的数据可以被任何人获取;所以必须对数据加密,常见的做法对关键字段加密比如用户密码直接通过 md5 加密;现在主流的做法是使用 https 协议,在 http 和 tcp 之间添加一层加密层 (SSL 层),这一层负责数据的加密和解密。

    2. 数据加签

    数据加签就是由发送者产生一段无法伪造的一段数字串,来保证数据在传输过程中不被篡改;你可能会问数据如果已经通过 https 加密了,还有必要进行加签吗?数据在传输过程中经过加密,理论上就算被抓包,也无法对数据进行篡改;但是我们要知道加密的部分其实只是在外网,现在很多服务在内网中都需要经过很多服务跳转,所以这里的加签可以防止内网中数据被篡改;

    3. 时间戳机制

    数据是很容易被抓包的,但是经过如上的加密,加签处理,就算拿到数据也不能看到真实的数据;但是有不法者不关心真实的数据,而是直接拿到抓取的数据包进行恶意请求;这时候可以使用时间戳机制,在每次请求中加入当前的时间,通过timestamp和redis来限制请求的时效首先根据项目情况设定一个有效时长比如设为60s,当请求到达服务器时首先拿timestamp和系统时间对比,如果在60s内那么timestamp校验通过,如果大于60s那么请求过期。如果只通过timestamp来防重放那么在60s内还是可以重放请求的,所以这样还是不够的。我们还需要设置一个nonce。请求到达服务器时去redis中查找key为nonce:{sign}的string,如果没有就创建这个key并把失效期设置为timestamp的失效期比如是60s,如果有说明在60s内这个请求已经请求过那么这个请求可以判断为重放请求。

    4. 白名单机制

    如果请求的服务器是已知的,可以在服务端判断请求api的服务器的ip是否是约定好的,例如:A部门(192.168.1.1)和B部门(192.168.1.2)需要通过公网进行远程数据调用(方法XXX)。那么B部门在方法XXX被调用的时候,判断时候和已经保存在redis或者数据库中的A部门服务器ip是否一致,如果一致,则放行,如果不一致则终止调用。

    5. AppId 机制

    大部分网站基本都需要用户名和密码才能登录,并不是谁来能使用我的网站,这其实也是一种安全机制;对应的对外提供的接口其实也需要这么一种机制,并不是谁都可以调用,需要使用接口的用户需要在后台开通 appid,提供给用户相关的密钥;在调用的接口中需要提供 appid + 密钥,服务器端会进行相关的验证;

    6. 黑名单机制

    与白名单相对应的就是白名单机制:黑名单是适用于,开放给多个客户使用的一种安全手段,假设你不知道调用方的具体信息,并且不能将全部调用都禁止调,那么可以采用黑名单机制。黑名单是基于公司内部的安全规则来规定的,比如,公司规定,1分钟调用5次,就拉入黑名单等等。

    7. 限流机制

    此方法是针对服务器的某个具体的方法而言,并不是针对调用者的。具体方法是,假设方法AAA请求量过大。我们可以使用业内开源的一些方法来进行处理,常见的算法有,漏桶限流,令牌桶限流。和计数器限流,这里就不细讲了,感兴趣的可以留言或者百度即可。

    总结:

    本文总结了一些常用api安全的方法,各位可以基于自己业务的需求,选择性使用即可。过多的限制和判断必然会导致性能的下降和代码维护难度的增加


    创作不易,各位的支持和认可,就是我创作的最大动力,

    【转载请联系本人】 如有问题,请联系我。欢迎斧正!不胜感激 !

    求点赞👍 求关注❤️ 求分享👥 求留言📪

     

     

    展开全文
  • 一、对外提供接口有哪些方式? 二、开发步骤 Step1.添加一个专属系统级服务 2.1.1.模仿Android原生服务接口,如WifiManager,规划自己Manager 2.1.2.为我们Manager生成AIDL 2.1.3.编写系统级服务 2.1.4....

    目录

    故事背景

    二、开发步骤

    Step1.添加一个专属系统级服务

    2.1.1.模仿Android原生服务接口,如WifiManager,规划自己的Manager

    2.1.2.为我们的Manager生成AIDL

    2.1.3.编写系统级服务

    2.1.4.注册服务

    2.1.5.初始化服务

    2.1.6.添加编译规则

    2.1.7.为新服务添加SELinux权限

    Step2.打包SDK,供第三方程序调用

    2.2.1.打包SDK

    2.2.2.使用SDK

    Step3.再添加一个专属系统级总控APP

    2.3.1.进程与进程间的交互

    2.3.2.编写总控APP代码

    三、扩展扩展再扩展


    【怒草 https://blog.csdn.net/visionliao?spm=1011.2124.3001.5113 未经允许严禁转载,请尊重作者劳动成果。】

    故事背景

    这篇文章是专门为马囧写的技术参考文档,主要涉及到Android系统的扩展开发,目的是让第三方应用可以很方便的调用一些高权限的系统级接口。有需要的小伙伴也可以参考。

    那么,马囧是何许人也?当年马囧是我司软件部的经理,后来马走了牛上任,我就是在牛上任的时候进入的公司。马囧离开后自己开了家公司,干起了高新技术创新型企业的勾当,并且因为双方都认识,公司也就是隔壁或上下楼层的距离,所以两家公司关系比较好,我也就和马囧渐渐的熟络了起来。

    创业公司嘛,你懂的。马囧身兼CEO、CTO、COO、CXXX.... 底层出身的马囧,不仅要做底层的技术,中间层和应用层的业务都要做,马囧长的很高,所以脸也很长,还要被客户时不时的按在地上摩擦摩擦,蓦然回首,发现马囧的脸被摩擦的更长了... 于是决定写下这篇文章,希望能给马囧带来一点帮助。


    一、对外提供接口有哪些方式?

    这个方式就很多了,至少有以下几种方式:

    • 系统添加好系统服务接口,和第三方应用开发这对接AIDL,需要第三方应用开发人员自行编写AIDL文件和接口打包到应用中
    • 简单的功能直接通过广播跨进程传递消息,系统收到消息后做相应的处理
    • 通过暴露ContentProivder接口给第三方开发者,让其通过ContentProvider跨进程通信

    以上的方式各自有各自的优点,但是缺点也很明显,必需要双方亲密的合作无间(广播那个还好),所有的协议都需要双方沟通好,一旦一方出了一点差错,这个功能就完犊子了。这对于做事有目标有要求长的还帅气逼人马囧来说,这些方式就显得太Low了,马囧要的是那种用户只需要简单的调用一个API,一行代码即可实现复杂功能的完美主义者,要让用户用过了就会竖起大拇指:“诶,你这SDK好,这SDK妙,这SDK用的我呱呱叫”。

    那要如何实现这么狂拽酷炫吊炸天的对外接口呢?我们先来参考参考Google原生的系统API

    private boolean setWifiEnable(boolean enable) {
            WifiManager wm = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
            return wm.setWifiEnabled(enable);
    }
    Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
    vibrator.vibrate(1000);

    上面是Google系统服务延续多年的标准使用方式,还有其它很多服务都是类似的使用方式。它们好用吗?当然好用(存在使用权限的情况下),至少在开发中调用起来还是肥肠不错的,但是这能让用户用起来得到"呱呱叫"的优良体验吗?显然不能,很明显的,这玩意还必须要有上下文(Context)才能发起调用,万一我想随时随地没有上下文的时候也调用呢?这就做不到了。

    要实现这么牛逼的功能也是可以的,下面我们一起来开发一款对外SDK,可以让第三方应用调用需要系统级权限的接口,并且调用方式比Google的原生接口还要人性化,不需要Context,随时随地都可以调用。

     

    二、开发步骤

    1. 添加一个属于自己的专属系统级服务
    2. 打包SDK,供第三方程序调用
    3. 再添加一个属于自己的专属系统级总控APP

    Step1.添加一个专属系统级服务

    2.1.1.模仿Android原生服务接口,如WifiManager,规划自己的Manager

    在frameworks/base/core/java/com/目录下创建自己的目录层级 majiong/mdm/sdk,在这里创建MaJiongManager.java,为什么是在这个目录下呢?在其它的目录下行不行?其它目录下当然也行,但是这个目录有其特殊性,看Google framework的目录结构,这个目录下本身就是供厂商添加自己代码的地方,最重要的是,在这里添加代码编译系统的时候不用update-api,编译过系统源码的人都知道,update-api这是一个很繁琐的事,而且会增大系统SDK,作为ODM厂商,我们将自己需要打包进系统的代码放在这里,就可以避免update-api。

    再有就是通常情况下,系统开发不会只添加一个服务和少量源文件,一般都会有大量的需求,所有添加的源文件会很多,我们不希望自己添加的东西过多的和系统原生的代码混杂在一起,创建独立的目录也可以更好的管理代码。

    package com.majiong.mdm.sdk;
    
    import android.os.majiong.IMaJiongService;
    import android.os.RemoteException;
    import android.os.ServiceManager;
    import android.util.Log;
    
    /*
     * 
     * @author   liaohongfei
     * 
     * @Date  2019 2019-5-29  上午10:08:23
     * 
     */
    public class MaJiongManager {
        private static final String TAG = "majiong";
        // 关闭
        public static final int CLOSE = 0;
        // 打开
        public static final int OPEN = 1;
        // 强制关闭用户不能打开
        public static final int FORCE_CLOSE = 2;
        // 强制打开用户不能关闭
        public static final int FORCE_OPEN = 3;
        // wifi状态
        public static final String WIFI_STATE = "wifi_state";
        // 蓝牙状态
        public static final String BLUETOOTH_STATE = "bluetooth_state";
    
        private static final String SERVICE_NAME = "majiong_service";
        private static MaJiongManager mInstance = null;
        private IMaJiongService mService;
    
        private MaJiongManager() {
    		mService = IMaJiongService.Stub.asInterface(ServiceManager.getService(SERVICE_NAME));
    	}
    
    	public static synchronized MaJiongManager getInstance() {
    		if (mInstance == null) {
    			synchronized (MaJiongManager.class) {
    				if (mInstance == null) {
    					mInstance = new MaJiongManager();
    				}
    			}
    		}
    		return mInstance;
    	}
    
        /**
    	 * 设置对应功能的管控方式
    	 * @param key
    	 * 			WIFI_STATE      	WIFI
    	 * 			BLUETOOTH_STATE 	蓝牙
    	 * @param status
    	 * 			OPEN		打开状态
    	 * 			CLOSE		关闭状态
    	 * 			FORCE_OPEN  强制打开用户不能关闭
    	 * 			FORCE_CLOSE 强制关闭用户不能打开
    	 */
    	public void setControlStatus(String key, int status) {
    		if (mService == null) {
            	Log.d(TAG, "MaJiongManager mService is null.");
                return;
            }
    		try {
    		    mService.setControlStatus(key, status);
    		} catch (RemoteException e) {
    		    Log.e(TAG, "MaJiongManager setControlStatus fail.");
    		}
    	}
    
        /**
    	 * 获取对应功能的管控状态
    	 * @param key
    	 * 			WIFI_STATE      	WIFI
    	 * 			BLUETOOTH_STATE 	蓝牙
    	 * @return
    	 * 			OPEN		打开状态
    	 * 			CLOSE		关闭状态
    	 * 			FORCE_OPEN  强制打开用户不能关闭
    	 * 			FORCE_CLOSE 强制关闭用户不能打开
    	 */
    	public int getControlStatus(String key) {
    		if (mService == null) {
            	Log.d(TAG, "MaJiongManager mService is null.");
                return OPEN;
            }
    		try {
    		    return mService.getControlStatus(key);
    		} catch (RemoteException e) {
    		    Log.e(TAG, "MaJiongManager getControlStatus fail.");
    		    return OPEN;
    		}
    	}
    
    }

    我们先简单的规划两个多功能接口,一个设置的接口setControlStatus,和一个获取的接口getControlStatus,可以通过传入不同的参数设置/获取不同功能的开关状态,这里只有wifi 和蓝牙两个参数选择,开关选项也只有OPEN和CLOSE两个状态,当然这些都是可以扩展的,我们先把最简单的功能跑通,如果要扩展,后面也会讲到。这个类里定义了一些Constant常量类型,这样做的好处是,当把这个类打包成SDK后,开发这可以轻松知道SDK接口支持的参数和范围。

    2.1.2.为我们的Manager生成AIDL

    在frameworks/base/core/java/android/os/目录下新建目录majiong,在majiong目录下创建IMaJiongService.aidl文件

    package android.os.majiong;
    
    /** {@hide} */
    interface IMaJiongService
    {
        void setControlStatus(String key, int status);
        int getControlStatus(String key);
    }
    

    为什么在os目录下新建文件夹以及文件?在2.1.1的目录下创建不行吗?答案当然是可以,还是那句话,为了方便维护和管理,Android系统的原生服务对应的AIDL文件绝大部分都是在android/os/目录下的,这里可以算的上是AIDL文件集中营,我们在这里新建自己的文件夹,存放自己的AIDL文件是科学且合理的。

    2.1.3.编写系统级服务

    在frameworks/base/services/core/java/com/android/server/目录下新建目录majiong,在majiong目录下创建MaJiongService.java,为什么在这里创建,理由和目的和上面两条如出一辙。

    package com.android.server.majiong;
    
    import com.majiong.mdm.sdk.MaJiongManager;
    import com.majiong.mdm.sdk.constant.MaJiongRestriction;
    
    import android.content.Context;
    import android.content.ContentValues;
    import android.content.ContentResolver;
    import android.content.Intent;
    import android.content.IntentFilter;
    import android.database.ContentObserver;
    import android.net.Uri;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.majiong.IMaJiongService;
    import android.provider.Settings;
    import android.util.Log;
    
    import android.net.wifi.WifiManager;
    import android.provider.Settings;
    
    public class MaJiongService extends IMaJiongService.Stub {
        private static final String TAG = "majiong";
        // True if systemReady() has been called.
        private boolean mSystemReady;
    	private Context mContext;
    	private ContentResolver mResolver = null;
    	// 启用(默认)
    	private static final int ENABLE = 0;
    	// 禁用
    	private static final int DISABLE = 1;
    
        public MaJiongService(Context context) {
            mContext = context;
            mResolver = mContext.getContentResolver();
            mResolver.registerContentObserver(
                    Settings.Global.getUriFor(Settings.Global.WIFI_ON), true,
                    mWifiObserver);
        }
    
        private ContentObserver mWifiObserver = new ContentObserver(new Handler()) {
            @Override
            public void onChange(boolean selfChange) {
                // 这里可以监听wifi的状态,当wifi的管控状态为不可关闭/不可开启的时候,可以做相应的处理,阻止用户改变wifi状态
            }
        };
    
        public void setControlStatus(String key, int status) {
            Log.d(TAG, "setControlStatus key: " + key + ", status: " + status);
            setWifiEnable(status == MaJiongManager.OPEN ? true : false);
        }
    
        @Override
        public int getControlStatus(String key) {
    		return getPersistedWifiOn() ? MaJiongManager.OPEN : MaJiongManager.CLOSE;
        }
    
        private boolean setWifiEnable(boolean enable) {
            WifiManager wm = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
            return wm.setWifiEnabled(enable);
        }
    
        private boolean getPersistedWifiOn() {
            return Settings.Global.getInt(mContext.getContentResolver(),
                    Settings.Global.WIFI_ON, 0) == 1;
        }
    
        public void systemRunning() {
            Log.d(TAG, "MaJiongService ready.");
            mSystemReady = true;
        }
    
        // 检查控制类属性 key 是否符合要求
        private boolean checkControlKey(String key) {
            if (null == key) {
                return false;
            }
            if (MaJiongManager.WIFI_STATE.equals(key)
            	|| MaJiongManager.BLUETOOTH_STATE.equals(key)) {
    
            	return true;
            }
            return false;
        }
    
        // 检查控制类属性 value 是否符合要求
        private boolean checkControlValue(int status) {
            if (status < MaJiongManager.CLOSE || status > MaJiongManager.FORCE_OPEN) {
                return false;
            }
            return true;
        }
    
    }

    MaJiongService继承了IMaJiongService.aidl的远程接口,实现其方法setControlStatus和getControlStatus,这也是Android AIDL的标准用法。为了方便测试,我们直接在setControlStatus方法中加上设置wifi开关状态的代码,在getControlStatus方法中添加读取wifi开关状态的代码。

    2.1.4.注册服务

    仅仅将服务写好是不够的,系统中的每个原生服务都需要注册,相当于给系统留一个备案,方便需要使用的时候快速查找,否则即使编译通过,也是无法找到这个服务的。在frameworks/base/core/java/android/app/SystemServiceRegistry.java文件中添加如下注册代码:

    registerService("majiong_service", MaJiongManager.class,
            new StaticServiceFetcher<MaJiongManager>() {
        @Override
        public MaJiongManager createService() {
            return MaJiongManager.getInstance();
        }});

    2.1.5.初始化服务

    上面一步我们已经将服务注册到系统了,这仅仅是有了备案信息而已,并不会让服务启动运行,系统级服务都是随着设备开机自启动的,那么如何初始化服务并启动它运行起来呢?Google为这些系统服务准备了一个特定的地方 -- frameworks/base/services/java/com/android/server/SystemServer.java,所有的系统服务都在这里初始化。

    diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
    index ed5c65ffabe..11f362d4cf8 100644
    --- a/services/java/com/android/server/SystemServer.java
    +++ b/services/java/com/android/server/SystemServer.java
     import com.android.server.job.JobSchedulerService;
     import com.android.server.lights.LightsService;
    +import com.android.server.majiong.MaJiongService;
     import com.android.server.media.MediaResourceMonitorService;
     import com.android.server.media.MediaRouterService;
     import com.android.server.media.MediaUpdateService;
    @@ -766,6 +767,7 @@ public final class SystemServer {
             final Context context = mSystemContext;
             VibratorService vibrator = null;
    +        MaJiongService mjs = null;
     
             IStorageManager storageManager = null;
             NetworkManagementService networkManagement = null;
    @@ -943,6 +945,16 @@ public final class SystemServer {
                 }
                 traceEnd();
     
    +            traceBeginAndSlog("StartMaJiongService");
    +            try {
    +                Slog.i(TAG, "add MaJiong Service");
    +                mjs = new MaJiongService(context);
    +                ServiceManager.addService("majiong_service", mjs);
    +            } catch (Throwable e) {
    +                reportWtf("starting MaJiong Service", e);
    +            }
    +            traceEnd();
    +
                 traceBeginAndSlog("StartDeviceSettingsService");
                 try {
                     Slog.i(TAG, "add DeviceSettings Service");
    @@ -1923,6 +1935,7 @@ public final class SystemServer {
             final MmsServiceBroker mmsServiceF = mmsService;
             final IpSecService ipSecServiceF = ipSecService;
             final WindowManagerService windowManagerF = wm;
    +        final MaJiongService mjsF = mjs;
     
             // We now tell the activity manager it is okay to run third party
             // code.  It will call back into us once it has gotten to the state
    @@ -2145,6 +2158,14 @@ public final class SystemServer {
                     reportWtf("Notifying DeviceRestrictionService running", e);
                 }
                 traceEnd();
    +            
    +            traceBeginAndSlog("MaJiongServiceReady");
    +            try {
    +                if (mjsF != null) mjsF.systemRunning();
    +            } catch (Throwable e) {
    +                reportWtf("Notifying MaJiongService running", e);
    +            }
    +            traceEnd();
     
                 traceBeginAndSlog("DeviceSettingsServiceReady");
    

    这个文件添加的内容比较分散,就直接上patch了,不过也是清晰易懂的,就是系统在开机的过程中,会按顺序挨个把这些服务都初始化好,并通知相关组件已经开机,可以工作了。

    2.1.6.添加编译规则

    我们所添加的所有java文件都不用操心编译规则,这些文件所在的路径都已经默认包含到编译规则里了,所以都会编译到,唯一要注意的是AIDL文件,它是一个比较特殊的文件,Android自己搞出来的玩意,我们要做的只是配置这个文件的编译规则,否则系统不编译AIDL,那么服务也就编译不过了。

    很简单,只需要在frameworks/base/Android.bp(Android O以下的代码为Android.mk文件) 中添加如下patch中的增量代码即可:

    diff --git a/Android.bp b/Android.bp
    index 6097d4052b4..5c7de32dee6 100755
    --- a/Android.bp
    +++ b/Android.bp
    @@ -225,6 +225,7 @@ java_library {
             "core/java/android/se/omapi/ISecureElementSession.aidl",
    +        "core/java/android/os/majiong/IMaJiongService.aidl",
             "core/java/android/os/IBatteryPropertiesListener.aidl",
             "core/java/android/os/IBatteryPropertiesRegistrar.aidl",

    2.1.7.为新服务添加SELinux权限

    一个新的服务已经添加好了,如果这是在Android5.0之前的系统上,这就可以用起来了,但是后来Google对系统权限管理的越来越严格,引入了SELinux权限,所以到目前为止,以上代码编译是没问题的,运行也没问题,但是要让其它的进程调用它就有问题了,因为这个服务没有权限让其它进程调用。先看看我在system/sepolicy目录下修改的文件

    这一大票都是神马玩意??不仅你看了烦,我看了也烦。简单来说就是将我们添加的服务公开化,让其它的进程能够引用它。否则这个服务初始化都不会成功,更别提让其它进程调用了。由于修改的文件众多,没办法全部贴出来,已经打好patch,有需要的猛戳这里下载

    Step2.打包SDK,供第三方程序调用

    2.2.1.打包SDK

    系统服务已经添加好了,为了验证服务的可用性,我们要让第三方应用调用我们的服务,最好的方式是提供一个开发用的SDK,开发人员只需要在Android工程里引用这个SDK就可以了,可以说肥肠方便了。

    打包SDK的方法也有多种:

    • Eclipse生成SDK
    • AS生成SDK
    • Android源码中编写Android.mk文件编译出jar包

    以上的方式都是可行的,我就以Eclipse为例说一下打包过程

    新建一个Android工程,名字无所谓,随便取,然后新建工程包名,这个包名一定要和我们系统源码中的MaJiongManager.java的包名一致,然后将系统中的MaJiongManager.java文件拷贝到这个包下,如下图所示

    然后修改MaJiongManager.java的内容,改成如下:

    package com.majiong.mdm.sdk;
    
    /*
     * 
     * @author   liaohongfei
     * 
     * @Date  2019 2019-5-29  上午10:08:23
     * 
     */
    public class MaJiongManager {
        // 关闭
        public static final int CLOSE = 0;
        // 打开
        public static final int OPEN = 1;
        // 强制关闭用户不能打开
        public static final int FORCE_CLOSE = 2;
        // 强制打开用户不能关闭
        public static final int FORCE_OPEN = 3;
        // wifi状态
        public static final String WIFI_STATE = "wifi_state";
        // 蓝牙状态
        public static final String BLUETOOTH_STATE = "bluetooth_state";
    
    	public static synchronized MaJiongManager getInstance() {
    		throw new RuntimeException("API not supported!");
    	}
    
        /**
    	 * 设置对应功能的管控方式
    	 * @param key
    	 * 			WIFI_STATE      	WIFI
    	 * 			BLUETOOTH_STATE 	蓝牙
    	 * @param status
    	 * 			OPEN		打开状态
    	 * 			CLOSE		关闭状态
    	 * 			FORCE_OPEN  强制打开用户不能关闭
    	 * 			FORCE_CLOSE 强制关闭用户不能打开
    	 */
    	public void setControlStatus(String key, int status) {
    		throw new RuntimeException("API not supported!");
    	}
    
        /**
    	 * 获取对应功能的管控状态
    	 * @param key
    	 * 			WIFI_STATE      	WIFI
    	 * 			BLUETOOTH_STATE 	蓝牙
    	 * @return
    	 * 			OPEN		打开状态
    	 * 			CLOSE		关闭状态
    	 * 			FORCE_OPEN  强制打开用户不能关闭
    	 * 			FORCE_CLOSE 强制关闭用户不能打开
    	 */
    	public int getControlStatus(String key) {
    		throw new RuntimeException("API not supported!");
    	}
    
    }
    

    这样就看明白了吧?这个代码其实就是系统源码中MaJiongManager.java的副本,只保留常量类型供开发人员引用,方法全部直接抛出异常,因为它只是个副本,真正调用的是系统源码中的正文。这样做的好处是,这个SDK对于其它平台其它设备都是没有丝毫价值的,只在我们自己的系统和平台上才有价值,因为这是我们的专属服务,别人没法用,也看不到我们的源码是怎么写的。

    然后就是制作SDK开发包咯,IDE都有现成的工具,一键生成

    右键项目名称,选择Export,再弹出来的窗口再选择JAR file,然后按下图的方式选择

    点击finish,就可在桌面上生成majiong-mdm-sdk.jar,然后这个JAR开发包就可以在开发中使用了。

    2.2.2.使用SDK

    让我们试验一下,看看SDK的使用效果如何,顺便系统添加的服务也需要验证是否没有问题,我们再编写一个测试APK,在工程中引用majiong-mdm-sdk.jar,并且调用它的相关接口

    这个测试程序布局文件很简单,界面上就一个Button,给它加上点击事件,调用我们的系统服务,主要代码如下:

    package com.example.majiongtest;
    
    import com.majiong.mdm.sdk.MaJiongManager;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.Button;
    
    public class MainActivity extends Activity {
    	private static final String TAG = "majiong";
    	private Button btn;
    	
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    		
    		btn = (Button) findViewById(R.id.btn_set);
    		btn.setOnClickListener(new OnClickListener() {
    			
    			@Override
    			public void onClick(View view) {
    				int wifiState = MaJiongManager.getInstance().getControlStatus(MaJiongManager.WIFI_STATE);
    				Log.d(TAG, "wifiState = " + wifiState);
    				if (wifiState == MaJiongManager.CLOSE) {
    					MaJiongManager.getInstance().setControlStatus(MaJiongManager.WIFI_STATE, MaJiongManager.OPEN);
    				} else {
    					MaJiongManager.getInstance().setControlStatus(MaJiongManager.WIFI_STATE, MaJiongManager.CLOSE);
    				}
    			}
    		});
    	}
    
    }

    界面就长这样

    代码非常简单,就是点击那个丑丑的图片按钮的时候先获取WiFi的状态,如果wifi关闭了就打开,反之则关闭。

    再看看我们的调用系统服务的方式,根本不需要Context上下文,一行代码一个功能,是不是肥肠不错?

    ”bia唧“一下点击按钮,通过log会发现代码最终调用到MaJiongService的getControlStatus和setControlStatus,说明我们的服务没问题,服务相关的SELinux权限也没问题,整个流程算是跑通了。但是很遗憾,在设置wifi状态的时候抛出了异常

    神马?权限错误?我们已经是系统级服务了,还不能开关一个WiFi??对的,还就是不让你好好玩了,这个问题就又牵扯到Android的权限分类管理了

    /**
                 * Android M 及之后的版本将系统权限分为四类:
                 * 1. 普通权限:对用户数据安全不敏感的权限,比如获取网络状态、WIFI状态等权限,只需在AndroidManifest配置即可,在运行中不需要再通过用户去手动赋予权限。
                 * 2. 危险权限:对用户数据安全很敏感的权限,比如获取通讯录、获取地理位置等权限,需要在AndroidManifest文件中配置相关权限,并且在运行中必须由用户动态的决定,是
    否赋予该程序打开这项权限,否则程序将异常退出。
                 * 3. 特殊权限:特别敏感的权限,主要有两个:SYSTEM_ALERT_WINDOW -- 设置系统级别的悬浮窗(比如系统开关机时的提示窗口,始终保持在最上层视图,用户无法关闭);WRITE_SETTINGS -- 修改系统设置。
                 * 4. 其它权限: 一些很少用到的权限
                 */

    总之就是这属于敏感权限,System Service也无权操作,那什么样的进程才有权操作呢?我们看一下系统的Settings,这玩意就能操作,因为Settings的android:sharedUserId="android.uid.system",并且它被置于/system/app/ or /system/priv-app/ 目录下,一个应用进程要同时满足这两个条件,那么系统中的大多数敏感权限都可以规避。当然还有少量特殊操作也是不能做的,这里不做分析。

    这样一来下一步该怎么做就很明显了,再编写一个android:sharedUserId="android.uid.system"、编译到system/app/ 的超级管理APP,当然这种程序都是无界面在后台运行的,用户不能感知它的存在,也是我们的系统专属管控应用,它所具有的权限非常的高。

    Step3.再添加一个专属系统级总控APP

    2.3.1.进程与进程间的交互

    我们已经有了一个MaJiongService,这是一个独立进程;现在要添加一个MaJiongMdmControl APP,这也是一个独立进程,这两个进程要进行交互,就不可避免的又要用到跨进程通信。MaJiongService的调用接口我们已经封装的很完美了,只需要调用MaJiongManager.getInstance().xxx就可以调用它的方法,实现跨进程通信,这可以说是肥肠便捷了。那么MaJiongService如何将消息传递给MaJiongMdmControl呢?其实Android系统中有一些没有公开的API,用起来也很方便,我们可以通过隐藏API很方便的实现这一点。

    ContentProvider我想大家肯定都用过,这是Android 四大组件之一,专门用来跨进程通信的。ContentProvider有一个call方法,可以很方便的实现跨进程通信,不需要专门暴露接口等复杂的操作,还要配合一个系统隐藏接口,ContentResolver 的acquireProvider方法。

    实现了以上两点MaJiongService和MaJiongMdmControl 之间就可以毫无障碍的通信了,我们通过这样的设计可以实现很多很多的功能,现在因为只是演示简单的代码功能,就用最简单的方式来实现。我们先来实现MaJiongMdmControl的代码编写。

    2.3.2.编写总控APP代码

    我们的总控APP MaJiongMdmControl需要放在源码中编译进系统system/app/下,所以我们需要编写一个Android.mk

    LOCAL_PATH:= $(call my-dir)
    
    include $(CLEAR_VARS)
    LOCAL_MODULE_TAGS := optional
    LOCAL_SRC_FILES := $(call all-java-files-under, src)
    LOCAL_MODULE_PATH := $(TARGET_OUT_SYSTEM_APPS)
    
    LOCAL_PACKAGE_NAME := MaJiongMdmControl
    LOCAL_CERTIFICATE := platform
    
    LOCAL_DEX_PREOPT := false
    LOCAL_PROGUARD_ENABLED := disabled
    #LOCAL_PROGUARD_ENABLED := full obfuscation
    #LOCAL_PROGUARD_FLAG_FILES := proguard.flags
    
    LOCAL_PRIVATE_PLATFORM_APIS := true
    
    include $(BUILD_PACKAGE)
    include $(call all-makefiles-under, $(LOCAL_PATH))

    然后再来编写APK的代码,APK代码可以借助IDE快速编写完成,还能排除掉不必要的错误,编写完后删除不必要的文件再放到系统中编辑即可

    AndroidManifest.xml

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.majiong.mdm.control"
        android:sharedUserId="android.uid.system" >
    
        <application
            android:name="com.majiong.mdm.control.MaJiongMdmApplication"
            android:allowBackup="true"
            android:icon="@drawable/ic_launcher"
            android:label="@string/app_name" >
    
            <!-- Device Restriction -->
            <provider android:name="com.majiong.mdm.control.provider.MaJiongProvider"
    		    android:authorities="com.mdm.MaJiongProvider"
    		    android:multiprocess="false"
    		    android:exported="true" >
    		</provider>
        </application>
    </manifest>

    程序入口类MaJiongMdmApplication.java

    package com.majiong.mdm.control;
    
    import android.app.Application;
    import android.content.Context;
    import android.util.Log;
    
    /*
     * 
     * @author   liaohongfei
     * 
     * @Date  2019 2019-6-28  下午7:21:17
     * 应用程序入口类
     */
    public class MaJiongMdmApplication extends Application {
        private static final String TAG = "majiong";
    	// 全局Context
    	private static Context mContext;
    
    	@Override
    	public void onCreate() {
    		super.onCreate();
    		Log.d(TAG, "MaJiongMdmApplication onCreate.");
    		mContext = getApplicationContext();
    	}
    
    	public static Context getContext() {
    		return mContext;
    	}
    
    }

    进程间通信的ContentProvider

    package com.majiong.mdm.control.provider;
    
    import com.majiong.mdm.sdk.constant.MaJiongRestriction;
    import com.majiong.mdm.sdk.MaJiongManager;
    
    import android.bluetooth.BluetoothAdapter;
    import android.content.Context;
    import android.content.ContentProvider;
    import android.content.ContentResolver;
    import android.content.ContentValues;
    import android.content.Intent;
    import android.database.Cursor;
    import android.net.Uri;
    import android.net.wifi.WifiManager;
    import android.os.Bundle;
    import android.provider.Settings;
    import android.util.Log;
    
    /*
     * 
     * @author   liaohongfei
     * 
     * @Date  2018 2018-10-12  下午4:59:54
     * 
     */
    public class MaJiongProvider extends ContentProvider {
    	private static final String TAG = "majiong";
    	private ContentResolver mResolver;
    	private Context mContext;
    
    	@Override
    	public boolean onCreate() {
    		mContext = getContext();
    		mResolver = mContext.getContentResolver();
    		return true;
    	}
    
    	@Override
    	public int delete(Uri uri, String selection, String[] selectionArgs) {
    		return 0;
    	}
    
    	@Override
    	public String getType(Uri uri) {
    		return null;
    	}
    
    	@Override
    	public Uri insert(Uri uri, ContentValues values) {
    		return null;
    	}
    
    	@Override
    	public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
    			String sortOrder) {
    		return null;
    	}
    
    	@Override
    	public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
    		return 0;
    	}
    
    	@Override
    	public Bundle call(String method, String arg, Bundle extras) {
    	    switch (method) {
    	    case MaJiongRestriction.SET_CONTROL_STATUS:
    	        if (extras != null) {
    	            String key = extras.getString(MaJiongRestriction.CONTROL_STATUS_KEY);
    	            int status = extras.getInt(MaJiongRestriction.CONTROL_STATUS_VALUE);
    	            setControlStatus(key, status);
    	        }
    	        break;
    	    case MaJiongRestriction.GET_CONTROL_STATUS:
    	        if (extras != null) {
    	            String key = extras.getString(MaJiongRestriction.CONTROL_STATUS_KEY);
    	            extras = new Bundle();
    	            extras.putInt(MaJiongRestriction.CONTROL_STATUS_VALUE, getControlStatus(key));
    	            return extras;
    	        }
    	        break;
    	    }
    		return null;
    	}
    
        private void setControlStatus(String key, int status) {
            Log.d(TAG, "马囧身高一米九,脸长一米二");
    
            boolean enable = false;
            switch (key) {
            case MaJiongManager.WIFI_STATE:
                if (status == MaJiongManager.OPEN || status == MaJiongManager.FORCE_OPEN) {
                    enable = true;
                }
                setWifiEnable(enable);
                break;
            case MaJiongManager.BLUETOOTH_STATE:
                if (status == MaJiongManager.OPEN || status == MaJiongManager.FORCE_OPEN) {
                    enable = true;
                }
                setBluetoothEnable(enable);
                break;
            }
        }
    
        private int getControlStatus(String key) {
            Log.d(TAG, "马囧身高一米九,脸长一米二");
    
            int state = MaJiongManager.CLOSE;
            switch (key) {
            case MaJiongManager.WIFI_STATE:
                state = getPersistedWifiOn() ? MaJiongManager.OPEN : MaJiongManager.CLOSE;
                break;
            case MaJiongManager.BLUETOOTH_STATE:
                state = getPersistedBluetoothOn() ? MaJiongManager.OPEN : MaJiongManager.CLOSE;
                break;
            }
            return state;
        }
    
        private void setBluetoothEnable(boolean enable) {
            BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
            boolean blueToothState = bluetoothAdapter.isEnabled();
            if (enable) {
                if (!blueToothState)
                    bluetoothAdapter.enable();
            } else {
                if (blueToothState)
                    bluetoothAdapter.disable();
            }
        }
    
        private boolean getPersistedBluetoothOn() {
            return Settings.Global.getInt(mContext.getContentResolver(),
                    Settings.Global.BLUETOOTH_ON, 0) == 1;
        }
    
        private boolean setWifiEnable(boolean enable) {
            WifiManager wm = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
            return wm.setWifiEnabled(enable);
        }
    
        private boolean getPersistedWifiOn() {
            return Settings.Global.getInt(mContext.getContentResolver(),
                    Settings.Global.WIFI_ON, 0) == 1;
        }
    
    }

    完整的工程猛戳这里下载!!

    还记得MaJiongService里面的方法吗?就是第三方APK引用SDK后调用的setControlStatus和getControlStatus方法,之前我们已经尝试过了在MaJiongService中直接操作WiFi权限上是通不过的,所以我们要把操作WiFi发代码移到总控APP的MaJiongProvider中来,如上代码所示。相应的,MaJiongService里应该调用MaJiongProvider里的方法,我们把MaJiongService里的方法稍微改一下:

    @Override
    public void setControlStatus(String key, int status) {
            Log.d(TAG, "setControlStatus key: " + key + ", status: " + status);
            if (mResolver.acquireProvider(MaJiongRestriction.MAJIONG_MDM_URI) != null) {
                Bundle extras = new Bundle();
                extras.putString(MaJiongRestriction.CONTROL_STATUS_KEY, key);
                extras.putInt(MaJiongRestriction.CONTROL_STATUS_VALUE, status);
                mResolver.call(MaJiongRestriction.MAJIONG_MDM_URI, MaJiongRestriction.SET_CONTROL_STATUS, null, extras);
            } else {
                Log.d(TAG, "Could Not Find Provider " + MaJiongRestriction.MAJIONG_MDM_URI);
            }
        }
    
        @Override
        public int getControlStatus(String key) {
            if (mResolver.acquireProvider(MaJiongRestriction.MAJIONG_MDM_URI) != null) {
                Bundle extras = new Bundle();
                extras.putString(MaJiongRestriction.CONTROL_STATUS_KEY, key);
                extras = mResolver.call(MaJiongRestriction.MAJIONG_MDM_URI, MaJiongRestriction.GET_CONTROL_STATUS, null, extras);
                if (extras != null)
                    return extras.getInt(MaJiongRestriction.CONTROL_STATUS_VALUE, DISABLE);
            } else {
                Log.d(TAG, "Could Not Find Provider " + MaJiongRestriction.MAJIONG_MDM_URI);
            }
    		return DISABLE;
        }

    可能大家又有疑问了,在MaJiongProvider和MaJiongService 的代码中都看到了MaJiongRestriction这个类的引用,MaJiongRestriction是个什么瘪犊子玩意儿?请看代码:

    package com.majiong.mdm.sdk.constant;
    
    import android.net.Uri;
    
    /*
     * 
     * @author   liaohongfei
     * 
     * @Date  2018 2018-10-12  下午4:59:54
     * Device Restriction Constant
     */
    public class MaJiongRestriction {
    	public static final String AUTHORITY = "com.mdm.MaJiongProvider";
    	public static final String CONTENT_TYPE =
                "vnd.android.cursor.dir/vnd.com.mdm.MaJiongProvider";
    	public static final String CONTENT_ITEM_TYPE =
                "vnd.android.cursor.item/vnd.com.mdm.MaJiongProvider";
    
        public static final class Table {
    		public static final String TABLE_NAME = "test_table";
    
    		public static final String ID = "_id";
    		public static final String KEY = "key";
    		public static final String VALUE = "value";
    
    		public static final int COLUMN_IDX_ID = 0;
    		public static final int COLUMN_IDX_KEY = 1;
    		public static final int COLUMN_IDX_VALUE = 2;
    
    		public static final String DEFAULT_SORT_ORDER = "_id asc";
    
    		public static final int ITEM = 1;
    		public static final int ITEM_NAME = 2;
    
    		public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/item");
    	}
    
        public static final Uri MAJIONG_MDM_URI = Uri.parse("content://com.mdm.MaJiongProvider");
        // Control
        public static final String SET_CONTROL_STATUS = "set_control_status";
        public static final String GET_CONTROL_STATUS = "get_control_status";
        public static final String CONTROL_STATUS_KEY = "control_key";
        public static final String CONTROL_STATUS_VALUE = "control_value";
    
    }

    这个类是在frameworks/base/core/java/com/majiong/mdm/sdk/constant目录下创建的,和MaJiongManager也算是同级目录了,可以看到这个类中定义的都是一些常量类型的数据,为的是方便系统其它进程引用,不至于每个地方还要单独定义变量引起变量。那么为什么不直接定义在MaJiongManager 里面呢?MaJiongManager里面定义的常量类是为了公开给第三方开发这调用的,而这些常量是需要对外隐藏的,只有系统知道,只有系统能引用。

    可以看到里面还定义了一张表的内部类,这个暂时不讲,后面的扩展里面会说到。目前我们只需要用到MAJIONG_MDM_URI 等几个常量数据。

    到这里为止,系统服务添加了,总控APP MaJiongMdmControl写好了,切编译到/system/app/下了,双方的交互规则也定义好了,SDK开发包也提供了,测试程序也写好了,一切准备就绪,来测试一波。

    再次打开MaJiongTest APP, ”bia唧“一下点击按钮,会发现WiFi打开了,再点击一下WiFi又关闭了。查看打印日志,可以看到输出了"马囧身高一米九,脸长一米二"的日志,只要”bia唧“一下按钮,不仅可以实现WiFi的开关操作,还可以知道马囧不仅高脸还长的事实,惊不惊喜?刺不刺激?这么体面的设计,谁用谁说好。

    我们回顾一下调用流程,当”bia唧“一下按钮,会调用SDK的接口,这里实际上通过Binder进程间通信(AIDL)调用到了我们的专属服务MaJiongService,然后MaJiongService 再通过Binder进程间通信(ContentProvider)调用到了我们的总控APP MaJiongMdmControl,因为MaJiongMdmControl的android:sharedUserId="android.uid.system"并且存在system/app/目录下,所以它具有和系统设置一样高的权限,完全有能力操控WiFi的状态。


    三、扩展扩展再扩展

    上面实际上已经把整个流程打通,并实现了想要的功能。那就会有童鞋要说了:你就实现了个开关WiFi的功能,至于搞这么一大堆复杂的东西吗?我用个反射一样的实现了。

    我只能说这个仅仅是一个很简单的功能演示代码,没有添加其它的复杂功能,一个Android操作系统,涉及到需要管理的功能项有多少?一百?几百个都不止,这些功能如果都要做难道全部用反射?这不科学也不现实,况且很多功能你是射不了的(哈哈,原谅我)。

    为什么要开发这些限制设备功能的API?因为有需求,有市场,有钱挣,所以会有人做。 据我所知,小米、华为等大型手机厂商,都做了这种SDK,专业一点的名词叫MDM(Mobile Device Manager)。用智能手机的不能仅仅有像你我这样的普通用户,各行各业都会越来越离不开智能设备,那么很多行业的只能设备都是需要管控的,或为了保证业务机密性或为了防止员工开小差,各行各业有各行各业的需求,普通大众的智能手机满足不了行业需求,这样行业定制机就应运而生,厂商会提供一系列管控SDK,用来控制设备的各项功能。目前来看MDM接口做的最全面应该是华为,功能几乎涵盖了整个操作系统的方方面面。

    不管是华为还是小米,实现这种MDM功能管控的对外SDK,流程大致上都和我上述的代码流程一致,因为目前为止对Android操作系统来说这可能是最优方案了。

    说了这么多,还没说到底怎么扩展。还记得MaJiongRestriction中的Table内部类吧?这个其实是我们预留的扩展的一部分。比如说WiFi,上面的代码我们只实现了开关功能,很多行业都需要四种状态的管理:

    OPEN、CLOSE、FORCE_OPEN、FORCE_CLOSE。啥意思?就是不仅可以控制wifi的开关,有些场景下还要强制打开Wifi不能让用户关闭,或者取反。那么我们的Table就有用了,这就是个方便跨进程操作DB的引子,我们可以通过这些定义很方便的将管控的值存进数据库,然后再到framework中开关wifi的代码里添加我们的判断流程:

    假如管理人员对设备下发了FORCE_OPEN的指令,那我们的数据库中对应的wifi state 状态就应该存储FORCE_OPEN;设备在用户(员工)手中,他可以自由使用手机,这个时候的工作场景因为需要是不能关闭WiFi的,但用户进入设置里主动去关闭wifi,关闭WiFi的动作必然会framework中的相关流程,我们可以在这里做拦截处理,通过我们添加的service和总控app,可以轻松的读取到我们保存的wifi state,此时的状态是FORCE_OPEN,就直接return 用户关闭WiFi的非法操作了。

    Android操作系统的功能众多,每个行业的需求也不一致,如果要兼顾到每一个功能和需求,那么我们添加的MDM代码会深入到系统源码的每一个角落。当然万变不离其宗,按照我们定义的这套框架、流程去做,绝大部分的问题都能迎刃而解。

     

    【怒草 https://blog.csdn.net/visionliao?spm=1011.2124.3001.5113 未经允许严禁转载,请尊重作者劳动成果。】

     

     

    展开全文
  • 封装 只对外提供接口,隐藏细节 继承 让系统或者功能一定的延续性 多态 编译时的多态和运行时的多态运行时的多态表现为:A系统访问B系统提供的服务时,B系统多种提供服务的方式,但一切对A系统来说都是透明的。...
  • 1 封装封装,即隐藏对象的属性和实现细节,仅对外公开接口。2 为什么要封装封装数据:可以保护隐私...3 封装有哪些表现3.1 python自带的封装创建一个类或对象,就会创建二者的命名空间,只需要用类名.或对象.的方式...
  • 微服务那些事

    2019-05-15 09:28:45
    以及一些其他接口描述信息最常见的服务发布和引用的方式有三种:RESTful API (一般对外)XML配置 (对内)IDL文件(跨语言,Thrift, gRPC)如何注册和发现服务在微服务架构下,主要有三种角色:服务提供者(RPC S...
  • 1、Dubbo 整体架构设计有哪些分层? 接口服务层(Service):该层与业务逻辑相关,根据 provider 和 consumer业务设计对应的接口和实现 ; 配置层(Config):对外配置接口,以 ServiceConfig 和 ReferenceConfig...
  • 1 项目对外提供接口有哪些? 回答:http(二进制流加密解密) 还有其他的方式吗? 不知道怎么实现的。待解 2 类的加载机制 回答: .class文件加载到JVM,并形成Class对象的机制 过程: 装载 链接 初始化 ...
  • 淘宝电话面试

    2012-03-03 17:48:51
    1 项目对外提供接口有哪些? 回答:http(二进制流加密解密) 还有其他的方式吗? 不知道怎么实现的。待解 2 类的加载机制 回答: .class文件加载到JVM,并形成Class对象的机制 过程: 装载 链接 初始化 3 ...
  • IOC及Bean容器

    2018-03-11 23:46:04
    一、接口及面向接口编程 1、接口 (1) 用于沟通中介物抽象化。... (3) 对应Java接口即声明,声明了哪些方法是对外公开提供的。 注:接口:只能声明,不能实现;抽象类:既可以声明,也可...
  • 1、 Java 面向对象特征有哪些? 封装:隐藏对象属性和实现细节,对外提供访问方法,提高了数据安全性。 抽象:从众多事物中抽取出共同、本质性特征 继承:子类可以拥有父类属性和方法,提高代码...
  • 我们就来看看常见路由器故障有哪些吧。  故障现象: A  无法登录至宽带路由器设置页面。  原因以及解决方法:  首先确认路由器与电脑已经正确连接。检查网卡端口和路由器LAN端口对应指示灯是否正常...
  • 对外提供的接口,一定要保证逻辑健壮性:尽量避免空指针等技术类异常;对于业务类异常要做好错误码或者异常信息封装。 单选 8.关于类序列化,下列说法哪些是正确:D A .类序列化与serialVersionUID毫无...
  • 提供了建筑对外的操作接口。由于最初是用是ILRuntime方式的热更,所以初版就是用了C#开发,后期项目转lua,建筑功能没有跟着转,主要是当时建筑还没有成形且体型比较大。 2、一个建筑单位类:用来存储当前建筑...
  • ❓ JAP 常见问题有哪些? ❔ JAP 不支持具体业务操作吗? JAP 针对用户、应用等业务数据,只提供标准业务接口,不提供数据库层面支持。JAP 要做是为广大开发者提供一套技术标准,既然是标准,那就不能依赖...

空空如也

空空如也

1 2 3
收藏数 55
精华内容 22
关键字:

对外提供接口的方式有哪些