自定义博客皮肤VIP专享

*博客头图:

格式为PNG、JPG,宽度*高度大于1920*100像素,不超过2MB,主视觉建议放在右侧,请参照线上博客头图

请上传大于1920*100像素的图片!

博客底图:

图片格式为PNG、JPG,不超过1MB,可上下左右平铺至整个背景

栏目图:

图片格式为PNG、JPG,图片宽度*高度为300*38像素,不超过0.5MB

主标题颜色:

RGB颜色,例如:#AFAFAF

Hover:

RGB颜色,例如:#AFAFAF

副标题颜色:

RGB颜色,例如:#AFAFAF

自定义博客皮肤

-+

longhuihu的专栏

工作、学习轨迹记录

  • 博客(65)
  • 资源 (1)
  • 收藏
  • 关注

原创 开源库GameNetty—让编写Socket服务像HTTP一样简单

在java领域,Netty已经成为编写Socket服务的首选。稍有遗憾的是,相比Spring MVC,Netty的使用门槛还是比较高的;要用好Netty,必需掌握好多线程、Socket、底层字节操作等技能,而且相关程序的调试难度也比较大。本人使用Netty从事游戏服务开发近5年,不断打磨底层网络框架,诞生了GameNetty这个工具库,权当本人对这个领域一点微不足道的回馈吧。GameNetty目前用于公司的多个项目,非常稳定。当然受限于使用场景,肯定还有很多不足之处,期望得到同行的批评指正。一、用

2020-12-18 09:46:06 266

原创 一种简单的系统分层架构模式

这里写自定义目录标题1、简洁架构模型2、简单分层架构模式2.1 业务模型层职责:实现业务逻辑角色:业务实体和业务服务,业务模型层是非必需的命名方案2.2 功能(UserCase)服务层职责:实现功能用例命名方案几乎是必需的2.3 数据适配层职责:负责业务数据读写命名方案几乎是必需的2.4 交互控制器职责:处理外部信号命名方案是否必需2.5 展示器职责:生成展示数据;命名方案非必需2.6 调用关系2.7 跨层调用3、技术框架框架不是架构框架是外层技术细节降低框架的侵入性4、简单示例业务模型层数据适配层功能服务

2021-04-13 20:22:38 706

原创 单元测试实践总结

对于单元测试的价值,相信大家都很认可(至少从政治正确的角度很认可),但具体执行起来却很难,常见的障碍有两个:延长了开发时间——本来工期就紧,加上单元测试,岂不雪上加霜;项目代码基于各种框架(如Spring)编写,并依赖外部环境(数据库、外部服务接口等),导致单元测试很难编写;第一个问题的本质是单元测试的成本收益问题,第二问题是项目架构的可测性问题;这两个问题是确确实实存在的,如果不能妥善解决,就在项目组强制推行单元测试,必然一地鸡毛。1、单元测试的成本与收益编写单元测试确确实实会引入一定的开发

2021-04-02 19:39:16 601 2

原创 软件架构整洁之道-读书笔记(3)

第五部分:软件架构第十五章:什么是软件架构1、架构师是什么样的人?首先软件架构师必须是能力最强的一群程序员,他们的代码产量可能不是最多的,但是他们必须不停的承接编程任务。如果不亲自承受因系统设计而带来的麻烦,就体会不到设计不加所带来的的痛苦,接着就会逐渐迷失正确的设计方向。软件架构这项工作的实质就是规划如何将系统划分成组件,并安排好组件之间的排列关系,以及组件之间互相通信的方式。2、架构设计和系统行为的关系软件架构的直接目的不是保证系统能否正常工作,毕竟世界上也有很多架构设计糟糕但是工作正常的软

2021-01-20 16:34:42 2379

原创 软件架构整洁之道-读书笔记(2)

第三部分:设计原则通常来说,要想构建一个好的软件系统,应该从写整洁的代码开始做起。这就是SOLID设计原则要解决的问题。SOLID原则的主要作用就是告诉我们如何将数据和函数组织成为类,以及如何将这些类链接起来成为程序。这里的”类“不限于面向对象编程的类,仅仅代表一种数据和函数的分组。一般情况下,我们为软件构建”中层结构“的主要目标是:使软件可容忍被改动;使软件更容易被理解;构建在多个软件系统中复用的组件。SOLID原则紧贴于模块级(中层结构)代码逻辑之上,帮助我们定义软件架构中的组件和模块。

