热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

SpringBoot集成开源IM框架MobileIMSDK,实现即时通讯IM聊天功能

SpringBoot集成开源IM框架MobileIMSDK,实现即时通讯IM聊天功能-一、前言MobileIMSDK是什么?MobileIMSDK是一套专门为移动端开发的开源I
一、前言

MobileIMSDK 是什么?

MobileIMSDK 是一套专门为移动端开发的开源IM即时通讯框架,超轻量级、高度提炼,一套API优雅支持UDP 、TCP 、WebSocket 三种协议,支持iOS、Android、H5、标准Java平台,服务端基于Netty编写。

工程地址是:

  • 1)Gitee码云地址:https://www.oschina.net/p/mobileimsdk
  • 2)Github托管地址:https://github.com/JackJiang2011/MobileIMSDK

本文将实现:

  • 1)基于springboot 集成 MobileIMSDK;

  • 2)开发IM服务端;

  • 3)开发客户端;

  • 4)实现Java客户端与客户端之间的通信。

* 补充说明: 本文所示Demo源码,请从文末“本文小结”的最后链接中下载!

二、SpringBoot 集成 MobileIMSDK 准备

2.1 MobileIMSDK下载

MobileIMSDK下载地址:

  • 1)国外地址:MobileIMSDK的Github地址(最新版打包下载)

  • 2)国内地址:MobileIMSDK的码云gitee地址(访问速度快!,最新版打包下载)

需要用到的lib包:

  • 1)服务端所需jar包: sdk_binary/Server/

  • 2)客服端所需jar包: sdk_binary/Client_TCP/java/

如下图所示:

2.2 pom.xml中引入相关依赖

由于这里是maven项目,其中一部分jar包可通过maven仓库直接引入,而其余的则通过外部jar包引入方式使用即可~

如下4个需作为外部jar包在pom.xml中引入 :



    com.google.code.gson
    gson
    2.8.5




    com.zhengqing
    MobileIMSDK4j
    system
    ${project.basedir}/src/main/resources/lib/MobileIMSDK4j.jar


    com.zhengqing
    MobileIMSDKServerX_meta
    system
    ${project.basedir}/src/main/resources/lib/MobileIMSDKServerX_meta.jar


    com.zhengqing
    swing-worker-1.2(1.6-)
    system
    ${project.basedir}/src/main/resources/lib/swing-worker-1.2(1.6-).jar


    com.zhengqing
    MobileIMSDKServerX_netty
    system
    ${project.basedir}/src/main/resources/lib/MobileIMSDKServerX_netty.jar



    
    
        org.springframework.boot
        spring-boot-maven-plugin
        
        
            true
        
    

三、开发服务端

3.1 与客服端的所有数据交互事件(实现ServerEventListener类)

public class ServerEventListenerImpl implements ServerEventListener {
    private static Logger logger = LoggerFactory.getLogger(ServerEventListenerImpl.class);

