Android面试题(六)2网络与安全机制(2)

加密算法介绍:

URL编码 URL编码其实并非加解密算法,只是对特殊字符进行字符转义,从而方便在URL中传输参数。URL编码有两种方式,一种是狭义的URL编码,另一种是广义的URL编码。 狭义的URL编码指的是只对汉字进行编码,相关代码参见《Android开发笔记(六十三)HTTP访问的通信方式》。 广义的URL编码指的是除了汉字之外,还对其他特殊字符进行编码,如空格转换为“%20”,其他的“?”、“&”“/”也分别转换为“%3F”、“%26”、“%2F”。广义的URL编码可直接使用URLEncoder的encode方法,URL解码使用URLDecoder的decode方法,代码示例如下:

//URL编码

public static String encodeURL(String str) {

String encode_str = str;

try {

encode_str = URLEncoder.encode(str, "utf-8");

}

catch (Exception e) {

e.printStackTrace();

}

return encode_str;

} //URL解码

public static String decodeURL(String str) {

String decode_str = str;

try {

decode_str = URLDecoder.decode(str, "utf-8");

} catch (Exception e) {

e.printStackTrace();

} r

eturn decode_str;

}

BASE64编码 BASE64是一种针对字节流的编码工具,用于把不可见的字节流转换为可见的字符串。如果把待加密的数据先转为字节流,然后再把字节流通过BASE64编码成字符串,就好像是完成了加密操作。同时,这个字符串也可以通过BASE64解码为原始数据,因此,我们也可以把BASE64编码看作是一种简单的可逆加密算法。 BASE64有两种编码方式,一种是SUN的,另一种是Apache的。 SUN的BASE64编码,编码算法在sun.misc.BASE64Encoder的encode函数,解码算法在sun.misc.BASE64Decoder的decodeBuffer函数。但是SUN的这个包不在Java的核心库内,所以Android上会报方法找不到的错误。要想在Android上也能使用SUN的BASE64,有两种方式,一种是导入rt.jar包,另一种是在工程中直接加入SUN的源码。 Apache的BASE64编码,编码算法在Base64的encodeBase64String函数,解码算法在Base64的decodeBase64函数。Apache方式对应的jar包名称是commons-codec-***.jar,可是Android内部有相同包名的方法,编译的时候不会报错,运行时便会报方法找不到。要想正常运行,得下载源码后修改包名,避免与系统自带的包冲突。

加密算法

MD5加密 MD5是不可逆的加密算法,也就是无法解密。MD5的加密实现在commons-codec-***.jar中,但是该包的MD5加密函数md5Hex在java环境可以正常运行,但在Android上运行会报错:java.lang.NoSuchMethodError: org.apache.commons.codec.binary.Hex.encodeHexString。这个报错与上面Apache的BASE64编码的问题是一样的,解决该问题有三个办法: 1、使用MessageDigest方式进行MD5加密; 2、下载org.apache.commons.codec的源码,改个包名,在Android环境重新编译打成jar; 3、使用下面代码实现曲线加密:

String md5Str = new String(Hex.encodeHex(DigestUtils.md5(raw))); 具体的MD5加密示例代码如下:

