tinode客户端安卓版编译手账

news2025/5/21 8:24:52

前一阵子我自己架设了一个tinode的IM服务器,

web直接可以运行

但是安卓版本的一直报错,

具体信息为:

No subjectAltNames on the certificate match

问了作者,作者竟然把我的问题直接删除了,还是自己调试代码吧。毕竟源码面前,了无秘密;

一、代码地址

GitHub - tinode/tindroid: Tinode chat client application for Android

我从release部分下载了0.20.9版本源码

二、更改源码配置

1)根目录下的build.gradle有2处需要更改,主要是版本信息,非git版本无从提取,随便设置一下

static def gitVersionCode() {
    // If you are not compiling in a git directory and getting an error like
    // [A problem occurred evaluating root project 'master'. For input string: ""]
    // then just return your manually assigned error code like this:
    //  return 12345
    def process = "git rev-list --count HEAD".execute()
    return 12345
}

// Use current git tag as a version name.
// For example, if the git tag is 'v0.20.0-rc1' then the version name will be '0.20.0-rc1'.
static def gitVersionName() {
    // If you are not compiling in a git directory, you should manually assign version name:
    //  return "MyVersionName"
    def process = "git describe --tags".execute()
    // Remove trailing CR and remove leading 'v' as in 'v1.2.3'
    return "1.2.3"
}

2)app下面的build.gradle有3处需要修改

2.1)程序使用googleService,需要去官网注册一下相关的资料,自己注册一个新的应用,下载得到google-services.json,这个文件放置于app目录;

2.2)google-services.json中我们注册了一个应用的名字,这文件中有个package_name替换原来的应用ID,否则编译不过

applicationId "com.birdschat.cn"

2.3)创建证书,文件放置于源码同级目录,比如我的:

../robinkeys/key.keystore

在根目录下添加一个配置文件,叫keystore.properties,内容大概如下:

keystoreFile=../robin_keys/key.keystore
keystoreAlias=key.keystore
keystorePassword=123456
keyPassword=123456

并根据自己配置文件中的参数名,设置一下build.gradle:

signingConfigs {
        release {
            storeFile file(keystoreProperties['keystoreFile'])
            storePassword keystoreProperties['keystorePassword']
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
        }
    }

这样应该就可以编译了!!

3)取消客户端WebSocket 的SSL双向认证

但是运行后,设置了自己的服务器,以及使用加密模式,无法注册或者登录,

主要是我们的证书需要有域名,并且是申请来的,也就是有CA认证的,而不是自己生成的,不然无法实现双向验证,这主要是为了防止中间人攻击;

但是我们往往就是自己内部试用,不需要这么麻烦,

需要对SDK部分代码进行更该,参考:java websocket及忽略证书_nell_lee的博客-CSDN博客_websocket 忽略证书

更改后的代码如下:Connection.java 全文

package co.tinode.tinodesdk;

import android.util.Log;

import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft_6455;
import org.java_websocket.handshake.ServerHandshake;

import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.*;
import java.net.Socket;
import java.net.URI;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

/**
 * A thinly wrapped websocket connection.
 */
public class Connection extends WebSocketClient {
    private static final String TAG = "Connection";

    private static final int CONNECTION_TIMEOUT = 3000; // in milliseconds

    // Connection states
    // TODO: consider extending ReadyState
    private enum State {
        // Created. No attempts were made to reconnect.
        NEW,
        // Created, in process of creating or restoring connection.
        CONNECTING,
        // Connected.
        CONNECTED,
        // Disconnected. A thread is waiting to reconnect again.
        WAITING_TO_RECONNECT,
        // Disconnected. Not waiting to reconnect.
        CLOSED
    }

    private final WsListener mListener;

    // Connection status
    private State mStatus;

    // If connection should try to reconnect automatically.
    private boolean mAutoreconnect;

    // This connection is a background connection.
    // The value is reset when the connection is successful.
    private boolean mBackground;

    // Exponential backoff/reconnecting
    final private ExpBackoff backoff = new ExpBackoff();