    /**
     * 用户身份验证回调方法定义.
     * 

* 服务端的应用层可在本方法中实现用户登陆验证。 *
* 注意:本回调在一种特殊情况下——即用户实际未退出登陆但再次发起来登陆包时,本回调是不会被调用的! *

* 根据MobileIMSDK的算法实现,本方法中用户验证通过(即方法返回值=0时)后 * ,将立即调用回调方法 {@link #onUserLoginAction_CallBack(int, String, IoSession)}。 * 否则会将验证结果(本方法返回值错误码通过客户端的 ChatBaseEvent.onLoginMessage(int dwUserId, int dwErrorCode) * 方法进行回调)通知客户端)。 * * @param userId 传递过来的准一id,保证唯一就可以通信,可能是登陆用户名、也可能是任意不重复的id等,具体意义由业务层决定 * @param token 用于身份鉴别和合法性检查的token,它可能是登陆密码,也可能是通过前置单点登陆接口拿到的token等,具体意义由业务层决定 * @param extra 额外信息字符串。本字段目前为保留字段,供上层应用自行放置需要的内容 * @param session 此客户端连接对应的 netty “会话” * @return 0 表示登陆验证通过,否则可以返回用户自已定义的错误码,错误码值应为:>=1025的整数 */ @Override public int onVerifyUserCallBack(String userId, String token, String extra, Channel session) { logger.debug("【DEBUG_回调通知】正在调用回调方法:OnVerifyUserCallBack...(extra=" + extra + ")"); return 0; } /** * 用户登录验证成功后的回调方法定义(可理解为上线通知回调). *

* 服务端的应用层通常可在本方法中实现用户上线通知等。 *
* 注意:本回调在一种特殊情况下——即用户实际未退出登陆但再次发起来登陆包时,回调也是一定会被调用。 * * @param userId 传递过来的准一id,保证唯一就可以通信,可能是登陆用户名、也可能是任意不重复的id等,具体意义由业务层决定 * @param extra 额外信息字符串。本字段目前为保留字段,供上层应用自行放置需要的内容。为了丰富应用层处理的手段,在本回调中也把此字段传进来了 * @param session 此客户端连接对应的 netty “会话” */ @Override public void onUserLoginAction_CallBack(String userId, String extra, Channel session) { logger.debug("【IM_回调通知OnUserLoginAction_CallBack】用户:" + userId + " 上线了!"); } /** * 用户退出登录回调方法定义(可理解为下线通知回调)。 *

* 服务端的应用层通常可在本方法中实现用户下线通知等。 * * @param userId 下线的用户user_id * @param obj * @param session 此客户端连接对应的 netty “会话” */ @Override public void onUserLogoutAction_CallBack(String userId, Object obj, Channel session) { logger.debug("【DEBUG_回调通知OnUserLogoutAction_CallBack】用户:" + userId + " 离线了!"); } /** * 通用数据回调方法定义(客户端发给服务端的(即接收user_id="0")). *

* MobileIMSDK在收到客户端向user_id=0(即接收目标是服务器)的情况下通过 * 本方法的回调通知上层。上层通常可在本方法中实现如:添加好友请求等业务实现。 * *

* 【版本兼容性说明】本方法用于替代v3.x中的以下方法:
* public boolean onTransBuffer_CallBack(String userId, String from_user_id * , String dataContent, String fingerPrint, int typeu, Channel session); * * * @param userId 接收方的user_id(本方法接收的是发给服务端的消息,所以此参数的值肯定==0) * @param from_user_id 发送方的user_id * @param dataContent 数据内容(文本形式) * @param session 此客户端连接对应的 netty “会话” * @return true表示本方法已成功处理完成,否则表示未处理成功。此返回值目前框架中并没有特殊意义,仅作保留吧 * @since 4.0 */ @Override public boolean onTransBuffer_C2S_CallBack(Protocal p, Channel session) { // 接收者uid String userId = p.getTo(); // 发送者uid String from_user_id = p.getFrom(); // 消息或指令内容 String dataCOntent= p.getDataContent(); // 消息或指令指纹码(即唯一ID) String fingerPrint = p.getFp(); // 【重要】用户定义的消息或指令协议类型(开发者可据此类型来区分具体的消息或指令) int typeu = p.getTypeu(); logger.debug("【DEBUG_回调通知】[typeu=" + typeu + "]收到了客户端" + from_user_id + "发给服务端的消息:str=" + dataContent); return true; } /** * 通道数据回调函数定义(客户端发给客户端的(即接收方user_id不为“0”的情况)). *

* 注意:本方法当且仅当在数据被服务端成功在线发送出去后被回调调用. *

* 上层通常可在本方法中实现用户聊天信息的收集,以便后期监控分析用户的行为等^_^。 *

* 提示:如果开启消息QoS保证,因重传机制,本回调中的消息理论上有重复的可能,请以参数 #fingerPrint * 作为消息的唯一标识ID进行去重处理。 * *

* 【版本兼容性说明】本方法用于替代v3.x中的以下方法:
* public void onTransBuffer_C2C_CallBack(String userId, String from_user_id * , String dataContent, String fingerPrint, int typeu); * * @param userId 接收方的user_id(本方法接收的是客户端发给客户端的,所以此参数的值肯定>0) * @param from_user_id 发送方的user_id * @param dataContent * @since 4.0 */ @Override public void onTransBuffer_C2C_CallBack(Protocal p) { // 接收者uid String userId = p.getTo(); // 发送者uid String from_user_id = p.getFrom(); // 消息或指令内容 String dataCOntent= p.getDataContent(); // 消息或指令指纹码(即唯一ID) String fingerPrint = p.getFp(); // 【重要】用户定义的消息或指令协议类型(开发者可据此类型来区分具体的消息或指令) int typeu = p.getTypeu(); logger.debug("【DEBUG_回调通知】[typeu=" + typeu + "]收到了客户端" + from_user_id + "发给客户端" + userId + "的消息:str=" + dataContent); } /** * 通用数据实时发送失败后的回调函数定义(客户端发给客户端的(即接收方user_id不为“0”的情况)). *

* 注意:本方法当且仅当在数据被服务端在线发送失败后被回调调用. *

* 此方法存的意义何在?
* 发生此种情况的场景可能是:对方确实不在线(那么此方法里就可以作为离线消息处理了)、 * 或者在发送时判断对方是在线的但服务端在发送时却没有成功(这种情况就可能是通信错误 * 或对方非正常通出但尚未到达会话超时时限)。
应用层在此方法里实现离线消息的处理即可! * *

* 【版本兼容性说明】本方法用于替代v3.x中的以下方法:
* public boolean onTransBuffer_C2C_RealTimeSendFaild_CallBack(String userId * , String from_user_id, String dataContent, String fingerPrint, int typeu); * * * @param userId 接收方的user_id(本方法接收的是客户端发给客户端的,所以此参数的值肯定>0),此id在本方法中不一定保证有意义 * @param from_user_id 发送方的user_id * @param dataContent 消息内容 * @param fingerPrint 该消息对应的指纹(如果该消息有QoS保证机制的话),用于在QoS重要机制下服务端离线存储时防止重复存储哦 * @return true表示应用层已经处理了离线消息(如果该消息有QoS机制,则服务端将代为发送一条伪应答包 * (伪应答仅意味着不是接收方的实时应答,而只是存储到离线DB中,但在发送方看来也算是被对方收到,只是延 * 迟收到而已(离线消息嘛))),否则表示应用层没有处理(如果此消息有QoS机制,则发送方在QoS重传机制超时 * 后报出消息发送失败的提示) * @see #onTransBuffer_C2C_CallBack(Protocal) * @since 4.0 */ @Override public boolean onTransBuffer_C2C_RealTimeSendFaild_CallBack(Protocal p) { // 接收者uid String userId = p.getTo(); // 发送者uid String from_user_id = p.getFrom(); // 消息或指令内容 String dataCOntent= p.getDataContent(); // 消息或指令指纹码(即唯一ID) String fingerPrint = p.getFp(); // 【重要】用户定义的消息或指令协议类型(开发者可据此类型来区分具体的消息或指令) int typeu = p.getTypeu(); logger.debug("【DEBUG_回调通知】[typeu=" + typeu + "]客户端" + from_user_id + "发给客户端" + userId + "的消息:str=" + dataContent + ",因实时发送没有成功,需要上层应用作离线处理哦,否则此消息将被丢弃."); return false; } } 3.2 服务端主动发起消息的QoS回调通知(实现MessageQoSEventListenerS2C类) public class MessageQoSEventS2CListnerImpl implements MessageQoSEventListenerS2C { private static Logger logger = LoggerFactory.getLogger(MessageQoSEventS2CListnerImpl.class); @Override public void messagesLost(ArrayList<;Protocal> lostMessages) { logger.debug("【DEBUG_QoS_S2C事件】收到系统的未实时送达事件通知,当前共有" + lostMessages.size() + "个包QoS保证机制结束,判定为【无法实时送达】!"); } @Override public void messagesBeReceived(String theFingerPrint) { if (theFingerPrint != null) { logger.debug("【DEBUG_QoS_S2C事件】收到对方已收到消息事件的通知,fp=" + theFingerPrint); } } } 3.3 服务端配置 public class ServerLauncherImpl extends ServerLauncher { // 静态类方法:进行一些全局配置设置 static { // 设置MobileIMSDK服务端的网络监听端口 ServerLauncherImpl.PORT = 7901; // 开/关Demog日志的输出 QoS4SendDaemonS2C.getInstance().setDebugable(true); QoS4ReciveDaemonC2S.getInstance().setDebugable(true); ServerLauncher.debug = true; // TODO 与客户端协商一致的心跳敏感模式设置 // ServerToolKits.setSenseMode(SenseMode.MODE_10S); // 关闭与Web端的消息互通桥接器(其实SDK中默认就是false) ServerLauncher.bridgeEnabled = false; // TODO 跨服桥接器MQ的URI(本参数只在ServerLauncher.bridgeEnabled为true时有意义) // BridgeProcessor.IMMQ_URI = "amqp://js:19844713@192.168.31.190"; } // 实例构造方法 public ServerLauncherImpl() throws IOException { super(); } /** * 初始化消息处理事件监听者. */ @Override protected void initListeners() { // ** 设置各种回调事件处理实现类 this.setServerEventListener(new ServerEventListenerImpl()); this.setServerMessageQoSEventListener(new MessageQoSEventS2CListnerImpl()); } } 3.4 服务端启动类

温馨小提示:这里由于小编将服务端和客户端集成在同一个项目中,因此如下配置:

  • SpringBoot的CommandLineRunner接口主要用于实现在服务初始化后,去执行一段代码块逻辑(run方法),这段初始化代码在整个应用生命周期内只会执行一次!

  • @Order(value = 1) :按照一定的顺序去执行,value值越小越先执行

    @Slf4j
    @Component
    @Order(value = 1)
    public class ChatServerRunner implements CommandLineRunner {

    @Override
    public void run(String... strings) throws Exception {
        log.info("================= ↓↓↓↓↓↓ 启动MobileIMSDK服务端 ↓↓↓↓↓↓ =================");
        // 实例化后记得startup哦,单独startup()的目的是让调用者可以延迟决定何时真正启动IM服务
        final ServerLauncherImpl sli = new ServerLauncherImpl();
        // 启动MobileIMSDK服务端的Demo
        sli.startup();
    
        // 加一个钩子,确保在JVM退出时释放netty的资源
        Runtime.getRuntime().addShutdownHook(new Thread(sli::shutdown));
    }

    }

    如果服务端与客户端不在同一个项目 ,服务端可直接通过如下方式启动即可~

四、开发客户端

4.1 客户端与IM服务端连接事件

@Slf4j
public class ChatBaseEventImpl implements ChatBaseEvent {

    @Override
    public void onLoginMessage(int dwErrorCode) {
        if (dwErrorCode == 0) {
            log.debug("IM服务器登录/连接成功!");
        } else {
            log.error("IM服务器登录/连接失败,错误代码:" + dwErrorCode);
        }
    }

    @Override
    public void onLinkCloseMessage(int dwErrorCode) {
        log.error("与IM服务器的网络连接出错关闭了,error:" + dwErrorCode);
    }
}

4.2 接收消息事件

@Slf4j
public class ChatTransDataEventImpl implements ChatTransDataEvent {

    @Override
    public void onTransBuffer(String fingerPrintOfProtocal, String userid, String dataContent, int typeu) {
        log.debug("[typeu=" + typeu + "]收到来自用户" + userid + "的消息:" + dataContent);
    }

    @Override
    public void onErrorResponse(int errorCode, String errorMsg) {
        log.debug("收到服务端错误消息,errorCode=" + errorCode + ", errorMsg=" + errorMsg);
    }
}

4.3 消息是否送达事件

@Slf4j
public class MessageQoSEventImpl implements MessageQoSEvent {

    @Override // 对方未成功接收消息的回调事件 lostMessages:存放消息内容
    public void messagesLost(ArrayList<;Protocal> lostMessages) {
        log.debug("收到系统的未实时送达事件通知,当前共有" + lostMessages.size() + "个包QoS保证机制结束,判定为【无法实时送达】!");
    }

    @Override // 对方成功接收到消息的回调事件
    public void messagesBeReceived(String theFingerPrint) {
        if (theFingerPrint != null) {
            log.debug("收到对方已收到消息事件的通知,fp=" + theFingerPrint);
        }
    }
}

4.4 MobileIMSDK初始化配置

public class IMClientManager {
    private static IMClientManager instance = null;

    /**
     * MobileIMSDK是否已被初始化. true表示已初化完成,否则未初始化.
     */
    private boolean init = false;

    public static IMClientManager getInstance() {
        if (instance == null) {
            instance = new IMClientManager();
        }
        return instance;
    }

    private IMClientManager() {
        initMobileIMSDK();
    }

    public void initMobileIMSDK() {
        if (!init) {
            // 设置服务器ip和服务器端口
            ConfigEntity.serverIP = "127.0.0.1";
            ConfigEntity.serverPort = 8901;

            // MobileIMSDK核心IM框架的敏感度模式设置
//           ConfigEntity.setSenseMode(SenseMode.MODE_10S);

            // 开启/关闭DEBUG信息输出
            ClientCoreSDK.DEBUG = false;

            // 设置事件回调
            ClientCoreSDK.getInstance().setChatBaseEvent(new ChatBaseEventImpl());
            ClientCoreSDK.getInstance().setChatTransDataEvent(new ChatTransDataEventImpl());
            ClientCoreSDK.getInstance().setMessageQoSEvent(new MessageQoSEventImpl());

            init = true;
        }
    }
}

4.5 连接IM服务端,发送消息

服务类:

public interface IChatService {

    /**
     * 登录连接IM服务器请求
     *
     * @param username: 用户名
     * @param password: 密码
     * @return: void
     */
    void loginConnect(String username, String password);

    /**
     * 发送消息
     *
     * @param friendId: 接收消息者id
     * @param msg:      消息内容
     * @return: void
     */
    void sendMsg(String friendId, String msg);
}

服务实现类:

@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class ChatServiceImpl implements IChatService {

    @Override
    public void loginConnect(String username, String password) {
        // 确保MobileIMSDK被初始化哦(整个APP生生命周期中只需调用一次哦)
        // 提示:在不退出APP的情况下退出登陆后再重新登陆时,请确保调用本方法一次,不然会报code=203错误哦!
        IMClientManager.getInstance().initMobileIMSDK();

        // * 异步提交登陆名和密码
        new LocalUDPDataSender.SendLoginDataAsync(username, password) {
            /**
             * 登陆信息发送完成后将调用本方法(注意:此处仅是登陆信息发送完成,真正的登陆结果要在异步回调中处理哦)。
             * @param code 数据发送返回码,0 表示数据成功发出,否则是错误码
             */
            protected void fireAfterSendLogin(int code) {
                if (code == 0) {
                    log.debug("数据发送成功!");
                } else {
                    log.error("数据发送失败。错误码是:" + code);
                }
            }
        }.execute();
    }

    @Override
    public void sendMsg(String friendId, String msg) {
        // 发送消息(异步提升体验,你也可直接调用LocalUDPDataSender.send(..)方法发送)
        new LocalUDPDataSender.SendCommonDataAsync(msg, friendId) {
            @Override
            protected void onPostExecute(Integer code) {
                if (code == 0) {
                    log.debug("数据已成功发出!");
                } else {
                    log.error("数据发送失败。错误码是:" + code + "!");
                }
            }
        }.execute();
    }
}
五、编写Controller进行测试
@RestController
@RequestMapping("/api")
@Api(tags = "聊天测试-接口")
public class ChatController {

    @Autowired
    private IChatService chatService;

    @PostMapping(value = "/loginConnect", produces = Constants.CONTENT_TYPE)
    @ApiOperation(value = "登陆请求", httpMethod = "POST", respOnse= ApiResult.class)
    public ApiResult loginConnect(@RequestParam String username, @RequestParam String password) {
        chatService.loginConnect(username, password);
        return ApiResult.ok();
    }

    @PostMapping(value = "/sendMsg", produces = Constants.CONTENT_TYPE)
    @ApiOperation(value = "发送消息", httpMethod = "POST", respOnse= ApiResult.class)
    public ApiResult sendMsg(@RequestParam String friendId, @RequestParam String msg) {
        chatService.sendMsg(friendId, msg);
        return ApiResult.ok();
    }
}

启动项目,访问:http://127.0.0.1:8080/swagger-ui.html

1) loginConnect接口:

任意输入一个账号密码登录连接IM服务端:

控制台日志如下:

2)sendMsg接口:

给指定用户发送消息:这里由于只有一个客户端,上一步登录了一个admin账号,因此小编给admin账号(也就是自己) 发送消息

控制台日志如下:

六、本文小结

关于集成可参考MobileIMSDK给出的文档一步一步实现。

该开源工程对应的官方文档比较齐全,需要哪个端,就去看对应端的手册就好了。

1)**Demo安装和使用**

  • 客户端Demo安装和使用帮助(Android) [1]
  • 客户端Demo安装和使用帮助(iOS) [2]
  • 客户端Demo安装和使用帮助(Java) [3]
  • 客户端Demo演示和说明(H5) [4]
  • 服务端Demo安装和使用帮助 [5] new

