編輯:關於Android編程
如果你自己想做一個客戶端玩玩,但是又不想去搭建後台服務器,顯然Bmob移動後端雲是你的最佳選擇。官方地址見bmob,他提供了Android的sdk,同樣也提供了Restful Api,但是個人建議Restful Api還是不適合直接在客戶端使用,畢竟會暴露一下一些key的信息,但是本篇文章就是在android中使用它的restful api,原因嘛很簡單,我想網絡層自己控制,不想用它提供的android sdk,對於安全方面,同樣給出了這種情況的解決方法。
我們使用OkHttp,還需要用到Gson,增加依賴
compile 'com.squareup.okhttp:okhttp:2.5.0'
compile 'com.google.code.gson:gson:2.3.1'
增加網絡訪問權限
data-snippet-id=ext.a07a4cf67b90ad77df941de12b11b97b data-snippet-saved=false data-csrftoken=wlzdJGob-PUBlORu9Wxf4eDhkE0NYjNzHse4 data-codota-status=done>
編寫一個網絡的請求,插入一條數據
實體類
public class Person {
private String name;
private String address;
private int age;
public Person(String name, String address, int age) {
this.name = name;
this.address = address;
this.age = age;
}
public Person() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return Person{ +
name=' + name + ''' +
, address=' + address + ''' +
, age= + age +
'}';
}
}
進行請求,這部分代碼是java平台的,在android上你需要開啟一個線程。
private static final String URL_INSERT =https://api.bmob.cn/1/classes/Person;
private static final String APPLICATION_ID=8dcb9fee2f******14ab19e7dfd9d;
private static final String API_KEY=aebe3b71c9b2***********430ac2de560b1;
private static final MediaType JSON = MediaType.parse(application/json; charset=utf-8);
private static final Gson gson=new Gson();
private static final OkHttpClient client=new OkHttpClient();
Person person=new Person(張三,杭州,20);
RequestBody body = RequestBody.create(JSON, gson.toJson(person));
Request insert=new Request.Builder()
.url(URL_INSERT)
.addHeader(Content-Type,application/json)
.addHeader(X-Bmob-Application-Id, APPLICATION_ID)
.addHeader(X-Bmob-REST-API-Key,API_KEY)
.post(body)
.build();
try {
Response execute = client.newCall(insert).execute();
System.out.println(execute.body().string());
} catch (IOException e) {
e.printStackTrace();
}
注意請求頭裡面的幾個參數,必須設置。
這時候如果你進行請求,你會發現會報一個異常
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
原來是https請求,我們需要獲得證書。
當然這時候你有兩個選擇,一個是信任所有證書。
public class MyX509TrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
try {
// 創建SSLContext對象,並使用我們指定的信任管理器初始化
TrustManager[] tm = { new MyX509TrustManager() };
SSLContext sslContext = SSLContext.getInstance(SSL, SunJSSE);
sslContext.init(null, tm, new java.security.SecureRandom());
// 從上述SSLContext對象中得到SSLSocketFactory對象
SSLSocketFactory ssf = sslContext.getSocketFactory();
client.setSslSocketFactory(ssf);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
但是這種方法有安全隱患,我們還是使用證書吧。
首先用Chrome打開https://api.bmob.cn/,然後點擊鏈接左邊的鎖的圖形,切到連接項,點擊證書信息,如下圖
然後將證書導出即可,之後一直下一步即可。
這裡假設導出的證書名字為bmob.cer,將其放到assets目錄下。
然後編寫一個https驗證的工具類。
/**
* User:lizhangqu([email protected])
* Date:2015-09-23
* Time: 17:45
*/
public class SSLUtil {
//使用命令keytool -printcert -rfc -file srca.cer 導出證書為字符串,然後將字符串轉換為輸入流,如果使用的是okhttp可以直接使用new Buffer().writeUtf8(s).inputStream()
/**
* 返回SSLSocketFactory
*
* @param certificates 證書的輸入流
* @return SSLSocketFactory
*/
public static SSLSocketFactory getSSLSocketFactory(InputStream... certificates) {
return getSSLSocketFactory(null,certificates);
}
/**
* 雙向認證
* @param keyManagers KeyManager[]
* @param certificates 證書的輸入流
* @return SSLSocketFactory
*/
public static SSLSocketFactory getSSLSocketFactory(KeyManager[] keyManagers, InputStream... certificates) {
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance(X.509);
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
int index = 0;
for (InputStream certificate : certificates) {
String certificateAlias = Integer.toString(index++);
keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
try {
if (certificate != null)
certificate.close();
} catch (IOException e) {
}
}
SSLContext sslContext = SSLContext.getInstance(TLS);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
sslContext.init(keyManagers, trustManagerFactory.getTrustManagers(), new SecureRandom());
SSLSocketFactory socketFactory = sslContext.getSocketFactory();
return socketFactory;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 獲得雙向認證所需的參數
* @param bks bks證書的輸入流
* @param keystorePass 秘鑰
* @return KeyManager[]對象
*/
public static KeyManager[] getKeyManagers(InputStream bks, String keystorePass) {
KeyStore clientKeyStore = null;
try {
clientKeyStore = KeyStore.getInstance(BKS);
clientKeyStore.load(bks, keystorePass.toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(clientKeyStore, keystorePass.toCharArray());
KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
return keyManagers;
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (UnrecoverableKeyException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
如果你不想使用文件,則你可以導出證書的內容,使用命令進行導出
keytool -printcert -rfc -file bmob.cer
然後將其賦值給一個字符串
private static final String CERT=-----BEGIN CERTIFICATE-----
+
MIIGLjCCBRagAwIBAgIDFCb6MA0GCSqGSIb3DQEBCwUAMIGMMQswCQYDVQQGEwJJTDEWMBQGA1UE
+
ChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2ln
+
bmluZzE4MDYGA1UEAxMvU3RhcnRDb20gQ2xhc3MgMSBQcmltYXJ5IEludGVybWVkaWF0ZSBTZXJ2
+
ZXIgQ0EwHhcNMTQxMTA3MDExNzM0WhcNMTUxMTA4MDIxMDQ2WjBJMQswCQYDVQQGEwJDTjEUMBIG
+
A1UEAxMLYXBpLmJtb2IuY24xJDAiBgkqhkiG9w0BCQEWFWhlc2hhb3l1ZUBmb3htYWlsLmNvbTCC
+
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANCEvBFYJmhW+8iixdK0zlzwprsuytUGW5BH
+
ye9EEkJzGzYfVnEO/v4wC3vEvlWqkwTxY/ydnneH+yo0msAN6IEt6IA+3eO55PAlooAF8b8I2e83
+
usRTK4YmooZc/2GYNk2WBXvVlMuWABMKJ/oQMXlM46gffd3Z+evbbptZ5vm+QEWjUlw8fsTALakq
+
JgKsrmGSNBVngx1qnm00DL/3yfR2DZHro4CDzRp4toQV3ofcnt6Nz43Z4YkAXZr5gqxge8BZ2n8P
+
raQo/5wSfWoPW79Z8lPvZSZv5UIGCUAXdt0qYb3awSDsPSnMrRl03V4XmOK3RDdYDPrWMvii+YrC
+
/vUCAwEAAaOCAtkwggLVMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgOoMBMGA1UdJQQMMAoGCCsGAQUF
+
BwMBMB0GA1UdDgQWBBR8ztcEh/lE/9fxcga6p7/b+x+pUTAfBgNVHSMEGDAWgBTrQjTQmLCrn/Qb
+
awj3zGQu7w4sRTAfBgNVHREEGDAWggthcGkuYm1vYi5jboIHYm1vYi5jbjCCAVYGA1UdIASCAU0w
+
ggFJMAgGBmeBDAECATCCATsGCysGAQQBgbU3AQIDMIIBKjAuBggrBgEFBQcCARYiaHR0cDovL3d3
+
dy5zdGFydHNzbC5jb20vcG9saWN5LnBkZjCB9wYIKwYBBQUHAgIwgeowJxYgU3RhcnRDb20gQ2Vy
+
dGlmaWNhdGlvbiBBdXRob3JpdHkwAwIBARqBvlRoaXMgY2VydGlmaWNhdGUgd2FzIGlzc3VlZCBh
+
Y2NvcmRpbmcgdG8gdGhlIENsYXNzIDEgVmFsaWRhdGlvbiByZXF1aXJlbWVudHMgb2YgdGhlIFN0
+
YXJ0Q29tIENBIHBvbGljeSwgcmVsaWFuY2Ugb25seSBmb3IgdGhlIGludGVuZGVkIHB1cnBvc2Ug
+
aW4gY29tcGxpYW5jZSBvZiB0aGUgcmVseWluZyBwYXJ0eSBvYmxpZ2F0aW9ucy4wNQYDVR0fBC4w
+
LDAqoCigJoYkaHR0cDovL2NybC5zdGFydHNzbC5jb20vY3J0MS1jcmwuY3JsMIGOBggrBgEFBQcB
+
AQSBgTB/MDkGCCsGAQUFBzABhi1odHRwOi8vb2NzcC5zdGFydHNzbC5jb20vc3ViL2NsYXNzMS9z
+
ZXJ2ZXIvY2EwQgYIKwYBBQUHMAKGNmh0dHA6Ly9haWEuc3RhcnRzc2wuY29tL2NlcnRzL3N1Yi5j
+
bGFzczEuc2VydmVyLmNhLmNydDAjBgNVHRIEHDAahhhodHRwOi8vd3d3LnN0YXJ0c3NsLmNvbS8w
+
DQYJKoZIhvcNAQELBQADggEBAF/t9Bc14BV0OwXcFf4Bs8y+p1AdbMqualCvLzjS95Z9HbPGcbRl
+
W76XwaM7iFE1R4mR1lGBQsacbBHOCNeZURYWGAG5c/yqhqCmWCzVJxM88AhCzkEv98uKa3IqE1zY
+
lOpYn4cMVqpPgg47QXqUfQlRoh21UTTORgiHEUY+JYNIlIXLoHtHVR0886+pIAq5fFrCwMHF45Df
+
r8tuTASazhYJUlOiGQTVv5p8Kg1wJ0ftMs9xJpThcnpEWrngmnNH/8H05rvJ9dEHkpnAU4mL46Bb
+
rmQe3oNoGE5EISL9KGVUMeS9wcR2kx+VmGhnAh7kjn5KuEidgfajS3XlcJ5o9t0=
+
-----END CERTIFICATE-----;
然後使用OkIO裡的Buffer類進行讀取並設置
SSLSocketFactory sslSocketFactory = SSLUtil.getSSLSocketFactory(new Buffer().writeUtf8(CERT).inputStream());
client.setSslSocketFactory(sslSocketFactory);
當然之前我們已將將其放到assets目錄下了,就不用這麼麻煩的導出字符串,賦值等操作,我們之間使用該文件即可
try {
SSLSocketFactory sslSocketFactory = SSLUtil.getSSLSocketFactory(getAssets().open(CERT_FILENAME));
client.setSslSocketFactory(sslSocketFactory);
} catch (IOException e) {
e.printStackTrace();
}
這時候你請求一下,就會發現可以成功請求了,並且服務器返回了信息
請求的問題解決了,那麼還遺留了一個重要的問題,就是如果使用Restful API,我們的APPLICATION_ID和API_KEY就直接暴露在客戶端了。有沒有一種方法可以提高一定的安全性呢,方法是有的,只不過只是相對來說安全一點,但是如果人家想搞你,那也是沒有辦法的,方法就是使用jni獲得這兩個值,由jni層返回這兩個字符串。
Android Studio下ndk的開發環境搭建見Android Studio使用新的Gradle構建工具配置NDK環境,這裡不再累贅。
聲明java層方法
public class KeyUtil {
static {
System.loadLibrary(key);
}
public static native String getApplicationId();
public static native String getAPIKey();
}
使用alt+enter生成jni方法,並在裡面返回這兩個值
JNIEXPORT jstring JNICALL
Java_cn_edu_zafu_bmobdemo_util_KeyUtil_getApplicationId(JNIEnv *env, jclass instance) {
char returnValue[]=8dcb9fe*************ab19e7dfd9d;
return (*env)->NewStringUTF(env, returnValue);
}
JNIEXPORT jstring JNICALL
Java_cn_edu_zafu_bmobdemo_util_KeyUtil_getAPIKey(JNIEnv *env, jclass instance) {
char returnValue[]=aebe3b71c9b*****************e560b1;
return (*env)->NewStringUTF(env, returnValue);
} data-snippet-id=ext.5eb349e2ad3e9fd820357c64c10973e8 data-snippet-saved=false data-csrftoken=JNlvlUwD-f2fRHXXRHfvw_8E266uWldmU0fQ data-codota-status=done>#include
JNIEXPORT jstring JNICALL
Java_cn_edu_zafu_bmobdemo_util_KeyUtil_getApplicationId(JNIEnv *env, jclass instance) {
char returnValue[]=8dcb9fe*************ab19e7dfd9d;
return (*env)->NewStringUTF(env, returnValue);
}
JNIEXPORT jstring JNICALL
Java_cn_edu_zafu_bmobdemo_util_KeyUtil_getAPIKey(JNIEnv *env, jclass instance) {
char returnValue[]=aebe3b71c9b*****************e560b1;
return (*env)->NewStringUTF(env, returnValue);
}
在java層要獲得這兩個值只要使用對應的靜態方法即可。
KeyUtil.getApplicationId();
KeyUtil.getAPIKey();
最終,我們的代碼也就成了這樣子
public class MainActivity extends AppCompatActivity {
private static final MediaType JSON = MediaType.parse(application/json; charset=utf-8);
private static final String URL =https://api.bmob.cn/1/classes/Person;
private static final String CERT_FILENAME =bmob.cer;
private static final Gson gson=new Gson();
private static final OkHttpClient client=new OkHttpClient();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
SSLSocketFactory sslSocketFactory = SSLUtil.getSSLSocketFactory(getAssets().open(CERT_FILENAME));
client.setSslSocketFactory(sslSocketFactory);
} catch (IOException e) {
e.printStackTrace();
}
Person person=new Person(張三,杭州,20);
RequestBody body = RequestBody.create(JSON, gson.toJson(person));
Request insert=new Request.Builder()
.url(URL)
.addHeader(Content-Type,application/json)
.addHeader(X-Bmob-Application-Id, KeyUtil.getApplicationId())
.addHeader(X-Bmob-REST-API-Key,KeyUtil.getAPIKey())
.post(body)
.build();
client.newCall(insert).enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
}
@Override
public void onResponse(Response response) throws IOException {
Log.e(TAG,response.body().string());
}
});
}
});
}
}
這種方法只能提高一定的安全性但是不能完全避免,人家只要反匯編你的so就能看到這兩個值,你要再加強安全性就只能在jni層進行加密解密等操作了,總之就是不要直接返回字符串。
之後你在Bmob後台就能看到數據
Bmob為懶人提供了很好的後端解決方案,我們幾乎不用寫一句代碼,就可以搭建一個強大的後台服務。
Android aapt自動打包工具概念在Android.mk中有LOCAL_AAPT_FLAGS配置項,在gradle中也有aaptOptions,那麼aapt到底是干
(一)前言今天我們一起來看一下進度加載條ProgressBarAndroid控件的講解與基本使用。剛創建的React Native技術交流群(282693535),歡迎各
前言:在日常的Android開發中會經常和控件打交道,有時Android提供的控件未必能滿足業務的需求,這個時候就需要我們實現自定義一些控件,今天先大致了解一下自定義控件
前言Universal-Image-Loader,android-Volley,Picasso、Fresco和Glide五大Android開源組件加載網絡圖片比較。在An