    @SuppressWarnings("WeakerAccess")
    protected Connection(URI endpoint, String apikey, WsListener listener) {
        super(normalizeEndpoint(endpoint), new Draft_6455(), wrapApiKey(apikey), CONNECTION_TIMEOUT);
        setReuseAddr(true);

        mListener = listener;
        mStatus = State.NEW;
        mAutoreconnect = false;
        mBackground = false;
        if(endpoint.toString().contains("wss://")){
            trustAllHosts(this);

        }
    }

    // robin add here
    /**
     *忽略证书
     *@paramclient
     */
    void trustAllHosts(Connection client) {
        TrustManager[] trustAllCerts = new TrustManager[]{new X509ExtendedTrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] x509Certificates, String s, Socket socket) throws CertificateException {

            }

            @Override
            public void checkServerTrusted(X509Certificate[] x509Certificates, String s, Socket socket) throws CertificateException {

            }

            @Override
            public void checkClientTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) throws CertificateException {

            }

            @Override
            public void checkServerTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) throws CertificateException {

            }

            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
//                return new java.security.cert.X509Certificate[]{};
//                System.out.println("getAcceptedIssuers");
                return null;
            }

            @Override
            public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                System.out.println("checkClientTrusted");
            }

            @Override
            public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                System.out.println("checkServerTrusted");
            }
        }};

        try {

            SSLContext ssl = SSLContext.getInstance("SSL");
            ssl.init(null, trustAllCerts, new java.security.SecureRandom());

            SSLSocketFactory socketFactory = ssl.getSocketFactory();
            this.setSocketFactory(socketFactory);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

//————————————————
//        版权声明:本文为CSDN博主「nell_lee」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
//        原文链接:https://blog.csdn.net/qq_33529102/article/details/115763483

    private static Map<String,String> wrapApiKey(String apikey) {
        Map<String, String> headers = new HashMap<>();
        headers.put("X-Tinode-APIKey",apikey);
        return headers;
    }

    private static URI normalizeEndpoint(URI endpoint) {
        String path = endpoint.getPath();
        if (path.equals("")) {
            path = "/";
        } else if (path.lastIndexOf("/") != path.length() - 1) {
            path += "/";
        }
        path += "channels"; // ws://www.example.com:12345/v0/channels

        String scheme = endpoint.getScheme();
        // Normalize scheme to ws or wss.
        scheme = ("wss".equals(scheme) || "https".equals(scheme)) ? "wss" : "ws";

        int port = endpoint.getPort();
        if (port < 0) {
            port = "wss".equals(scheme) ? 443 : 80;
        }
        try {
            endpoint = new URI(scheme,
                    endpoint.getUserInfo(),
                    endpoint.getHost(),
                    port,
                    path,
                    endpoint.getQuery(),
                    endpoint.getFragment());
        } catch (URISyntaxException e) {
            Log.w(TAG, "Invalid endpoint URI", e);
        }

        return endpoint;
    }

    private void connectSocket(final boolean reconnect) {
        new Thread(() -> {
            try {
                if (reconnect) {
                    reconnectBlocking();
                } else {
                    connectBlocking(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS);
                }

                if ("wss".equals(uri.getScheme())) {
                    // SNI: Verify server host name.
                    SSLSocket ss = (SSLSocket) getSocket();
                    ss.setWantClientAuth(false);
                    SSLSession sess = ss.getSession();
                    String hostName = uri.getHost();
                    // robin add
//                    if (!HttpsURLConnection.getDefaultHostnameVerifier().verify(hostName, sess)) {
//                        close();
//                        throw new SSLHandshakeException("SNI verification failed. Expected: '" + uri.getHost() +
//                                "', actual: '" + sess.getPeerPrincipal() + "'");
//                   }
                }
            } catch (Exception ex) {
                Log.i(TAG, "WS connection failed", ex);
                if (mListener != null) {
                    mListener.onError(Connection.this, ex);
                }
            }
        }).start();
    }

    /**
     * Establish a connection with the server. It opens or reopens a websocket in a separate
     * thread.
     *
     * This is a non-blocking call.
     *
     * @param autoReconnect if connection is dropped, reconnect automatically
     */
    @SuppressWarnings("WeakerAccess")
    synchronized public void connect(boolean autoReconnect, boolean background) {
        mAutoreconnect = autoReconnect;
        mBackground = background;

        switch (mStatus) {
            case CONNECTED:
            case CONNECTING:
                // Already connected or in process of connecting: do nothing.
                break;
            case WAITING_TO_RECONNECT:
                backoff.wakeUp();
                break;
            case NEW:
                mStatus = State.CONNECTING;
                connectSocket(false);
                break;
            case CLOSED:
                mStatus = State.CONNECTING;
                connectSocket(true);
                break;
            // exhaustive, no default:
        }
    }

    /**
     * Gracefully close websocket connection. The socket will attempt
     * to send a frame to the server.
     *
     * The call is idempotent: if connection is already closed it does nothing.
     */
    @SuppressWarnings("WeakerAccess")
    synchronized public void disconnect() {
        boolean wakeUp = mAutoreconnect;
        mAutoreconnect = false;

        // Actually close the socket (non-blocking).
        close();

        if (wakeUp) {
            // Make sure we are not waiting to reconnect
            backoff.wakeUp();
        }
    }

    /**
     * Check if the socket is OPEN.
     *
     * @return true if the socket is OPEN, false otherwise;
     */
    @SuppressWarnings("WeakerAccess")
    public boolean isConnected() {
        return isOpen();
    }

    /**
     * Check if the socket is waiting to reconnect.
     *
     * @return true if the socket is OPEN, false otherwise;
     */
    @SuppressWarnings("WeakerAccess")
    public boolean isWaitingToReconnect() {
        return mStatus == State.WAITING_TO_RECONNECT;
    }
    /**
     * Reset exponential backoff counter to zero.
     * If autoreconnect is true and WsListener is provided, then WsListener.onConnect must call
     * this method.
     */
    @SuppressWarnings("WeakerAccess")
    public void backoffReset() {
        backoff.reset();
    }

    @Override
    public void onOpen(ServerHandshake handshakeData) {
        synchronized (this) {
            mStatus = State.CONNECTED;
        }

        if (mListener != null) {
            boolean bkg = mBackground;
            mBackground = false;
            mListener.onConnect(this, bkg);
        } else {
            backoff.reset();
        }
    }

    @Override
    public void onMessage(String message) {
        if (mListener != null) {
            mListener.onMessage(this, message);
        }
    }

    @Override
    public void onMessage(ByteBuffer blob) {
        // do nothing, server does not send binary frames
        Log.w(TAG, "binary message received (should not happen)");
    }

    @Override
    public void onClose(int code, String reason, boolean remote) {
        // Avoid infinite recursion
        synchronized (this) {
            if (mStatus == State.WAITING_TO_RECONNECT) {
                return;
            } else if (mAutoreconnect) {
                mStatus = State.WAITING_TO_RECONNECT;
            } else {
                mStatus = State.CLOSED;
            }
        }

        if (mListener != null) {
            mListener.onDisconnect(this, remote, code, reason);
        }

        if (mAutoreconnect) {
            new Thread(() -> {
                while (mStatus == State.WAITING_TO_RECONNECT) {
                    backoff.doSleep();

                    synchronized (Connection.this) {
                        // Check if we no longer need to connect.
                        if (mStatus != State.WAITING_TO_RECONNECT) {
                            break;
                        }
                        mStatus = State.CONNECTING;
                    }
                    connectSocket(true);
                }
            }).start();
        }
    }

    @Override
    public void onError(Exception ex) {
        Log.w(TAG, "Websocket error", ex);

        if (mListener != null) {
            mListener.onError(this, ex);
        }
    }

    interface WsListener {
        default void onConnect(Connection conn, boolean background) {
        }

        default void onMessage(Connection conn, String message) {
        }

        default void onDisconnect(Connection conn, boolean byServer, int code, String reason) {
        }

        default void onError(Connection conn, Exception err) {
        }
    }
}