2)**开发者指南**

  • 客户端开发指南(Android)
  • 客户端开发指南(iOS)
  • 客户端开发指南(Java)
  • 客户端开发指南(H5)
  • 服务端开发指南

3)**API文档**

  • 客户端SDK API文档(Android):TCP版、UDP版
  • 客户端SDK API文档(iOS):TCP版、UDP版
  • 客户端SDK API文档(Java):TCP版、UDP版
  • 客户端SDK API文档(H5):点此进入
  • 服务端SDK API文档

另外: 作者给出了通过Java GUI编程实现的一个小demo,我们可以先将其运行起来,先体验一下功能,代码量也不是太多,我们可以通过debug方式查看执行流程。

清楚执行流程之后我们就可以将demo中的代码移植到我们自己的项目中加以修改运用于自己的业务中,切勿拿起就跑,否则一旦运气不好,将浪费更多的时间去集成,这样很不好!

最后: 案例demo中相关代码注释都有,这里就简单说下整个流程吧:

  • 1)首先启动IM服务端

  • 2) 用户在客户端登录一个用户与服务端建立连接保持通信( 客户端ChatServiceImpl中loginConnect方法为登录连接服务端事件;服务端ServerEventListenerImpl中onUserLoginVerify方法为服务端接收的上线通知事件);

  • 3) 客户端通过 ChatServiceImpl中sendMsg方法发送一条消息,如果对方在线能接收消息则走服务端ServerEventListenerImpl中onTransferMessage4C2C方法,否则走onTransferMessage_RealTimeSendFaild方法;如果对方成功接收到消息,客户端将走MessageQoSEventImpl中messagesBeReceived事件,否则走messagesLost事件;

  • 4) 客户端通过ChatMessageEvent中onRecieveMessage回调事件接收消息。

