Android开发问题汇总(一)

1.Gradle使用代理服务器

1
2
3
4
5
6
7
// /Users/<username>/.gradle/gradle.properties (Mac)
# Http Proxy
systemProp.http.proxyHost=www.somehost.org
systemProp.http.proxyPort=8080
systemProp.http.proxyUser=userid
systemProp.http.proxyPassword=password
systemProp.http.nonProxyHosts=*.nonproxyrepos.com|localhost

Reference:Gradle使用代理服务器

2.设置Gradle home

1
2
// Mac OS X
Use local gradle distribution > Gradle home > /Applications/Android Studio.app/Contents/plugins/gradle

3.The project is using an unsupported version of Gradle

1
2
3
4
The project is using an unsupported version of Gradle.
Please point to a supported Gradle version in the project's Gradle settings or in the project's Gradle wrapper (if applicable.)

Consult IDE log for more details (Help | Show Log)
继续阅读

在 iOS App 中使用自签名证书

大多数App都需和Server通信来提供服务,这中间就牵涉到网络通信安全。网络通信安全是一个很大的话题,本文不打算全面覆盖,而是来理理HTTPS。

移动设备可能会处于不安全的网络环境中,比如连接了某个公共热点,攻击者不需要访问设备,只需访问设备所在的网络,就能获取到用户信息,所以,当应用中用户的信息需要保护时,开发者需要保证通信的安全性。

最简单直接的解决办法是采用HTTPS,在web服务器上安装一个自签名证书,启用HTTPS,然后对NSURLSession进行配置以接受该自签名证书。

HTTPS是如何做到通信安全的呢?答案是TLS/SSL协议。TLS(Transport Layer Security)/SSL(Secure Socket Layer)协议是专门为解决网络通信安全设计的。它的基石是非对称加密。

TLS/SSL链路中的数据是加密的,客户端给服务器发送的数据是用服务器的公钥加密的,由于非对称加密的数学特性,只有拥有私钥的服务器才能正确解密数据。服务器给客户端发送的数据则是用自己的私钥加密的,客户端用公钥解密。

那么我们如何判断服务器发给我们的公钥是值得信任的呢?通常商业网站的数字证书都是由中级证书或根证书来签名,而根证书是一开始就内置在设备中,不是通过网络交换的,这样当某个服务器声明说我是某某,我们可以通过证书链来判断真伪。

根证书其实是一个自签名证书,我们的应用也可以用自签名证书来确保网络通信安全,还可以省掉很大一笔证书费用,只要私钥足够安全,它甚至比商业证书更安全。

创建自签名证书

为了方便创建自签名证书来测试 TLS, Apple 为我们提供一个工具 Certificate Assitant,它内置在 OS X 中,我们可以通过 KeyChain 打开它;我们也可以使用 openssl。新手的话还是建议使用 Certificate Assitant. 详细步骤参考Creating Certificates for TLS Testing.

为服务端配置证书

我使用的是 Apache,配置如下:

1
2
3
4
5
6
7
#/etc/apache2/httpd.conf
<VirtualHost *:443>
#ServerName www.example.com
SSLEngine on
SSLCertificateFile "/etc/apache2/server.crt"
SSLCertificateKeyFile "/etc/apache2/server.key"
</VirtualHost>

由于 Apache 是使用 PEM 格式的证书和私钥,所以我们需要格式转换下:

1
2
3
4
5
6
7
8
9
10
11
12
13
#Extracting a digital identity for use with Apache

$ # First extract the server certificate.
$
$ openssl pkcs12 -in "Deep Thought.p12" -nokeys -out server.crt
Enter Import Password: ****
MAC verified OK
$
$ # Next extract the server private key.
$
$ openssl pkcs12 -in "Deep Thought.p12" -nocerts -nodes -out server.key
Enter Import Password: ****
MAC verified OK

重启 Apache, 我们可以使用 openssl 的 s_client 子命令来测试下。

1
2
3
4
5
// Failed
$ openssl s_client -connect myserver.com:443

// Success
$ openssl s_client -connect myserver.com:443 -CAfile ./MyCACertificate.pem

接受自签名证书

URLSession