这样就OK了;

4)设置服务器默认参数

将服务器的链接参数预先设置好为我们需要的:

4.1) 地址与端口:全文搜索“:6060”字样,在资源文件res/strings.xml中更改:

<string name="emulator_host_name" translatable="false">119.0.0.1:6060</string>

同时,将build.gradle的相关位置做更改,自动生成相关的资源文件

buildTypes {
        debug {
            resValue "string", "default_host_name", '"119.0.0.0:6060"'
        }
        release {
            resValue "string", "default_host_name", '"api.tinode.co"'
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.release
        }

在同样在资源中更改为自己的地址和端口;

4.2)使用https,更改TindroidApp.java

将返回的默认的参数设置为true

 public static boolean getDefaultTLS() {
        //return !isEmulator();
        return true;
    }

编译好了就可以用了!

备注:编译好的apk https://download.csdn.net/download/robinfoxnan/87300700

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/93547.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

两年CRUD,没料到我这渣二本,备战两个月面试阿里,居然侥幸拿下P6的offer

对于很多没有学历优势的人来说&#xff0c;面试大厂是非常困难的&#xff0c;这对我而言&#xff0c;也是一样&#xff0c;出身于二本&#xff0c;原本以为就三点一线的生活度过一生&#xff0c;直到生活上的变故&#xff0c;才让我有了新的想法和目标&#xff0c;因此我这个二…