2021-01-20 16:29:53 233

原创 软件架构整洁之道-读书笔记(1)

《软件架构整洁之道》是绝对的好书,是真正的顶级程序员编写的关于架构设计的书,一口气读完,有酣畅淋漓之感,解除了自己长久以来非常多的疑惑。这么好的书,这么多年才发现,简直是犯罪。这三篇笔记几乎就是原书的摘要,因为原文写得足够精彩,无需我补充什么;笔记的内容结构与原书也完全一致,之所以分成三篇,纯粹是篇幅原因。第一部分:绪论第一章,概述1、软件架构和设计有什么区别?通常情况下,”架构“这个词往往使用于“高层级“的讨论中,这些讨论暂时将”底层“实现细节排除在外;而”设计“一词往往用来指代具体的系统底层组

2021-01-20 16:27:13 379

原创 JavaGC核心问题脉络

Java GC是一个庞大的主题,涉及的理论知识和具体技术特别多,而它又是java开发者最需要掌握的JVM知识(其他JVM知识离开发者较远,对日常工作帮助不大)。这篇笔记总结一下Java GC技术所涉及的核心问题脉络,让阅读者从更高层次把握Java GC技术的来龙去脉,尽量不陷入技术细节;期望的效果是:有这个脉络在心中,再去研究具体问题时,可做到心中有数、有的放矢,至少明白,目标问题到底处于GC的哪个环节,为什么这个问题会存在?参考资料:《深入理解Java虚拟机第三版》,《垃圾回收算法手册》一、概述

2020-09-15 14:18:28 248

原创 Netty详解之十一:ByteBuf内存泄露

我们现在知道ByteBuf是通过引用计数来管理生命周期的,换句话说,需要开发者手动管理,这对java程序员来说是非常有挑战性的一件事;为此,Netty提供了内存泄露检测机制。ByteBuf泄露检测原理首先ByteBuf是一个java对象,Netty并不关注java对象的泄露,使用者作为java开发者必须保证没有发生java对象泄露,在这个前提下,Netty为ByteBuf包含的数据区域的泄露提供诊断。java对象泄露,是指意外地缓存了不再使用对象的强引用,更多相关知识请自行搜索。假设开发者分配了

2020-08-04 17:36:15 2656 2

原创 Netty详解之九:使用ByteBuf

上一章介绍了几种典型ByteBuf的原理,这一章介绍它的使用方法,包括Netty是如何使用ByteBuf的。引用计数上一章已经提及“引用计数”的概念;引用及计数是一种历史悠久的对象生命周期管理手段。它的核心理念是在对象上增加一个int字段来维护对象“拥有者的数量”,每当对象增加一个拥有者,引用计数加一,反之减一;对象创建之初引用计数等于1,引用计数变成0的那一刻立刻释放。ByteBuf从ReferenceCounted接口继承,后者定义了引用计数的概念抽象。public interface Refe

2020-08-03 09:31:33 2737 1

原创 Netty详解之九:ByteBuf介绍

本篇深入剖析Netty读写缓冲区的设计,内容包括ByteBuf抽象、池化ByteBuf、Direct ByteBuf、Channel的读写冲缓冲区。ByteBuf为了提高性能,Netty重新设计了字节缓冲区ByteBuf,类似Nio的ByteBuffer,但工作方式略有区别,比后者更加灵活、高效。ByteBuf有几个重要属性:capacity:容量;0:缓冲区开始位置;readIndex:下一个读位置;writeIndex:下一个写位置;一个ByteBuf对象即可像byte数组一样工作,

2020-08-03 09:18:35 3873

原创 Netty详解之八:编解码器

Socket只能发送&接收字节数据,我们的业务层肯定期望处理预定义的数据对象,从字节数据到数据对象之间的转换叫做解码,反过来就是解码。Netty对编解码的支持非常优秀,本文以一个案例来介绍“如何编写编解码器”。编解码器案例基本定义假设我们在业务层处理的数据对象定义如下://为了缩短代码篇幅,用public字段public class SocketMessage { public int userId; public String content;}这个结构的设计有一定的典型性,

2020-07-31 19:11:56 730 1

原创 Netty详解之七:Pipeline与ChannelHandler