我们需要介入到 TLS 的授权过程,基本做法是判断是与我们指定的服务器通信需要授权,然后把自签名证书加入锚中。代码如下:

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
57
58
59
60
61
62
// Authentication Challenges and TLS Chain Validation
func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {

    print("authentication method \(challenge.protectionSpace.authenticationMethod)\n host: \(challenge.protectionSpace.host)")

        if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust && challenge.protectionSpace.host == "dongmeiliangsmacbook-pro.local" {

            // Custom evaluating a trust object
            let serverTrust = challenge.protectionSpace.serverTrust!
                let policy = SecPolicyCreateSSL(true, "dongmeiliangsmacbook-pro.local" as CFString)

                SecTrustSetPolicies(serverTrust, [policy] as CFArray)

                let path = Bundle.init(for: ViewController.self).path(forResource: "ServerCertificates", ofType: "cer")

                do {
                    let certData = try NSData(contentsOfFile: path!, options: NSData.ReadingOptions(rawValue: 0))
                        if let certificate = SecCertificateCreateWithData(nil, certData as CFData) {
                            SecTrustSetAnchorCertificates(serverTrust, [certificate] as CFArray)

                                var allowConnection = false

                                var trustResult: SecTrustResultType = .invalid

                                let err = SecTrustEvaluate(serverTrust, &trustResult)

                                if err == noErr {
                                    allowConnection = (trustResult == .unspecified) || (trustResult == .proceed)
                                }

                            print("err:\(err)\nallowConnection:\(allowConnection)")

                                    if
                                        allowConnection
                                        {
                                            completionHandler(.useCredential, URLCredential(trust:serverTrust))
                                        }
                                    else
                                    {
                                        completionHandler(.cancelAuthenticationChallenge, nil)
                                    }

                        }
                        else
                        {
                            print("certificate create with data failed")
                            completionHandler(.cancelAuthenticationChallenge, nil)
                        }

                }
            catch
            {
                print("read certificate data failed:\(error)")
                completionHandler(.cancelAuthenticationChallenge, nil)
            }

        }
        else
        {
            completionHandler(.performDefaultHandling, nil)
        }
}

AFNetworking

AFNetworking 只需要我们配置下 securityPolicy,代码如下:

1
2
3
4
5
6
7
8
9
lazy var apiClient: AFHTTPSessionManager = {
    let client = AFHTTPSessionManager(baseURL: URL(string: "https://dongmeiliangsmacbook-pro.local/"))
        let selfSignedCertificates = AFSecurityPolicy.certificates(in: Bundle.init(for: ViewController.self))

        client.securityPolicy = AFSecurityPolicy(pinningMode: .certificate, withPinnedCertificates: selfSignedCertificates)
        client.securityPolicy.allowInvalidCertificates = true

        return client
}()

注意点

客户端是把服务端的证书加入锚中。

完整示例

SelfSignedCertificate

Reference

AFNetworking SSL Pinning With Self-Signed Certificates
Creating Certificates for TLS Testing
HTTPS Server Trust Evaluation
URL Session Programming Guide

如何创建自定义的Xcode 6 工程模板

随着时间的推移,这篇文章的实践部分需要更新了,我找到一个更好的工具来做这件事,这就是 liftoff。相比与手动来制作工程模板,自定义 liftoff 的配置文件要容易很多,更重要的是它提供了文档。

liftoff 支持配置工程的组,目录结构和模板。配置工程的组和目录结构是通过 .liftoffrc 文件,它查找顺序是 ./.liftoffrc > ~/.liftoffrc > 默认配置文件,可以 man liftoffrc 来查看详细介绍。

litfoff 还可以在新建工程时通过在 .liftoffrc 中指定包含哪些文件,这些文件的来源顺序是 ./.liftoff/templates > ~/.liftoff/templates > 默认的文件。

通过 .liftoffrc 来配置我们想要的工程结构,然后自定义Podfile 来包含工程常用的 pod,可以为我们在新建工程省些事,减轻些负担,总之还是我们之前想办法把重复的事情自动化的思想。

下面是我的 ~/.liftoffrc:

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
############################################################################
# The following keys can be used to configure defaults for project creation
# project_name:
# company:
# author:
# prefix:
# company_identifier:
############################################################################

test_target_name: UnitTests
configure_git: true
warnings_as_errors: true
enable_static_analyzer: true
indentation_level: 4
use_tabs: false
dependency_managers: cocoapods
enable_settings: false
strict_prompts: false
deployment_target: 8.0