fl studio21版本如何更新FL最新版升级教程

2022年12月7日晚&#xff0c;全球知名的音乐创作软件&#xff0c;FL Studio正式推出最新21版&#xff0c;为原创音乐人提供更好用的DAW&#xff08;数字音乐工作站&#xff09;工具。 FL Studio中文已上线21新版 FL Studio国人也叫它水果编曲软件&#xff0c;是一款有着20多年…

Java——布隆过滤器

在上一篇博客中讲到位图是用来判定一个正整数是否存在的。对于一个负数&#xff0c;我们可以统一规定让他们加上一个数&#xff0c;变成正数&#xff0c;然后用位图的方式存储。但是对于字符串&#xff0c;我们就没办法存储了。因此发明了布隆过滤器 概念 对于网络上很多需要…

计算机毕设Python+Vue校园新闻发布系统(程序+LW+部署)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【从零开始学习深度学习】26.卷积神经网络之AlexNet模型介绍及其Pytorch实现【含完整代码】

目录1. AlexNet模型1.1 AlexNet与LeNet的区别1.2 简化的AlexNet实现1.3 各层输出形状详解2. 读取数据3. 模型训练4. 总结上一篇文章中我们了解到神经网络可以直接基于图像的原始像素进行分类&#xff0c;这种称为端到端&#xff08;end-to-end&#xff09;的方法可以节省很多中…

腾讯实践:从推荐模型的基础特点看大规模推荐类深度学习系统的设计

省时查报告-专业、及时、全面的行研报告库省时查方案-专业、及时、全面的营销策划方案库【免费下载】2022年11月份热门报告盘点腾讯新闻信息流推荐技术实践.pdf推荐系统在腾讯游戏中的应用实践.pdf基于深度学习的个性化推荐系统实时化改造与升级.pdf推荐技术在vivo互联网商业化…

Zabbix与乐维监控对比分析(四)——告警管理篇

在前面发布的Zabbix与乐维监控对比分析文章中&#xff0c;我们评析了二者在架构与性能、Agent管理、自动发现、权限管理、对象管理等方面的差异。接下来让我们一起看看二者在告警管理方面的差异。 告警管理是所有IT监控平台最重磅的功能之一&#xff0c;也是评判一个监控平台好…

cad2010怎么隐藏标注尺寸,cad2007怎么隐藏标注尺寸

1、CAD2007怎么隐藏所有的标注尺寸? 1、在"查看器"菜单面板中隐藏的工具有"线宽"、"测量"、"文本"三种工具,可用于隐藏或显示CAD图中的线条宽度、测量尺寸和文本内容。 2、点击选择"测量"工具,将尺寸内容的CAD图隐藏起来。…

JavaScript-BOM

&#x1f496;通过看视频教程和红宝书浅浅的写下一些关于BOM的笔记 红宝书知识系统全面&#xff0c;精炼。大概是因为太干货了&#xff0c;涉及的知识点太多&#xff0c;所以我选择看着简单的视频教程&#xff0c;同时打开红宝书。笔记的内容以红宝书为基准。 window对象 BOM的…