来自Channel底层的IO事件,会转发给pipeline来处理,另一方面用户直接调用Channel的IO方法也是通过pipeline达到底层。因此pipepline是Netty内核与业务层之间的传送带,是一个双向的IO事件通道,其中从业务层往底层方向叫"outbound",从底层通往上层,叫“inbound"。Pipeline本质上是由一个一个ChannelHandler节点组成的双向链表,其中既有Netty预置的节点,也有用户代码添加的节点。每个节点处理特定IO事件,或者将IO事件进行转换再传递给下一

2020-07-30 14:49:11 995

原创 Netty详解之六:Channel

Java NIO就已经将底层socket链路抽象为java.nio.channels.Channel,Netty也将其抽象为io.netty.channel.Channel。如果将Netty比如为一条高速运转的生产线,那么EventLoop是这条生产线的动力装置,而Channel则是一个个的机械臂,EventLoop驱动着Channel不断地执行IO操作。Channel可以认为Channel是对socket连接的抽象,一个支持IO操作的通道,Netty对Channel的定义如下:public inte

2020-07-29 14:19:27 4627

原创 Netty详解之五:EventLoop

上一篇介绍了Netty的EventExecutor框架,这一篇还是围绕EventExecutor,介绍它在通信模块里是如何运用的。EventLoopGroup和EventLoopEventExecutor到了通信模块内,就变成了EventLoop,表明它是一个事件处理循环;EventLoop相关类型全部定义在io.netty.channel这个package下面。EventLoopGroup接口扩展自EventExecutorGroup,增加了注册Channel的方法:public interfac

2020-07-29 09:26:45 768

原创 Netty详解之四:EventExecutor框架

Netty是按事件驱动模型来工作的,在涉及Netty的网络通信功能之前,我们先彻底剖析一下它的事件驱动机制,或者说是Netty的并发机制。netty并发相关类全部位于io.netty.util.concurrent下面,居于核心位置的接口有两个:EventLoopGroup和EventLoop。由于Netty并发机制相对比较独立,完全可独立于其他功能而被使用,所以这块的分析我们采用自顶向下的方式。Netty的并发机制实际上是java executor框架的一个实现,它比JDK的executor更加强大一

2020-07-28 12:21:38 4029

原创 Netty详解之三:Server端启动过程

Netty的源码包里面,io.netty.example下有很多示例代码可供参考,是学习使用Netty的利器;另外要掌握netty,需要对它的关键源码有一定程度的了解。从这一章开始,我们开始跟着源码学习Netty的各核心模块,源码使用当前最新稳定版4.1.45.Final。另外要能读懂Netty源码,有几个前提条件:掌握java的多线程原理,有一定深度,尤其对java.util.concurrent.Executor框架要熟悉;对TCP协议有一定了解,会Socket编程,掌握java NIO相关接口

2020-07-28 11:46:33 619

原创 Netty详解之二:Netty概述

总体上,Netty是一个高性能网络编程框架,不仅仅是对java socket接口的封装,它的功能可概括如下:事件驱动的异步编程模型(reactor模型)灵活的线程池管理;支持常见的传输层协议和应用层协议;屏蔽了底层bug(比如NIO Epoll Bug),更加稳定高效。总之,netty性能较好,功能较全,文档相对完善,基本成为了java领域socket编程的首选工具。Reactor模型很多文章将Recactor模型和IO模型混在一起介绍,是不合适的;因为这二者不在同一个层次,reactor

2020-07-27 19:25:43 547

原创 Netty详解之一:IO模型与Java NIO