run_script_phases:
  - file: todo.sh
    name: Warn for TODO and FIXME comments
  - file: bundle_version.sh
    name: Set version number

templates:
  - test.sh: bin/test
  - setup.sh: bin/setup
  - README.md: README.md

warnings:
  - GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED
  - GCC_WARN_MISSING_PARENTHESES
  - GCC_WARN_ABOUT_RETURN_TYPE
  - GCC_WARN_SIGN_COMPARE
  - GCC_WARN_CHECK_SWITCH_STATEMENTS
  - GCC_WARN_UNUSED_FUNCTION
  - GCC_WARN_UNUSED_LABEL
  - GCC_WARN_UNUSED_VALUE
  - GCC_WARN_UNUSED_VARIABLE
  - GCC_WARN_SHADOW
  - GCC_WARN_64_TO_32_BIT_CONVERSION
  - GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS
  - GCC_WARN_ABOUT_MISSING_NEWLINE
  - GCC_WARN_UNDECLARED_SELECTOR
  - GCC_WARN_TYPECHECK_CALLS_TO_PRINTF
  - GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS
  - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS
  - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF
  - CLANG_WARN_IMPLICIT_SIGN_CONVERSION
  - CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION
  - CLANG_WARN_EMPTY_BODY
  - CLANG_WARN_ENUM_CONVERSION
  - CLANG_WARN_INT_CONVERSION
  - CLANG_WARN_CONSTANT_CONVERSION

xcode_command: open -a 'Xcode' .

project_template: objc

app_target_templates:
  objc:
    - <%= project_name %>:
      - Categories:
      - Protocols:
      - Headers:
      - Models:
      - Sections:
      - Classes:
      - AppDelegate:
        - <%= prefix %>AppDelegate.h
        - <%= prefix %>AppDelegate.m
      - Network:
      - DataPersistence:
      - Docs:
      - Vendors:
      - Resources:
        - Images.xcassets
        - Nibs:
          - LaunchScreen.xib
        - Other-Sources:
          - Info.plist
          - <%= project_name %>-Prefix.pch
          - main.m
  swift:
    - <%= project_name %>:
      - Extensions:
      - Protocols:
      - Models:
      - ViewModels:
      - Controllers:
        - AppDelegate.swift
      - Views:
      - Resources:
        - Images.xcassets
        - Storyboards:
          - Main.storyboard
        - Nibs:
          - LaunchScreen.xib
        - Other-Sources:
          - Info.plist

test_target_templates:
  objc:
    - <%= test_target_name %>:
      - Resources:
        - <%= test_target_name %>-Info.plist
        - <%= test_target_name %>-Prefix.pch
      - Helpers:
      - Tests:
  swift:
    - <%= test_target_name %>:
      - Resources:
        - <%= test_target_name %>-Info.plist
      - Helpers:
      - Tests:

使用Xcode 6新建工程时,Apple准备了好些模板,这些模板写个Demo还是没有问题的,但是用来组织项目文件还是太弱了,所以情况经常是不得不每次去新建各种目录,这种重复性的劳动一来乏味,二来浪费时间。那么我们能不像创建自己的模板呢?这样新建的工程就能按自己的想法包含各种目录和文件。好消息是可以,坏消息是Apple没有提供相应的文档。虽然没有文档,还是试着来创建一个模板,每次都重复实在太烦(就是这么任性)。

既然没有文档,我们就把Apple的模板复制一份,在它的基础上修改成我们需要的样子。/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates/Project\ Templates/iOS/Application/有iOS所有工程模板。用户自定义的模板建议放到~/Library/Developer/Xcode/Templates/,目录如果不存在就创建。模板至少要包含两部分:一是扩展名为.xctemplate的文件夹;二是名称为TemplateInfo.plist的属性列表文件。好了,我们来创建一个自定义模板:

1
2
3
4
5
// Step 1:
$ mkdir ~/Library/Developer/Xcode/Templates/CocoaBite.xctemplate/

// Step 2:
$ cp /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates/Project\ Templates/iOS/Application/Single\ View\ Application.xctemplate/* ~/Library/Developer/Xcode/Templates/CocoaBite.xctemplate/
继续阅读