艾美捷内皮细胞生长添加剂解决方案

内皮细胞生长添加剂是一种培养基补充物&#xff0c;旨在体外优化人原代微血管内皮细胞的生长。这是一种无菌浓缩&#xff08;100X&#xff09;溶液&#xff0c;含有培养正常人微血管内皮细胞所需的生长因子、激素和蛋白质。该补充剂的配制&#xff08;定量和定性&#xff09;旨…

Linux下的多线程编程

线程&#xff08;thread&#xff09;技术早在60年代就被提出&#xff0c;但真正应用多线程到操作系统中去&#xff0c;是在80年代中期&#xff0c;solaris是这方面的佼佼者。传统的Unix也支持线程的概念&#xff0c;但是在一个进程&#xff08;process&#xff09;中只允许有一…

基于java+springmvc+mybatis+vue+mysql的教资考前指导系统

项目介绍 对于本教资考前指导系统的设计来说&#xff0c;系统开发主要是采用java语言技术&#xff0c;后端采用springboot框架&#xff0c;前端采用vue技术&#xff0c;在整个系统的设计中应用MySQL数据库来完成数据存储&#xff0c;具体根据教资考前指导系统的现状来进行开发…

Metasploit Framework简介

没有框架渗透测试者的困扰 ● 需要掌握数百个工具软件&#xff0c;上千个命令参数&#xff0c;实在记不住 ● 新出现的漏洞PoC/EXP有不同的运行环境要求&#xff0c;准备工作繁琐 ● 大部分时间都在学习不同工具的使用习惯&#xff0c;如果能同意就好了 ● Metasploit能解决以上…

pyinstaller遇到的问题

我到底看看能有多少问题&#xff0c;真的烦死我了&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#…

[附源码]Python计算机毕业设计公交电子站牌管理系统软件Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

HAProxy走私漏洞

HAProxy走私漏洞 JFrog安全研究团队发布了一个HAProxy的严重漏洞的信息。HAProxy是一个使用C语言编写的自由及开放源代码软件&#xff0c;其提供高可用性、负载均衡&#xff0c;以及基于TCP和HTTP的应用程序代理。 参考文章&#xff1a;https://jfrog.com/blog/critical-vulne…

虚拟生产、交付、体验,元宇宙技术对供应链的深远影响#低碳生活

#背景自新冠肺炎疫情爆发以来&#xff0c;元宇宙增长速度加快&#xff0c;也推动了对远程工作工具的前所未有的需求。目前全球元宇宙市场估值高于 1000 亿美元&#xff0c;据预计&#xff0c;到 2029 年&#xff0c;预计年均增长 47 %&#xff0c;达到 15270 亿美元。#改造供应…

【DevOps实战系列】第三章:详解Maven仓库及环境搭建

个人亲自录制全套DevOps系列实战教程 &#xff1a;手把手教你玩转DevOps全栈技术 Maven私有仓库&#xff0c;就不多说了&#xff0c;我们这里选用最新的Nexus3的3.17版本&#xff0c;平时公司使用的都是Nexus 2.x,新的3.x版本做了很多的升级&#xff0c;包括存储方式等&#xf…

self.eval_net.forward(state)和self.eval_net.forward(state)区别

在根据状态获取一个动作&#xff1a;self.eval_net.forward(state) 在更新网络时&#xff1a;self.eval_net(state) 这2个有什么区别呀&#xff0c;为啥不都是forward 我打印了一下返回值的时候&#xff0c;我感觉格式是一样的 action_value tensor([[0.7177, 0.7369, 0.7124,…

amfori BSCI行为守则(2021)最新版-2023年生效

【amfori BSCI行为守则(2021)最新版-2023年生效】 amfori BSCI 商界社会责任倡议&#xff08;Business Social Compliance Initiative, BSCI&#xff09;是一套国际通用的企业社会责任管理工具和验厂标准。amfori BSCI 颁布行为守则&#xff08;Code of Conduct&#xff09;&am…