import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.digest.DigestUtils; public class MD5Util { /* * 1.一个运用基本类的实例 MessageDigest 对象开始被初始化。该对象通过使用 update 方法处理数据。 任何时候都可以调用 * reset 方法重置摘要。 一旦所有需要更新的数据都已经被更新了,应该调用 digest 方法之一完成哈希计算。 * 对于给定数量的更新数据,digest 方法只能被调用一次。 在调用 digest 之后,MessageDigest 对象被重新设置成其初始状态。 */ public static String encrypByMd5(String raw) { String md5Str = raw; try { MessageDigest md = MessageDigest.getInstance("MD5"); md.update(raw.getBytes()); byte[] encryContext = md.digest(); int i; StringBuffer buf = new StringBuffer(""); for (int offset = 0; offset < encryContext.length; offset++) { i = encryContext[offset]; if (i < 0) { i += 256; } if (i < 16) { buf.append("0"); } buf.append(Integer.toHexString(i)); } md5Str = buf.toString(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return md5Str; } /* * 2.使用开发的jar直接应用 使用外部的jar包中的类:import * org.apache.commons.codec.digest.DigestUtils; 对上面内容的一个封装使用方便 */ public static String encrypByMd5Jar(String raw) { //String md5Str = DigestUtils.md5Hex(raw); String md5Str = new String(Hex.encodeHex(DigestUtils.md5(raw))); return md5Str; } }

RSA加密

RSA使用公钥加密,在另一端使用私钥加密,这样即使加密的公钥被泄露,对方没有私钥仍然无法正确解密。 下面是RSA加密的几个注意事项: 1、需要导入bcprov-jdk16-1.46.jar; 2、RSA加密的结果是byte字节流,得经过BASE64编码,形成文本字符串后方可正常传输; 3、有时候要对加密前的字符串做reverse倒序处理;

AES加密

AES是设计用来替换DES的高级加密算法。下面是AES算法的代码示例:

import java.security.SecureRandom; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; public class AesCipher { private static final String Algorithm = "AES"; private final static String HEX = "0123456789ABCDEF"; private static void appendHex(StringBuffer sb, byte b) { sb.append(HEX.charAt((b>>4)&0x0f)).append(HEX.charAt(b&0x0f)); } public static String encrypt(String key, String src) throws Exception { byte[] rawKey = getRawKey(key.getBytes()); byte[] result = encrypt(rawKey, src.getBytes()); return toHex(result); } public static String decrypt(String key, String encrypted) throws Exception { byte[] rawKey = getRawKey(key.getBytes()); byte[] enc = toByte(encrypted); byte[] result = decrypt(rawKey, enc); return new String(result); } private static byte[] getRawKey(byte[] seed) throws Exception { KeyGenerator kgen = KeyGenerator.getInstance(Algorithm); // SHA1PRNG 强随机种子算法, 要区别4.2以上版本的调用方法 SecureRandom sr = null; if (android.os.Build.VERSION.SDK_INT >= 17) { sr = SecureRandom.getInstance("SHA1PRNG", "Crypto"); }else { sr = SecureRandom.getInstance("SHA1PRNG"); } sr.setSeed(seed); kgen.init(256, sr);//256 bits or 128 bits,192bits SecretKey skey = kgen.generateKey(); byte[] raw = skey.getEncoded(); return raw; } private static byte[] encrypt(byte[] key, byte[] src) throws Exception { SecretKeySpec skeySpec = new SecretKeySpec(key, Algorithm); Cipher cipher = Cipher.getInstance(Algorithm); cipher.init(Cipher.ENCRYPT_MODE, skeySpec); byte[] encrypted = cipher.doFinal(src); return encrypted; } private static byte[] decrypt(byte[] key, byte[] encrypted) throws Exception { SecretKeySpec skeySpec = new SecretKeySpec(key, Algorithm); Cipher cipher = Cipher.getInstance(Algorithm); cipher.init(Cipher.DECRYPT_MODE, skeySpec); byte[] decrypted = cipher.doFinal(encrypted); return decrypted; } public static String toHex(String txt) { return toHex(txt.getBytes()); } public static String fromHex(String hex) { return new String(toByte(hex)); } public static byte[] toByte(String hexString) { int len = hexString.length()/2; byte[] result = new byte[len]; for (int i = 0; i < len; i++) { result[i] = Integer.valueOf(hexString.substring(2*i, 2*i+2), 16).byteValue(); } return result; } public static String toHex(byte[] buf) { if (buf == null) { return ""; } StringBuffer result = new StringBuffer(2*buf.length); for (int i = 0; i < buf.length; i++) { appendHex(result, buf[i]); } return result.toString(); } }

3DES加密

3DES(或称为Triple DES)是三重数据加密算法,它相当于对每个数据块应用三次DES加密算法。因为原先DES算法的密钥长度过短,使得容易遭到破解,所以3DES通过增加密钥的长度,从而防范被暴力破解,因此3DES不是设计全新的密码算法。实际开发中,3DES的密钥必须是24位的字节数组,过短或过长在运行时都会报错“java.security.InvalidKeyException”。另外,3DES加密生成的是字节数组,也得通过BASE64编码为文本字符串。 具体的3DES加密过程,除了密钥不同之外,还存在两种加密方式: 1、使用加密算法“DESede”,此时初始化Cipher对象只需传入密钥; 2、使用加密算法“desede/CBC/PKCS5Padding”,此时初始化Cipher对象除了传入密钥,还需传入一个字节数组的参数对象即IvParameterSpec; 下面是DESede方式的代码示例:

import android.annotation.SuppressLint; import java.io.IOException; import java.io.UnsupportedEncodingException; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import com.example.exmencrypt.base64.BASE64Decoder; import com.example.exmencrypt.base64.BASE64Encoder; public class Des3Util { // 定义加密算法,DESede即3DES private static final String Algorithm = "DESede"; @SuppressLint("TrulyRandom") private static byte[] encryptMode(String key, byte[] src) { try { SecretKey deskey = new SecretKeySpec(build3DesKey(key), Algorithm); Cipher cipher = Cipher.getInstance(Algorithm); cipher.init(Cipher.ENCRYPT_MODE, deskey); return cipher.doFinal(src); }catch (Exception e) { e.printStackTrace(); return null; } } private static byte[] decryptMode(String key, byte[] src) { try { SecretKey deskey = new SecretKeySpec(build3DesKey(key), Algorithm); Cipher cipher = Cipher.getInstance(Algorithm); cipher.init(Cipher.DECRYPT_MODE, deskey); return cipher.doFinal(src); }catch (Exception e) { e.printStackTrace(); return null; } } //根据字符串生成密钥24位的字节数组 private static byte[] build3DesKey(String keyStr) throws UnsupportedEncodingException { byte[] key = new byte[24]; byte[] temp = keyStr.getBytes("UTF-8"); if (key.length > temp.length) { System.arraycopy(temp, 0, key, 0, temp.length); }else { System.arraycopy(temp, 0, key, 0, key.length); } return key; } public static String encrypt(String key, String raw) { byte[] enBytes = encryptMode(key, raw.getBytes()); BASE64Encoder encoder = new BASE64Encoder(); return encoder.encode(enBytes); } public static String decrypt(String key, String enc) { try { BASE64Decoder decoder = new BASE64Decoder(); byte[] enBytes = decoder.decodeBuffer(enc); byte[] deBytes = decryptMode(key, enBytes); return new String(deBytes); } catch (IOException e) { e.printStackTrace(); return enc; } } }

下面是desede/CBC/PKCS5Padding方式的代码示例:

import android.annotation.SuppressLint; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.DESedeKeySpec; import javax.crypto.spec.IvParameterSpec; public class Des3Cipher { private static final String Algorithm = "desede"; // 向量 private final static String iv = "01234567"; // 加解密统一使用的编码方式 private final static String encoding = "utf-8"; @SuppressLint("TrulyRandom") public static String encrypt(String secretKey, String plainText) throws Exception { SecretKey deskey = null; DESedeKeySpec spec = new DESedeKeySpec(secretKey.getBytes()); SecretKeyFactory keyfactory = SecretKeyFactory.getInstance(Algorithm); deskey = keyfactory.generateSecret(spec); Cipher cipher = Cipher.getInstance("desede/CBC/PKCS5Padding"); IvParameterSpec ips = new IvParameterSpec(iv.getBytes()); cipher.init(Cipher.ENCRYPT_MODE, deskey, ips); byte[] encryptData = cipher.doFinal(plainText.getBytes(encoding)); return Base64Util.encode(encryptData); } public static String decrypt(String secretKey, String encryptText) throws Exception { SecretKey deskey = null; DESedeKeySpec spec = new DESedeKeySpec(secretKey.getBytes()); SecretKeyFactory keyfactory = SecretKeyFactory.getInstance(Algorithm); deskey = keyfactory.generateSecret(spec); Cipher cipher = Cipher.getInstance("desede/CBC/PKCS5Padding"); IvParameterSpec ips = new IvParameterSpec(iv.getBytes()); cipher.init(Cipher.DECRYPT_MODE, deskey, ips); byte[] decryptData = cipher.doFinal(Base64Util.decode(encryptText)); return new String(decryptData, encoding); } }

6.8 WebSocket

WebSocket介绍与原理

WebSocket 是一种双向通信协议,在建立连接后,WebSocket 服务器和 Browser/Client Agent 都能主动的向对方发送或接收数据,就像 Socket 一样;WebSocket 需要类似 TCP 的客户端和服务器端通过握手连接,连接成功后才能相互通信。

目的:即时通讯,替代轮询

连接过程 —— 握手过程

1. 浏览器、服务器建立TCP连接,三次握手。这是通信的基础,传输控制层,若失败后续都不执行。

2. TCP连接成功后,浏览器通过HTTP协议向服务器传送WebSocket支持的版本号等信息。(开始前的HTTP握手)

3. 服务器收到客户端的握手请求后,同样采用HTTP协议回馈数据。

4. 当收到了连接成功的消息后,通过TCP通道进行传输通信。

WebSocket 机制

非 WebSocket 模式传统 HTTP 客户端与服务器的交互如下图所示:

使用 WebSocket 模式客户端与服务器的交互如下图:

图 2.WebSocket 请求响应客户端服务器交互图

相对于传统 HTTP 每次请求-应答都需要客户端与服务端建立连接的模式,WebSocket 是类似 Socket 的 TCP 长连接的通讯模式,一旦 WebSocket 连接建立后,后续数据都以帧序列的形式传输。在客户端断开 WebSocket 连接或 Server 端断掉连接前,不需要客户端和服务端重新发起连接请求。

在海量并发及客户端与服务器交互负载流量大的情况下,极大的节省了网络带宽资源的消耗,有明显的性能优势,且客户端发送和接受消息是在同一个持久连接上发起,实时性优势明显。

WebSocket能更好的节省服务器资源和带宽并达到实时通讯,它建立在 TCP 之上,同 HTTP 一样通过 TCP 来传输数据

WebSocket和 HTTP 最大不同是

HTTP协议是非持久化的,单向的网络协议,在建立连接后只允许浏览器向服务器发出请求后,服务器才能返回相应的数据。这样的方法最明显的缺点就是需要不断的发送请求,而且通常HTTP request的Header是非常长的,为了传输一个很小的数据 需要付出巨大的代价,是很不合算的,占用了很多的宽带。会导致过多不必要的请求,浪费流量和服务器资源,每一次请求、应答,都浪费了一定流量在相同的头部信息上

然而WebSocket的出现可以弥补这一缺点。在WebSocket中,只需要服务器和浏览器通过HTTP协议进行一个握手的动作,然后单独建立一条TCP的通信通道进行数据的传送。

不同点

1. WebSocket是双向通信协议,模拟Socket协议,可以双向发送或接受信息。HTTP是单向的。

2. WebSocket是需要握手进行建立连接的。

相同点

1. 都是一样基于TCP的,都是可靠性传输协议。

2. 都是应用层协议。

原理

WebSocket同HTTP一样也是应用层的协议,但是它是一种双向通信协议,是建立在TCP之上的。

WebSocket与socket的区别

Socket其实并不是一个协议,而是为了方便使用TCP或UDP而抽象出来的一层,是位于应用层和传输控制层之间的一组接口。

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

当两台主机通信时,必须通过Socket连接,Socket则利用TCP/IP协议建立TCP连接。TCP连接则更依靠于底层的IP协议,IP协议的连接则依赖于链路层等更低层次。

WebSocket则是一个典型的应用层协议。

区别

Socket是传输控制层协议,WebSocket是应用层协议。

Android端的代码如下:

 private WebSocketConnection mConnect = new WebSocketConnection();  

//websocket连接,接收服务器消息

    /** 

     * websocket连接,接收服务器消息 

    */  

    private void connect() {  

        Log.i(TAG, "ws connect....");  

        try {  

            mConnect.connect(wsurl, new WebSocketHandler() {  

                @Override  

                public void onOpen() {  

                    Log.i(TAG, "Status:Connect to " + wsurl);  

                    sendUsername();  

                }  

                @Override  

                public void onTextMessage(String payload) {  

                    Log.i(TAG, payload);  

                    mText.setText(payload != null ? payload : "");  

//                    mConnect.sendTextMessage("I am android client");  

                }  

  

                @Override  

                public void onClose(int code, String reason) {  

                    Log.i(TAG, "Connection lost..");  

                }  

            });  

        } catch (WebSocketException e) {  

            e.printStackTrace();  

        }  

    }  

  

    /** 

     * 发送用户名给服务器 

     */  

    private void sendUsername() {  

        String user = mUserName.getText().toString();  

        if (user != null && user.length() != 0)  

            mConnect.sendTextMessage(user);  

        else  

            Toast.makeText(getApplicationContext(), "不能为空", Toast.LENGTH_SHORT).show();  

    }  

  

    /** 

     * 发送消息 

     * 

     * @param msg 

     */  

    private void sendMessage(String msg) {  

        if (mConnect.isConnected()) {  

            mConnect.sendTextMessage(msg);  

        } else {  

            Log.i(TAG, "no connection!!");  

        }  

    }  

  

    @Override  

    protected void onDestroy() {  

        super.onDestroy();  

        mConnect.disconnect();  

    }  

6.9 Socket

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

步骤一: 初始化 Socket 对象

客户端一般选择在一个新的线程中,初始化一个 Socket 对象,初始化是需要设置 IP 和端口号,以帮助低层网络路由找到相应的服务端进程。

步骤二: 获取与 Server 端通信的引用

此步和 Server 端建立连接后的步骤类似:根据步骤一中获取的 Socket 对象,进行封装,得到相应的输入、输出流对象,这些输入、输出流对象就是和 Server 端进行通信的引用。

步骤三: 通过步骤二中得到的引用,循环的读取(在新线程中) Server 端发送过来的消息,并做相应的处理

步骤四:在合适的时机关闭与 Server 端的 Socket 连接

private class connectService implements Runnable {

@Override

public void run() {//可以考虑在此处添加一个while循环,结合下面的catch语句,实现Socket对象获取失败后的超时重连,直到成功建立Socket连接

try {

Socket socket = new Socket(HOST, PORT); //步骤一

socket.setSoTimeout(60000);

printWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter( //步骤二

socket.getOutputStream(), "UTF-8")), true);

in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));

receiveMsg();

} catch (Exception e) {

Log.e(TAG, ("connectService:" + e.getMessage())); //如果Socket对象获取失败,即连接建立失败,会走到这段逻辑

}

}

}

private void receiveMsg() {

try {

while (true) { //步骤三

if ((receiveMsg = in.readLine()) != null) {

Log.d(TAG, "receiveMsg:" + receiveMsg);

runOnUiThread(new Runnable() {

@Override

public void run() {

mTextView.setText(receiveMsg + "\n\n" + mTextView.getText());

}

});

}

}

} catch (IOException e) {

Log.e(TAG, "receiveMsg: ");

e.printStackTrace();

}

}

}

6.10 安卓签名

  • 安卓签名的理解

Android的签名工作机制:

  1. 每个应用都必须签名
  2. 应用可以被不同的签名文件签名(如果有源代码或者反编译后重新编译)
  3. 同一个应用如果签名不同则不能覆盖安装
  • 安卓为啥要加签名机制?

为什么要签名:

  1. 发送者的身份认证:由于开发商可能通过使用相同的 Package Name 来混淆替换已经安装的程序,以此保证签名不同的包不被替换
  2. 保证信息传输的完整性:签名对于包中的每个文件进行处理,以此确保包中内容不被替换
  3. 防止交易中的抵赖发生:Market(应用市场)对软件的要求

 

给apk签名可以带来以下好处:

  1. 应用程序升级:能无缝升级到新的版本,必须要同一个证书进行签名并且包名称要相同。(如果证书不同,可能会被系统认为是不同的应用)
  2. 应用程序模块化:Android系统可以允许同一个证书签名的多个应用程序在一个进程里运行(系统实际把他们作为一个单个的应用程序),此时就可以把我们的应用程序以模块的方式进行部署,而用户可以独立的升级其中的一个模块
  3. 代码或者数据共享:Android提供了基于签名的权限机制,那么一个应用程序就可以为另一个以相同证书签名的应用程序公开自己的功能。

 

签名的说明:

  1. 所有的应用程序都必须有数字证书:Android 系统不会安装一个没有数字证书的应用程序
  2. Android 程序包使用的数字证书可以是自签名的:不需要一个权威的数字证书机构签名认证
  3. 使用一个合适的私钥生成的数字证书来给程序签名:如果要正式发布一个 Android 应用,必须使用一个合适的私钥生成的数字证书来给程序签名,而不能使用 adt 插件或者 ant 工具生成的调试证书来发布
  4. 数字证书都是有有效期的:Android 只是在应用程序安装的时候才会检查证书的有效期。如果程序已经安装在系统中,即使证书过期也不会影响程序的正常功能

 

6.11 视频加密传输

  1. DES加密。用java中提供的加密包。
  2. 将视频文件的数据流前100个字节中的每个字节与其下标进行异或运算。解密时只需将加密过的文件再进行一次异或运算即可。

 

6.12 App 是如何沙箱化,为什么要这么做?

在Android系统中,应用(通常)都在一个独立的沙箱中运行,即每一个Android应用程序都在它自己的进程中运行,都拥有一个独立的Dalvik虚拟机实例。Dalvik经过优化,允许在有限的内存中同时高效地运行多个虚拟机的实例,并且每一个Dalvik应用作为一个独立的Linux进程执行。Android这种基于Linux的进程“沙箱”机制,是整个安全设计的基础之一。

Android扩展了Linux内核安全模型的用户与权限机制,将多用户操作系统的用户隔离机制巧妙地移植为应用程序隔离。将UID(一个用户标识)不同的应用程序自然形成资源隔离,如此便形成了一个操作系统级别的应用程序“沙箱”。

Android应用程序的“沙箱”机制:

  1. 互相不具备信任关系的应用程序相互隔离,独自运行,访问是禁止的:
  2. 通过共享UID(SharedUserID),应用程序在同一个进程运行,共享数据

Android 的权限分离的基础是建立在 Linux 已有的 uid 、 gid 、 gids 基础上的 。

  1. UID: Android 在 安装一个应用程序,就会为 它 分配一个 uid 。(其中普通 A ndroid 应用程序的 uid 是从 10000 开始分配, 10000 以下是系统进程的 uid )
  2. GID:对 于普通应用程序来说, gid 等于 uid 。由于每个应用程序的 uid 和 gid 都不相同, 因此不管是 native 层还是 java 层都能够达到保护私有数据的作用 。
  3. GIDS: gids 是由框架在 Application 安装过程中生成,与 Application 申请的具体权限相关。

Act ivityManagerService 中的 startProcessLocked 。在通过 zygote 来启动一个 process 时,直接将 uid 传给 给了 gid 。再通过 zygote 来 fork 出新的进程( zygote.java 中的 forkAndSpecialize ),最终在 native 层( dalvik_system_zygote.c )中的 forkAndSpecializeCommon 中通过 linux 系统调用来进行 gid 和 uid 和 gids 的设置。

6.13 权限管理系统(底层的权限是如何进行 grant 的)?

  1. permission 的初始化:是指 permission 的向系统申请,系统进行检测并授权,并建立相应的数据结构。
  2. 权限检查:Android framework 中提供了一些接口用来对外来的访问(包括自己)进行权限检查 。 这些接口 主要通过 ContextWrapper 提供,具体实现在 ContextImpl 中 。ContextImpl.java 中提供的 API ,其实都是由 ActivityManagerService 中的如下几个接口进行的封装。

其中有一个checkPermission() // 主要用于一般的 permission 检查

checkPermission 的实现分析

  1. 如果传入的 permission 名称为 null ,那么返回 PackageManager.PERMISSION_DENIED 。
  2. 判断调用者 uid 是否符合要求 。
  3. 如果通过 2 的检查后,再 调用 PackageManagerService.checkUidPermission ,判断 这个 uid 是否拥有相应的权限

3.权限校验之后,应给分发了

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值