附:本文案例demo源码下载:

  • 1)主地址:java-workspace: 存放案例demo代码

  • 2)备地址:java-workspace: 存放案例demo代码


推荐阅读
  • Java验证码——kaptcha的使用配置及样式
    本文介绍了如何使用kaptcha库来实现Java验证码的配置和样式设置,包括pom.xml的依赖配置和web.xml中servlet的配置。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
  • ZSI.generate.Wsdl2PythonError: unsupported local simpleType restriction ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • Google Play推出全新的应用内评价API,帮助开发者获取更多优质用户反馈。用户每天在Google Play上发表数百万条评论,这有助于开发者了解用户喜好和改进需求。开发者可以选择在适当的时间请求用户撰写评论,以获得全面而有用的反馈。全新应用内评价功能让用户无需返回应用详情页面即可发表评论,提升用户体验。 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • Android开发实现的计时器功能示例
    本文分享了Android开发实现的计时器功能示例,包括效果图、布局和按钮的使用。通过使用Chronometer控件,可以实现计时器功能。该示例适用于Android平台,供开发者参考。 ... [详细]
  • iOS超签签名服务器搭建及其优劣势
    本文介绍了搭建iOS超签签名服务器的原因和优势,包括不掉签、用户可以直接安装不需要信任、体验好等。同时也提到了超签的劣势,即一个证书只能安装100个,成本较高。文章还详细介绍了超签的实现原理,包括用户请求服务器安装mobileconfig文件、服务器调用苹果接口添加udid等步骤。最后,还提到了生成mobileconfig文件和导出AppleWorldwideDeveloperRelationsCertificationAuthority证书的方法。 ... [详细]
  • 本文介绍了如何使用JSONObiect和Gson相关方法实现json数据与kotlin对象的相互转换。首先解释了JSON的概念和数据格式,然后详细介绍了相关API,包括JSONObject和Gson的使用方法。接着讲解了如何将json格式的字符串转换为kotlin对象或List,以及如何将kotlin对象转换为json字符串。最后提到了使用Map封装json对象的特殊情况。文章还对JSON和XML进行了比较,指出了JSON的优势和缺点。 ... [详细]
  • 本文介绍了Android中的assets目录和raw目录的共同点和区别,包括获取资源的方法、目录结构的限制以及列出资源的能力。同时,还解释了raw目录中资源文件生成的ID,并说明了这些目录的使用方法。 ... [详细]
  • 一次上线事故,30岁+的程序员踩坑经验之谈
    本文主要介绍了一位30岁+的程序员在一次上线事故中踩坑的经验之谈。文章提到了在双十一活动期间,作为一个在线医疗项目,他们进行了优惠折扣活动的升级改造。然而,在上线前的最后一天,由于大量数据请求,导致部分接口出现问题。作者通过部署两台opentsdb来解决问题,但读数据的opentsdb仍然经常假死。作者只能查询最近24小时的数据。这次事故给他带来了很多教训和经验。 ... [详细]
  • Java如何导入和导出Excel文件的方法和步骤详解
    本文详细介绍了在SpringBoot中使用Java导入和导出Excel文件的方法和步骤,包括添加操作Excel的依赖、自定义注解等。文章还提供了示例代码,并将代码上传至GitHub供访问。 ... [详细]
  • Play1.2.3中主要包括的第三方包有xstreamxml与object互转snakeyamlyaml解析与发射slf4j日志接口相关signpost ... [详细]
author-avatar
迷途羔羊1989_751
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有