网上很多文章都说android仅支持BKS格式的证书 ,要使用keytool装换CRT成为BKS格式,按照步骤试了一下,乱七八糟的,也还是没有成功,最后还是找到靠谱的方法,Android支持CRT!Android支持CRT!Android支持CRT!
下面就按照步骤做,不出意外的话,会成功的。至于CRT哪来的,去问问搞服务器的搭档。
代码是从项目中摘出来的,可能有些地方会报错
最关键的是 MySocketFactory.class 和 MQTTManager.class 两个类
其他的代码可以根据自己的实际情况调用
1.引入依赖 我用的是paho的MQTT
implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0'
implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1'
2.在AndroidManifest.xml文件注册service ,如果没有用Service管理连接只要下面一行,否则还添加自己的Service,基础知识,不解释
<service android:name="org.eclipse.paho.android.service.MqttService" />
3.创建 MySocketFactory.class
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
public class MySocketFactory extends SSLSocketFactory {
private SSLSocketFactory factory;
public static class SocketFactoryOptions {
private InputStream caCrtInputStream;
private InputStream caClientP12InputStream;
private String caClientP12Password;
public SocketFactoryOptions withCaInputStream(InputStream stream) {
this.caCrtInputStream = stream;
return this;
}
public SocketFactoryOptions withClientP12InputStream(InputStream stream) {
this.caClientP12InputStream = stream;
return this;
}
public SocketFactoryOptions withClientP12Password(String password) {
this.caClientP12Password = password;
return this;
}
public boolean hasCaCrt() {
return caCrtInputStream != null;
}
public boolean hasClientP12Crt() {
return caClientP12Password != null;
}
public InputStream getCaCrtInputStream() {
return caCrtInputStream;
}
public InputStream getCaClientP12InputStream() {
return caClientP12InputStream;
}
public String getCaClientP12Password() {
return caClientP12Password;
}
public boolean hasClientP12Password() {
return (caClientP12Password != null) && !caClientP12Password.equals("");
}
}
public MySocketFactory() throws CertificateException, KeyStoreException, NoSuchAlgorithmException, IOException, KeyManagementException, CertificateException, UnrecoverableKeyException {
this(new SocketFactoryOptions());
}
private TrustManagerFactory tmf;
public MySocketFactory(SocketFactoryOptions options) throws KeyStoreException, NoSuchAlgorithmException, IOException, KeyManagementException, CertificateException, UnrecoverableKeyException {
Log.v(this.toString(), "initializing CustomSocketFactory");
tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
if(options.hasCaCrt()) {
Log.v(this.toString(), "MQTT_CONNECTION_OPTIONS.hasCaCrt(): true");
KeyStore caKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
caKeyStore.load(null, null);
CertificateFactory caCF = CertificateFactory.getInstance("X.509");
X509Certificate ca = (X509Certificate) caCF.generateCertificate(options.getCaCrtInputStream());
String alias = ca.getSubjectX500Principal().getName();
// Set propper alias name
caKeyStore.setCertificateEntry(alias, ca);
tmf.init(caKeyStore);
Enumeration<String> aliasesCA = caKeyStore.aliases();
for (; aliasesCA.hasMoreElements(); ) {
String o = aliasesCA.nextElement();
}
} else {
// Timber.v("CA sideload: false, using system keystore");
KeyStore keyStore = KeyStore.getInstance("AndroidCAStore");
keyStore.load(null);
tmf.init(keyStore);
}
if (options.hasClientP12Crt()) {
Log.v(this.toString(), "MQTT_CONNECTION_OPTIONS.hasClientP12Crt(): true");
KeyStore clientKeyStore = KeyStore.getInstance("PKCS12");
clientKeyStore.load(options.getCaClientP12InputStream(), options.hasClientP12Password() ? options.getCaClientP12Password().toCharArray() : new char[0]);
kmf.init(clientKeyStore, options.hasClientP12Password() ? options.getCaClientP12Password().toCharArray() : new char[0]);
Log.v(this.toString(), "Client .p12 Keystore content: ");
Enumeration<String> aliasesClientCert = clientKeyStore.aliases();
for (; aliasesClientCert.hasMoreElements(); ) {
String o = aliasesClientCert.nextElement();
// Timber.v("Alias: %s", o);
}
} else {
Log.v(this.toString(), "Client .p12 sideload: false, using null CLIENT cert");
kmf.init(null,null);
}
// Create an SSLContext that uses our TrustManager
SSLContext context = SSLContext.getInstance("TLSv1.2");
context.init(kmf.getKeyManagers(), getTrustManagers(), null);
this.factory= getSSLSocketFactory();
}
public TrustManager[] getTrustManagers() {
return tmf.getTrustManagers();
}
@Override
public String[] getDefaultCipherSuites() {
return this.factory.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return this.factory.getSupportedCipherSuites();
}
@Override
public Socket createSocket() throws IOException {
SSLSocket r = (SSLSocket)this.factory.createSocket();
r.setEnabledProtocols(new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"});
return r;
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
SSLSocket r = (SSLSocket)this.factory.createSocket(s, host, port, autoClose);
r.setEnabledProtocols(new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"});
return r;
}
@Override
public Socket createSocket(String host, int port) throws IOException {
SSLSocket r = (SSLSocket)this.factory.createSocket(host, port);
r.setEnabledProtocols(new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"});
return r;
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
SSLSocket r = (SSLSocket)this.factory.createSocket(host, port, localHost, localPort);
r.setEnabledProtocols(new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"});
return r;
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
SSLSocket r = (SSLSocket)this.factory.createSocket(host, port);
r.setEnabledProtocols(new String[]{ "TLSv1", "TLSv1.1", "TLSv1.2"});
return r;
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
SSLSocket r = (SSLSocket)this.factory.createSocket(address, port, localAddress,localPort);
r.setEnabledProtocols(new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"});
return r;
}
private static SSLSocketFactory getSSLSocketFactory() {
try {
// Create a trust manager that does not validate certificate chains
final TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[]{};
}
}
};
// Install the all-trusting trust manager
final SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
// Create an ssl socket factory with our all-trusting manager
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
return sslSocketFactory;
} catch (KeyManagementException | NoSuchAlgorithmException e) {
return null;
}
}
}
4.创建 MQTTManager 用于管理MQTT的 连接 订阅 发布
先把自己的crt证书放到raw文件夹下
import android.content.Context;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttPersistenceException;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
public class MQTTManager {
private static final String TAG = "MQTTManager";
//换上你的主机地址
public static final String SERVER_HOST = "ssl://XXXXXXXXXXX:8883";
//设置的 clientid 可以自己生成
private String clientid = "xxxxxxx";
private static MQTTManager mqttManager=null;
private MqttClient client;
private MqttConnectOptions options;
private Context context;
private MQTTManager(Context context){
this.context=context;
}
/**
* 获取一个MQTTManager单例
* @param
* @return 返回一个MQTTManager的实例对象
*/
public static MQTTManager getInstance(Context context) {
LogUtils.d("mqttManager="+mqttManager);
if (mqttManager==null) {
mqttManager=new MQTTManager(context);
synchronized (Object.class) {
LogUtils.d("synchronized mqttManager="+mqttManager);
if (mqttManager!=null) {
return mqttManager;
}
}
}else {
LogUtils.d("else mqttManager="+mqttManager);
return mqttManager;
}
return null;
}
/**
* 连接服务器
*/
public void connect(){
LogUtils.d("开始连接MQtt");
try {
// host为主机名,clientid即连接MQTT的客户端ID,一般以唯一标识符表示,MemoryPersistence设置clientid的保存形式,默认为以内存保存
if(client==null){
client = new MqttClient(SERVER_HOST, clientid, new MemoryPersistence());
}
// MQTT的连接设置
options = new MqttConnectOptions();
// 设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录,这里设置为true表示每次连接到服务器都以新的身份连接
options.setCleanSession(false);
// 设置连接的用户名
options.setUserName(你的用户名);
// 设置连接的密码
options.setPassword(你的密码.toCharArray());
// 设置超时时间 单位为秒
options.setConnectionTimeout(30);
// 设置会话心跳时间 单位为秒 服务器会每隔1.5*20秒的时间向客户端发送个消息判断客户端是否在线,但这个方法并没有重连的机制
options.setKeepAliveInterval(30);
options.setAutomaticReconnect(true);
if (SERVER_HOST.contains("ssl")) {
MySocketFactory.SocketFactoryOptions socketFactoryOptions = new MySocketFactory.SocketFactoryOptions();
try {
socketFactoryOptions.withCaInputStream(getResources().openRawResource(R.raw.ca));
options.setSocketFactory(new MySocketFactory(socketFactoryOptions));
} catch (IOException | NoSuchAlgorithmException | KeyStoreException | CertificateException | KeyManagementException | UnrecoverableKeyException e) {
e.printStackTrace();
}
}
// 设置回调
// MqttTopic topic = client.getTopic(TOPIC);
//setWill方法,如果项目中需要知道客户端是否掉线可以调用该方法。设置最终端口的通知消息
// options.setWill(topic, "close".getBytes(), 2, true);
client.setCallback(new PushCallback());
client.connect(options);
LogUtils.d("ClientId="+client.getClientId());
} catch (MqttException e) {
LogUtils.d( "connect: " + e );
e.printStackTrace();
}
}
/**
* 订阅消息
* @param topic 订阅消息的主题
*/
public void subscribeMsg(String topic,int qos){
if (client!=null) {
int[] Qos = {qos};
String[] topic1 = {topic};
try {
client.subscribe(topic1, Qos);
LogUtils.d("开始订阅topic= "+topic);
} catch (MqttException e) {
e.printStackTrace();
}
}
}
public void subscribeMsgArray(String topic[],int qos[]){
if (client!=null) {
try {
client.subscribe(topic, qos);
LogUtils.d("开始订阅topic= "+topic);
} catch (MqttException e) {
e.printStackTrace();
}
}
}
/**
* 发布消息
* @param topic 发布消息主题
* @param msg 消息体
* @param isRetained 是否为保留消息
*/
public void publish(String topic,String msg,boolean isRetained,int qos) {
try {
if (client!=null) {
MqttMessage message = new MqttMessage();
message.setQos(qos);
message.setRetained(isRetained);
message.setPayload(msg.getBytes());
client.publish(topic, message);
LogUtils.d("topic="+topic+"--msg="+msg+"--isRetained"+isRetained);
}
} catch (MqttPersistenceException e) {
e.printStackTrace();
} catch (MqttException e) {
e.printStackTrace();
}
}
public void unsubcribe(String topic[]){
if(client!=null){
try {
client.unsubscribe(topic);
} catch (MqttException e) {
e.printStackTrace();
}
}
}
/**
* 发布和订阅消息的回调
*
*/
public class PushCallback implements MqttCallbackExtended {
@Override
public void connectionLost(Throwable cause) {
LogUtils.d("断线了 ");
}
/**
* 发布消息的回调
*/
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
//publish后会执行到这里
LogUtils.d("发布消息成功的回调 "+token.isComplete());
}
/**
* 接收消息的回调方法
*/
@Override
public void messageArrived(final String topicName, final MqttMessage message)
throws Exception {
//subscribe后得到的消息会执行到这里面
LogUtils.d("接收TOPIC息==" + topicName);
LogUtils.d("接收消息==" + new String(message.getPayload()));
MyMqttMessage myMqttMessage=new MyMqttMessage();
myMqttMessage.setTopic(topicName);
myMqttMessage.setMsgContent(new String(message.getPayload()));
EventBus.getDefault().post(myMqttMessage);
}
@Override
public void connectComplete(boolean reconnect, String serverURI) {
if(reconnect){
LogUtils.d("已重连");
}
}
}
/**
* 断开链接
*/
public void disconnect(){
if (client!=null&&client.isConnected()) {
try {
client.disconnect();
mqttManager=null;
} catch (MqttException e) {
e.printStackTrace();
}
}
}
/**
* 释放资源
*/
public void release(){
if (mqttManager!=null) {
mqttManager=null;
}
}
/**
* 判断服务是否连接
* @return
*/
public boolean isConnected(){
if (client!=null) {
return client.isConnected();
}
return false;
}
}
5.用到的 ResourcesUtils一个小工具类 自己可以修改一下
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.support.annotation.StringRes;
import android.text.TextUtils;
import android.view.View;
import java.text.MessageFormat;
public class ResourcesUtils {
/**
* 获取Resources
* @return 资源Resources
*/
public static Resources getResources() {
return MyApplication.getInstance().getResources();
}
/**
* 获取strings.xml资源文件字符串
*
* @param id 资源文件id
* @return 资源文件对应字符串
*/
public static String getString(int id) {
return getResources().getString(id);
}
/**
* 获取strings.xml资源文件字符串数组
*
* @param id 资源文件id
* @return 资源文件对应字符串数组
*/
public static String[] getStringArray(int id) {
return getResources().getStringArray(id);
}
/**
* 获取drawable资源文件图片
*
* @param id 资源文件id
* @return 资源文件对应图片
*/
public static Drawable getDrawable(int id) {
return getResources().getDrawable(id);
}
/**
* 获取colors.xml资源文件颜色
*
* @param id 资源文件id
* @return 资源文件对应颜色值
*/
public static int getColor(int id) {
return getResources().getColor(id);
}
/**
* 获取颜色的状态选择器
*
* @param id 资源文件id
* @return 资源文件对应颜色状态
*/
public static ColorStateList getColorStateList(int id) {
return getResources().getColorStateList(id);
}
/**
* 获取dimens资源文件中具体像素值
*
* @param id 资源文件id
* @return 资源文件对应像素值
*/
public static int getDimen(int id) {
return getResources().getDimensionPixelSize(id);// 返回具体像素值
}
/**
* 加载布局文件
*
* @param id 布局文件id
* @return 布局view
*/
public static View inflate(int id) {
return View.inflate(MyApplication.getInstance(), id, null);
}
/**
* 获取字符串资源并格式化输出
*
* @param resId
* @param params
* @return
*/
public static String getString(@StringRes int resId, Object... params) {
String _str = MyApplication.getInstance().getString(resId);
if (TextUtils.isEmpty(_str)) {
return "";
}
if (params != null && params.length > 0) {
for (int i = 0; i < params.length; i++) {
params[i] += "";
}
try {
_str = _str.contains("{0}") ? MessageFormat.format(_str, params) : String.format(_str, params);
} catch (Exception e) {
e.printStackTrace();
}
}
return _str.replace("\u2028","");
}
}
6.准备工作基本完成,下面就是开始调用了,至于怎么用,可以根据自己的实际情况,我是在一个Service中开始建立连接的,在Activity或者Fragment中判断一下是否是连接状态,然后进行订阅,配合EventBus会很方便
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class MyMqttService extends Service {
@Override
public IBinder onBind(Intent intent) {
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
LogUtils.d("MqttService onStartCommand executed");
MQTTManager instance=MQTTManager.getInstance(getApplicationContext());
if(!instance.isConnected()){
LogUtils.d("未连接,开始连接");
instance.connect();
}else {
LogUtils.d("之前已连接,无需再连");
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
MQTTManager.getInstance(getApplicationContext()).disconnect();
LogUtils.d("MqttService onDestroy executed");
}
}
在主Activity中启动 或者自己认为需要的时候启动,我是在MainActivity中启动的
Intent intent = new Intent(this, MyMqttService.class);
startService(intent);
在需要订阅的Activity中订阅 比如在自己的 init() 方法中
String topics[] = {"xxxxxxx", "xxxxxxxxx"};
int qoss[] = {1, 1};
MQTTManager instance = MQTTManager.getInstance(getApplicationContext());
if (instance.isConnected()) {
instance.subscribeMsgArray(topics, qoss);
}
在页面中获取消息
//结合EventBus获取消息
@Subscribe(threadMode = ThreadMode.MAIN)
public void getMyMqttMessge(MyMqttMessage myMqttMessage) {
String topic = myMqttMessage.getTopic();
String message = myMqttMessage.getMsgContent();
//做你要做的事
}
其中MyMqttMessage 是自己定义的实体类 我只需要两个属性
public class MyMqttMessage {
private String topic;
private String msgContent;
public String getTopic() {
return topic;
}
public void setTopic(String topic) {
this.topic = topic;
}
public String getMsgContent() {
return msgContent;
}
public void setMsgContent(String msgContent) {
this.msgContent = msgContent;
}
}
退出的时候可以取消订阅
@Override
protected void onDestroy() {
if (dialog != null) {
dialog.dismiss();
}
String topics[] = {"xxxxxxx", "xxxxxxxx"};
MQTTManager.getInstance(getApplicationContext()).unsubcribe(topics);
EventBus.getDefault().unregister(this);
super.onDestroy();
}
大概就是这样,把握两个关键类,其他的自己控制,如有遗漏,可以留言。