ios获取udid

背景

苹果企业账号打包分发app,方便内部安装测试App。最近公司企业账号续费失败,申请新的企业账号没成功。(国内近2年基本没有企业申请成功)
这就蛋疼了,了解到其他app是通过多开发者账号,adhoc证书白名单的方式来做分发,adhoc证书无法解决设备数量的问题,最多100台设备,而且还要提前注册好,一年更新一次,注册设备就是要拿到设备的udid。

获取udid

了解到苹果可以通过safari以OTA(over the air)的方式拿到udid。

整体流程分三步

  1. 下发udid.mobileconfig文件
  2. 授权安装配置文件
  3. 回调解析xml拿到udid


udid.mobileconfig

一个xml文件,文件内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<dict>
<key>URL</key>
<string>https://xxx.net/udid/action</string> <!--接收数据的接口地址-->
<key>DeviceAttributes</key>
<array>
<string>SERIAL</string>
<string>MAC_ADDRESS_EN0</string>
<string>UDID</string>
<string>IMEI</string>
<string>ICCID</string>
<string>VERSION</string>
<string>PRODUCT</string>
</array>
</dict>
<key>PayloadOrganization</key>
<string>dev.xxx.com</string> <!--组织名称-->
<key>PayloadDisplayName</key>
<string>查询设备UDID</string> <!--安装时显示的标题-->
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadUUID</key>
<string>CD0BD3C5-7164-B9DA-FCBB-D81AA343C7A0</string> <!--自己随机填写的唯一字符串-->
<key>PayloadIdentifier</key>
<string>dev.xxx.profile-service</string>
<key>PayloadDescription</key>
<string>本文件仅用来获取设备ID</string> <!--描述-->
<key>PayloadType</key>
<string>Profile Service</string>
</dict>
</plist>

需要注意就是回调接口 需要支持https,否者会报ATS错误 the resource could not be loaded because the app transport security policy requires the use of a secure connection

起service下发 xml 文件,我这里以java spring mvc来实现的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RequestMapping("/udid.mobileconfig")
@ResponseBody
public ResponseEntity<byte[]> downloadsEntity(HttpServletRequest request) throws Exception {
String path = Objects.requireNonNull(this.getClass().getClassLoader().getResource("udid.mobileconfig")).toURI().getPath();
File file = new File(path);
if (!file.isFile()) {
return null;
}

byte[] bytes = FileUtils.readFileToByteArray(file);

HttpHeaders headers = new HttpHeaders();
headers.add("Content-type","application/x-apple-aspen-config; charset=utf-8"); // 按苹果的规范
headers.add("Content-Disposition", "attachment;filename=" + "udid.mobileconfig");

HttpStatus status = HttpStatus.OK;
return new ResponseEntity<>(bytes, headers, status);
}

授权安装

下载udid.mobileconfig之后,系统会自动在设置里面登记该配置文件,需要自行去设置里面 授权安装。
安装成功之后 会回调 mobileconfig里面提供的回调地址,udid等信息会以参数的形式一并带上,格式也是xml格式。

如果下载的过程提示无效文件,可能就是mobileconfig文件内容有问题,可以通过pc浏览器检查下文件内容,我这里就遇到了文件乱码的情况

上面提到文件内容是以字节流的方式下发的,配置好ByteArrayHttpMessageConverter的顺序就ok了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!--自动注册基于注解风格的处理器 (数据转换 格式化 数据)-->
<mvc:annotation-driven>
<mvc:message-converters>
<ref bean="stringHttpMessageConverter"/>

<!--配置ByteArrayHttpMessageConverter-->
<bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>

<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="features">
<array>
<!-- List字段如果为null,输出为[],而非nul -->
<value>WriteNullListAsEmpty</value>
<!-- 字符类型字段如果为null,输出为”“,而非null -->
<value>WriteNullStringAsEmpty</value>
<!-- 进制循环引用 -->
<value>DisableCircularReferenceDetect</value>
<!--Boolean字段如果为null,输出为false,而非null -->
<value>WriteNullBooleanAsFalse</value>
<!--时间 yyyy.MM.dd hh:mm:ss -->
<value type="com.alibaba.fastjson.serializer.SerializerFeature">WriteDateUseDateFormat</value>
</array>
</property>
</bean>

</mvc:message-converters>
</mvc:annotation-driven>

解析udid

还是以java实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@RequestMapping(value = "/action", method = RequestMethod.POST)
@ResponseBody
public ResponseEntity<String> action(HttpServletRequest request) throws IOException {

//获取HTTP请求的输入流
InputStream is = request.getInputStream();
//已HTTP请求输入流建立一个BufferedReader对象
BufferedReader br = new BufferedReader(new InputStreamReader(is,"UTF-8"));
StringBuilder sb = new StringBuilder();

//读取HTTP请求内容
String buffer = null;
while ((buffer = br.readLine()) != null) {
sb.append(buffer);
}
String content = sb.toString().substring(sb.toString().indexOf("<?xml"), sb.toString().indexOf("</plist>")+8);
System.out.println(content);


// 创建xml解析对象
SAXReader reader = new SAXReader();
// 定义一个文档
Document document = null;
//将字符串转换为
try {
document = reader.read(new ByteArrayInputStream(content.getBytes("GBK")));
} catch (DocumentException e) {
e.printStackTrace();
}

String udid = "ERROR";
if(document != null) {
Element element = (Element) document.selectNodes("/plist/dict").get(0);
for(Iterator iter = element.content().iterator(); iter.hasNext();) {
DefaultElement next = (DefaultElement) iter.next();
String name = next.getName();
String text = next.getText();

if(StringUtils.isEquals(name,"key") && StringUtils.isEquals(text,"UDID") && iter.hasNext()) {
next = (DefaultElement) iter.next();
name = next.getName();
text = next.getText();

udid = text;
}
}
}

HttpHeaders headers = new HttpHeaders();
headers.add("Content-type","text/html; charset=utf-8");
headers.setLocation(URI.create("https://xxx.com/udid/udid?udid="+udid)); /// 跳转到指定的页面 展示udid信息

HttpStatus status = HttpStatus.MOVED_PERMANENTLY;
return new ResponseEntity<>("", headers, status);

}

解析xml就可以拿到udid字段,注意的是 重定向一定要使用301重定向(MOVED_PERMANENTLY),有些重定向默认是302重定向,这样就会导致安装失败,设备安装会提示”无效的描述文件