Netty是一个网络IO框架,想要掌握Netty,必须要对于TCP协议、Socket这些基础知识有一个了解。这方面的经典书籍莫过于《UNIX网络编程卷1:套接字联网API》和《TCP-IP详解卷1》。当然,没有这么深厚的基础知识,也不妨碍你使用Netty。由于Netty是Java领域的框架,因此本系列文章在涉及相关概念时,会优先使用java领域的说法。因此有可能在某些地方显得不够严谨,这首先是本人水平有限所致,有时也是有意为之。IO模型在Java网络编程领域,我们通常有三种网络IO模型:bio(Blo

2020-07-27 19:17:20 577

原创 《Java并发编程实践》五(4):java内存模型(终结篇)

到此为止,我们基本已经覆盖了java并发编程涉及的所有技术主题,包括安全发布、同步机制。这些技术之所以这样设计,都与Java内存模型(Java Memory Model, JMM)有关。我们编程时不会直接与java内存模型打交道,但是理解它对能让我们更加得心应手地使用之前学习到的各种技术。这一章,就让我们揭开java内存模型的神秘面纱。内存模型假设有一个线程执行了一句代码:aVariable = 3,内存模型要解决一个问题:在何种情况下,一个线程(包括其他线程)读取aVariable时能得到值——3。这

2020-05-29 16:19:10 434

原创 《Java并发编程实践》五(3):原子变量和非阻塞同步

java并发库(java.util.concurrent)提供了很多(相比锁)性能更优越的同步设施,比如ConcurrentLinkedQueue。本章的主题,是研究此类并发装置的性能秘密:原子变量和非阻塞同步。锁的性能劣势对现代JVM来说,在锁未发生竞争的情况下,JVM执行“加锁、释放锁”操作是非常快的;但是一旦发生竞争,就需要执行系统调用来挂起竞争失败的线程,等将来锁释放时再唤醒它们。挂起和唤醒线程的性能损耗是不能忽视的,对那些需要加锁的高频操作(比如集合读写)来说,锁竞争导致的线程调度消耗的CP

2020-05-29 16:06:05 282

原创 《Java并发编程实践》五(2):自定义同步器

这一章介绍基于状态条件同步的一般场景,以及通过条件队列来构建自定义同步机制的方法。最后介绍JDK同步器的公共抽象—AQS的基本原理,以及它在Java并发库内的应用。状态条件一般来说,线程之间的同步都是围绕状态条件展开的,以”容量有限队列“为例,take操作必须在队列满足“nonempty“状态条件下才能执行;如果不满足条件,take操作要么失败,要么阻塞。构建基于状态、具备并发同步功能的类,最容易的方式是使用现有的同步装置,下使用CountDownLatch构建一个二元的同步装置ValueLatch,用

2020-05-26 19:40:05 367

原创 《Java并发编程实践》五(1):显示锁

本书最后一部分:“并发高级主题“,其内容如下:第13章:显式锁ReentrantLock;第14章:构建自定义同步器;第15章:非阻塞同步器;第16章:java内存模型ReentrantLockjava 5之前,java语言唯一的线程同步手段就是 synchronized 和 volatile;Java 5增加了ReentrantLock,它不是java监视锁的替代品,而是在后者不满足需求时,提供更丰富的功能。java为锁定义了一个通用的接口Lock,提供了相关锁操作方法如下:publi

2020-05-26 19:26:47 203

原创 《Java并发编程实践》四(2):并发性能和可伸缩性

使用多线程的主要动机就是提升系统的性能和响应性,充分利用硬件和其他资源的处理能力。这一章介绍分析、监控和改善并统发系性能的技术。不过,多线程系统极其复杂,提升性能的技术往往会增加安全和活性风险;如果使用不当,改善性能的尝试,往往是解决一个问题的同时,引入另一个问题,得不偿失。因此,在准备对并发系统进行性能优化之前要记住两点:先保证程序正确,再优化性能,且仅在测试表明需要优化时;追求极致的性能没有什么意义;对性能的思考提升性能意味着用更少的资源做更多的事。资源有多种多样,比如CPU,内存,网络带

2020-05-18 10:50:15 372

原创 《Java并发编程实践》四(1):死锁

第10~12章是本书第三部分,介绍多线程模式下的性能问题:第10章:死锁,主要介绍死锁引起的线程活性失败;第11章:并发性能和可伸缩性,介绍多线程角度如何分析程序的性能;第12章:测试,如何对多线程程序进行测试,此章跳过。死锁及死锁避免(Deadlock)如果线程A持有一个锁,那么其他想要获取这个锁的线程会被阻塞;如果A永远不释放这个锁,那么其他线程永远被阻塞,这最简单的死锁场景。这种情景是比较明显的编程错误,实际项目中比较少出现。如果线程A当前拥有锁L,尝试获取锁M,但是线程B却相反,当前

2020-05-18 10:23:22 256

原创 《Java并发编程实践》三(8):线程池和任务队列

线程池的概念在第6、7章已经反复出现多次,因为Executor框架的实现需要线程池来执行任务;这一章详细介绍如何配置线程池。任务和执行策略前面提到,Excecutor框架将任务的提交和执行分离,实现了二者之间的解耦。但在实际项目中,任务之间可能存在关联或其他约束,并不能在任何执行策略下保证正确性。非独立任务:独立任务可适应任何执行策略,是程序具有最好的可扩展性。在执行过程中,你可以随意...

2020-05-08 11:38:08 814

原创 《Java并发编程实践》三(7):任务取消

通过Executor框架提交一个任务是很简单的,绝大多数情况下,我们让任务代码执行完毕;换句话说,任务自身决定如何结束。如果调用者想终止一个任务该怎么办?上一章介绍的Future.cancel方法看起来正好满足我们的需求,然而如何安全、快速、可靠地取消一个异步任务或线程,要比这句代码看起来要复杂得多,值得我们用一整章来学习。任务取消在任务正常结束前,取消一个任务可能有以下几种动机:用户要求...

2020-05-08 11:34:35 330

原创 《Java并发编程实践》三(6):任务及其执行

前面第2~5章是本书的第二部分,介绍了线程安全的基本知识,以及常用的java线程安全组件。这一章开始,进入第二部分,从更高的层次,介绍如何通过结构化的方式,来设计、构建基于多线程的应用系统。第6章:任务及其执行第7章:任务的取消和关闭第8章:使用线程池第9张:GUI应用(本系列文章跳过这一章)本章介绍Task的概念及其执行方式。Task大多数并发系统将工作抽象为Task,所谓ta...

2020-05-08 11:31:51 189

原创 《Java并发编程实践》二(5):线程安全组件

这是第二部分最后一章,介绍java提供的线程安全组件;需要注意的是,由于本书比较老,只涵盖Java 1.6的并发组件,内容并不过时,但完整性有所欠缺。第4章介绍了编写线程安全类的几种途径,其中“委托线程安全”策略基于现有的线程安全类型来构建自定义的线程安全类,是最可靠的、最常用的策略。因此本章全面地介绍一下JDK为我们提供的线程安全的组件。同步集合(Synchronized Collectio...

2020-05-08 11:28:38 200

原创 《Java并发编程实践》二(4):组合对象的线程安全性

前面介绍了线程安全的基础技术,在实际项目中,我们肯定不希望在那个层面来分析每一个对象操作的线程安全性,而是期望使用现有的线程安全组件来构建线程安全的程序。如何设计一个线程安全的类如果将一个类的状态存储在public static的字段内,那么保证线程安全是很难的,因为程序的任何地方都可以自由地修改状态。良好的封装限制了状态的访问路径,更容保证线程安全性。设计一个线程安全类需要考虑一下几个方面...

2020-04-09 19:59:39 185

原创 《Java并发编程实践》二(3):共享对象

上一章介绍了如何通过原子操作来保护对象状态,这一章介绍如何在多线程之间共享和发布对象。多个线程共享一个对象涉及两个问题,第一个问题是多线程并发地访问对象而不发生错误;第二个问题是当一个线程修改了对象状态,其他线程能够及时看到这个修改。第一个问题上一章已经给出解决思路,而第二个问题也被称为“可见性”问题。可见性对于单线程的应用来说,如果一个变量被修改,那么后续对这个变量的读操作,读到的一定是修改...

2020-04-04 16:00:55 267 1

原创 《Java并发编程实践》二(2):什么是线程安全

每当我们谈及线程安全的话题,都会想到Thread, Lock这些词语,不过这些都是线程安全相关的工具,就像建筑桥梁所用的钉子、砖头。线程安全真正关注的核心问题是“正确地管理程序状态”,更具体的,是“管理共享可变状态”。状态在谈及线程安全时,状态可简单理解为内存数据,在java世界里,状态就是指存储在状态变量里的数据;状态变量可以是对象实例的字段,也可能是类的静态字段。对象的状态包括任何影响该对...

2020-03-30 15:40:50 133

原创 《Java并发编程实践》一(1):为什么使用多线程

《Java Concurrency in Practice》是java并发编程领域的经典书籍,本人认为是最好的;本系列文章算是该书2006版本(虽然有一定年头,但内容一点不过时)的读书笔记。为什么要使用多线程?早年间,计算机的性能还比较弱的时候,大家使用多线程(进程)的动机主要是以下几点:充分利用资源当一个操作需要阻塞时(比如等待IO完成),可以通过线程调度让出CPU,让其他线程有来执...

2020-03-30 15:14:29 212

原创 SpringMVC之五:Controller

@Controller将一个bean标注为控制器,@RequestMapping标注一个控制器方法为url处理器。这大概是我们在日常开发工作中接触得最多的两个关键字了。本章介绍Controller类,尤其是@RequestMapping标注的处理器方法背后的工作原理。这部分介绍的知识,都是大家在日常工作中大概率能用上的知识,非常值得我们花点时间把它搞清楚。本章的示例代码:SpringMVCSam...

2020-01-14 16:36:52 752

原创 SpringMVC之四:Interceptor

拦截器Interceptor用来对http请求的处理过程进行拦截,以插入特定的处理逻辑,比如权限鉴别,有点AOP的味道。上文讲过,HandlerMapping对匹配的http请求会返回一个HandlerExecutionChain,而Interceptor正是这个chain的一部分。HandlerInterceptor拦截器必须实现HandlerInterceptor接口:public in...

2020-01-14 16:20:09 193

原创 SpringMVC之三:HandlerMapping

我们知道Spring通常以bean的形式来组织功能模块,Spring MVC也不列外。Spring MVC以一系列特定类型的bean来构建整个框架。相关Bean类型Bean类型说明HandlerMapping实现了url到处理器的映射关系,包括与之关联的拦截器(interceptors);有两个主要实现类,RequestMappingHandlerMapping支持@Re...

2020-01-14 16:14:26 284

原创 详解Spring MVC之二:M-V-C在哪

Spring MVC实现了一个完整的基于MVC设计模式的web服务框架。这个框架包含的知识特别多,而且这些知识点之间存在密切的联系。因此我们有必要先从整体上介绍了一下这个框架,建立一个完整的印象,为后续的学习做一个铺垫。我们先看一个示例代码:@Controllerpublic class MyController {@AutoWiredprivate PetRepository rep...

2019-12-13 17:57:36 717

原创 详解Spring MVC之一:DispatcherServlet

前言让我们开启Spring知识一个新的系列文章,详解Spring Web MVC。同样,这个系列的文章是对Spring官方文档的一个翻译、整理、重组,以及小幅度的补充。原文档的地址在这里。Spring MVC是符合servlet规范的一个web框架,它通过一个叫做DispatcherServlet的Servlet,来接受所有的http请求,再转发给Spring容器内的MVC组件来处理这些请求。...

2019-12-13 17:09:54 285

原创 Spring AOP:底层API

上一章介绍了Spring AOP的概念和基本用法,本章探寻SpringAOP更底层API,一窥Spring AOP的实现原理。在使用层面,上一章介绍的知识完全足够了,并不推荐在实际项目中使用本章介绍的API。本章对应的官方原文档的地址。PointCut API通过上一章我们知道,@PoinCut注解可以定义的切点,可在多个通知之间复用。因此在实现层面,切点和通知也是完全解耦的。切点通过接口o...

2019-12-11 17:29:03 625

原创 Spring AOP:概念和用法

在一个应用系统中,我们会有一些核心业务逻辑之外的关注点,比如安全、日志、事务,这些关注点横跨整个业务系统,与具体业务功能交织在一起。对于此类关注点,面向对象编程束手无策。AOP(面向切面编程)是解决该问题的技术概念模型,一个切面是对某个横切关注点的模块化,织入将这个切面插入目标模块而不需要目标模块修改代码。有很多的AOP实现方案,比如强大的AspectJ;Spring AOP借鉴了AspectJ...

2019-12-11 17:17:42 226

原创 Spring基础十三(终):BeanFactory

这是Spring基础知识系列的最后一个章节,让我们回顾一下Spring核心功能:Ioc容器。我们初始化一个Spring容器时,是创建一个ApplicationContext的实现类,需要直接访问容器功能时,大多也是调用ApplicationContext方法;仿佛容器这个抽象概念的具体形式就是ApplicationContext。这样的理解本身也没有大问题,不过ApplicationContex...

2019-11-28 16:19:21 100

Series_60_Developer_Platform_1_0_2_0_Using_the_Phone_Book_Engine_v1_0_en.pdf

S60中使用Phonebook API的使用详解

2008-10-10

空空如也

TA创建的收藏夹 TA关注的收藏夹

TA关注的人

提示
确定要删除当前文章?
取消 删除