Android RIL的Java部分也被分为了两个模块,RIL模块与Phone模块。其中RIL模块负责进行请求以及相应的处理,它将直接与RIL的原声代码进行通信。而Phone模块则向应用程序开发者提供了一系列的电话功能接口。

1.RIL模块结构

在RIL.java中实现了几个类来进行与下层rild的通信。

它实现了如下几个类来完成操作:

RILRequest:代表一个命令请求

RIL.RILSender:负责AT指令的发送

RIL.RILReceiver:用于处理主动和普通上报信息

RIL.RILSender与RIL.RILReceiver是两个线程。

RILRequest提供了obtain()方法,用于得到具体的request操作,这些操作被定义在RILConstants.java中 (RILConstants.java中定义的request命令与RIL原生代码中ril.h中定义的request命令是相同的),然后通过 send()函数发送EVENT_SEND,在RIL_Sender线程中处理这个EVENT_SEND将命令写入到stream(socket)中去。 Socket是来自常量SOCKET_NAME_RIL,它与RIL 原生代码部分的s_fdListen所指的socket是同一个。

当有上报信息来到时,系统将通过RILReciver来得到信息,并进行处理。在RILReciver的生命周期里,它一直监视着 SOCKET_NAME_RIL这个socket,当有数据到来时,它将通过readRilMessage()方法读取到一个完整的响应,然后通过 processResponse来进行处理。

2.Phone模块结构

Android通过暴露Phone模块来供上层应用程序用户使用电话功能相关的接口。它为用户提供了诸如电话呼叫,短信息,SIM卡管理之类的接口调用。它的核心部分是类GSMPhone,这个是Gsm的电话实现,需要通过PhoneFactory获取这个GSMPhone。

GSMPhone并不是直接提供接口给上层用户使用,而是通过另外一个管理类TelephonyManager来供应用程序用户使用。

类TelephonyManager实现了android的电话相关操作。它主要使用两个服务来访问telephony功能:

1.ITelephony,提供给上层应用程序用户与telephony进行操作,交互的接口,在packages/apps/Phone中由PhoneInterfaceManager.java实现。

2.ItelephonyRegistry提供了一个通知机制,将底层来的上报通知给框架中需要得到通知的部分,由TelephonyRegistry.java实现。

GSMPhone通过PhoneNotifier的实现者DefaultPhoneNotifier将具体的事件转化为函数调用,通知到 TelephonyRegistry。TelephonyRegistry再通过两种方式通知给用户,其一是广播事件,另外一种是通过服务用户在 TelephonyRegistry中注册的IphoneStateListener接口,实现回调(回调方式参见android的aidl机制)。

Android的RIL驱动模块,在hardware/ril目录下,一共分rild,libril.so以及librefrence_ril.so三个部分,另有一 radiooptions可供自动或手动调试使用。都依赖于include目录中ril.h头文件。目前cupcake分支上带的是gsm的支持,另有一 cdma分支,这里分析的是gsm驱动。

GSM模块,由于Modem的历史原因,AP一直是通过基于串口的AT命令与BB交互。包括到了目前的一些edge或3g模块,或像omap这类ap,bp集成的芯片,已经使用了USB或其他等高速总线通信,但大多仍然使用模拟串口机制来使用AT命令。这里的RIL(Radio Interface Layer)层,主要也就是基于AT命令的操作,如发命令,response解析等。(gprs等传输会用到的MUX协议等在这里并没有包含,也暂不作介绍。)

以下是详细分析,本文主要涉及基本架构和初始化的内容:

首先介绍一下rild与libril.so以及 librefrence_ril.so的关系:

1. rild:

仅实现一main函数作为整个ril层的入口点,负责完成初始化。

2. libril.so:

与rild结合相当紧密,是其共享库,编译时就已经建立了这一关系。组成部分为 ril.cpp,ril_event.cpp。libril.so驻留在rild这一守护进程中,主要完成同上层通信的工作,接受ril请求并传递给 librefrence_ril.so, 同时把来自librefrence_ril.so的反馈回传给调用进程。

3. librefrence_ril.so:

rild通过手动的dlopen方式加载,结合稍微松散,这也是因为librefrence.so主要负责跟Modem硬件通信的缘故。这样做更方便替换或修改以适配更多的Modem种类。它转换来自libril.so的请求为AT命令,同时监控Modem 的反馈信息,并传递回libril.so。在初始化时, rild通过符号RIL_Init获取一组函数指针并以此与之建立联系。

4. radiooptions:

radiooptiongs通过获取启动参数, 利用socket与rild通信,可供调试时配置Modem参数。

接下来分析初始化流程,主入口是rild.c中的main函数,主要完成三个任务:

1. 开启libril.so中的event机制,在RIL_startEventLoop中,是最核心的由多路I/O驱动的消息循环。

2. 初始化librefrence_ril.so,也就是跟硬件或模拟硬件modem通信的部分(后面统一称硬件), 通过RIL_Init函数完成。

3. 通过RIL_Init获取一组函数指针RIL_RadioFunctions,并通过RIL_register完成注册,并打开接受上层命令的socket通道。

首先看第一个任务,也就是 RIL_startEventLoop函数。RIL_startEventLoop在ril.cpp中实现,它的主要目的是通过pthread_create(&s_tid_dispatch, &attr, eventLoop, NULL)建立一个dispatch线程,入口点在eventLoop. 而eventLoop中,会调ril_event.cpp中的ril_event_loop()函数,建立起消息(event)队列机制。

我们来仔细看看这一消息队列的机制,这些代码都在ril_event.cpp中。

Java代码

voidril_event_init();

voidril_event_set(struct ril_event * ev,intfd, bool persist, ril_event_cb func,void* param);

voidril_event_add(struct ril_event * ev);

voidril_timer_add(struct ril_event * ev, struct timeval * tv);

voidril_event_del(struct ril_event * ev);

voidril_event_loop();

struct ril_event {

struct ril_event *next;

struct ril_event *prev;

intfd;

intindex;

bool persist;

struct timeval timeout;

ril_event_cb func;

void*param;

};

void ril_event_init();

void ril_event_set(struct ril_event * ev, int fd, bool persist, ril_event_cb func, void * param);

void ril_event_add(struct ril_event * ev);

void ril_timer_add(struct ril_event * ev, struct timeval * tv);

void ril_event_del(struct ril_event * ev);

void ril_event_loop();

struct ril_event {

struct ril_event *next;

struct ril_event *prev;

int fd;

int index;

bool persist;

struct timeval timeout;

ril_event_cb func;

void *param;

};

每个 ril_event结构,与一个fd句柄绑定(可以是文件,socket,管道等),并且带一个func指针去执行指定的操作。

具体流程是: ril_event_init完成后,通过ril_event_set来配置一新ril_event,并通过ril_event_add加入队列之中(实际通常用rilEventAddWakeup来添加),add会把队列里所有ril_event的fd,放入一个fd集合readFds中。这样 ril_event_loop能通过一个多路复用I/O的机制(select)来等待这些fd,如果任何一个fd有数据写入,则进入分析流程processTimeouts(),processReadReadies(&rfds, n),firePending()。 后文会详细分析这些流程。

另外我们可以看到, 在进入ril_event_loop之前,已经挂入了一s_wakeupfd_event, 通过pipe的机制实现的,这个event的目的是可以在一些情况下,能内部唤醒ril_event_loop的多路复用阻塞,比如一些带timeout的命令timeout到期的时候。

至此第一个任务分析完毕,这样便建立起了基于event队列的消息循环,稍后便可以接受上层发来的的请求了(上层请求的 event对象建立,在第三个任务中)。

接下来看第二个任务,这个任务的入口是RIL_Init, RIL_Init首先通过参数获取硬件接口的设备文件或模拟硬件接口的socket. 接下来便新开一个线程继续初始化, 即mainLoop。

mainLoop的主要任务是建立起与硬件的通信,然后通过read方法阻塞等待硬件的主动上报或响应。在注册一些基础回调(timeout,readerclose)后,mainLoop首先打开硬件设备文件,建立起与硬件的通信,s_device_path和s_port 是前面获取的设备路径参数,将其打开(两者可以同时打开并拥有各自的reader,这里也很容易添加双卡双待等支持)。

接下来通过 at_open函数建立起这一设备文件上的reader等待循环,这也是通过新建一个线程完成, ret = pthread_create(&s_tid_reader, &attr, readerLoop, &attr),入口点readerLoop。

AT命令都是以\r\n或\n\r的换行符来作为分隔符的,所以 readerLoop是line驱动的,除非出错,超时等,否则会读到一行完整的响应或主动上报,才会返回。这个循环跑起来以后,我们基本的AT响应机制已经建立了起来。它的具体分析,包括at_open中挂接的ATUnsolHandler, 我们都放到后面分析response的连载文章里去。

有了响应的机制(当然,能与硬件通信也已经可以发请求了),通过 RIL_requestTimedCallback(initializeCallback, NULL, &TIMEVAL_0),跑到initializeCallback中,执行一些Modem的初始化命令,主要都是AT命令的方式。发AT命令的流程,我们放到后面分析request的连载文章里。这里可以看到,主要是一些参数配置,以及网络状态的检查等。至此第二个任务分析完毕,硬件已经可以访问了。

最后是第三个任务。第三个任务是由RIL_Init的返回值开始的,这是一个RIL_RadioFunctions结构的指针。

Java代码

typedef struct {

intversion;/* set to RIL_VERSION */

RIL_RequestFunc onRequest;

RIL_RadioStateRequest onStateRequest;

RIL_Supports supports;

RIL_Cancel onCancel;

RIL_GetVersion getVersion;

} RIL_RadioFunctions;

typedef struct {

int version; /* set to RIL_VERSION */

RIL_RequestFunc onRequest;

RIL_RadioStateRequest onStateRequest;

RIL_Supports supports;

RIL_Cancel onCancel;

RIL_GetVersion getVersion;

} RIL_RadioFunctions;

其中最重要的是onRequest域,上层来的请求都由这个函数进行映射后转换成对应的AT命令发给硬件。

rild通过 RIL_register注册这一指针。

RIL_register中要完成的另外一个任务,就是打开前面提到的跟上层通信的 socket接口(s_fdListen是主接口,s_fdDebug供调试时使用)。

然后将这两个socket接口使用任务一中实现的机制进行注册(仅列出s_fdListen)

ril_event_set (&s_listen_event, s_fdListen, false,

listenCallback, NULL);

rilEventAddWakeup (&s_listen_event);

这样将两个socket加到任务一中建立起来多路复用I/O的检查句柄集合中,一旦有上层来的(调试)请求,event机制便能响应处理了。到这里启动流程已经分析完毕。

ril_event_set负责配置一个event,主要有两种event:

ril_event_add 添加使用多路I/O的event,它负责将其挂到队列,同时将event的通道 句柄fd加入到watch_table,然后通过select等待.

ril_timer_add 添加timer event,它将其挂在队列,同时重新计算最短超时时间.

无论哪种add,最后都会调用triggerEvLoop来刷新队列, 更新超时值或等待对象.

刷新之后, ril_event_loop从阻塞的位置,select返回,只有两种可能,一是超时,二是等待到了某I/O操作.

超时的处理在processTimeouts中,摘下超时的event,加入pending_list.

检查有 I/O操作的通道的处理在processReadReadies中,将超时的event加入 pending_list.

最后在 firePending中,检索pending_list的event并依次执行event->func.

这些操作完之后,计算新超时时间,并重新select阻塞于多路I/O.

前面的初始化流程已分析得知,初始化完成以后,队列上挂了3个event对象,分别是:

s_listen_event: 名为rild的socket,主要requeset & response通道

s_debug_event: 名为rild-debug的socket,调试用requeset & response通道(流程与s_listen_event基本相同,后面仅分析s_listen_event)

s_wakeupfd_event: 无名管道,用于队列主动唤醒(前面提到的队列刷新,就用它来 实现,请参考使用它的相关地方)。

明白了event队列的基本运行流程,我们可以来看看request是怎么传入和dispatch的了.

上层的部分,核心代码在 frameworks/base/telephony/java/com/android/internal/telephony/gsm/RIL.java, 这是android java框架处理radio(gsm)的核心组件.本文因为主要关注rild,也就是 驱动部分,所以这里只作简单介绍.

我们看一个具体的例子,RIL.java中的dial函数:

Java代码

publicvoid

dial (String address,intclirMode, Message result)

{

RILRequest rr = RILRequest.obtain(RIL_REQUEST_DIAL, result);

rr.mp.writeString(address);

rr.mp.writeInt(clirMode);

if(RILJ_LOGD) riljLog(rr.serialString() +"> "+ requestToString(rr.mRequest));

send(rr);

}

public void

dial (String address, int clirMode, Message result)

{

RILRequest rr = RILRequest.obtain(RIL_REQUEST_DIAL, result);

rr.mp.writeString(address);

rr.mp.writeInt(clirMode);

if (RILJ_LOGD) riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));

send(rr);

}

rr是以 RIL_REQUEST_DIAL为request号而申请的一个RILRequest对象.这个request 号在java框架和rild库中共享(参考RILConstants.java中这些值的由来)。

RILRequest初始化的时候, 会连接名为rild的socket(也就是rild中 s_listen_event绑定的socket),初始化数据传输的通道.

rr.mp是 Parcel对象,Parcel是一套简单的序列化协议,用于将对象(或对象的成 员)序列化成字节流,以供传递参数之用.这里可以看到 String address和int clirMode都是将依次序列化的成员.在这之前,rr初始化的时候,request号跟 request的序列号(自动生成的递增数),已经成为头两个将被序列化的成员.这为 后面的request解析打下了基础。

接下来是 send到handleMessage的流程,send将rr直接传递给另一个线程的 handleMessage,handleMessage执行data = rr.mp.marshall()执行序列化操作, 并将data字节流写入到rild socket。

接下来回到我们的rild,select发现rild socket有了请求链接的信号,导致 s_listen_event被挂入pending_list,执行event->func,即

static void listenCallback (int fd, short flags, void *param);。

接下来,s_fdCommand = accept(s_fdListen, (sockaddr *) &peeraddr, &socklen),获取传入的socket描述符,也就是上层的java RIL传入的连接。

然后,通过 record_stream_new建立起一个record_stream, 将其与s_fdCommand绑 定, 这里我们不关注record_stream 的具体流程, 我们来关注command event的回 调, processCommandsCallback函数, 从前面的event机制分析, 一旦s_fdCommand 上有数据, 此回调函数就会被调用. (略过onNewCommandConnect的分析)。

processCommandsCallback通过record_stream_get_next阻塞读取s_fdCommand上发 来的 数据, 直到收到一完整的request(request包的完整性由record_stream的机 制保证), 然后将其送达processCommandBuffer。

进入processCommandBuffer以后,我们就正式进入了命令的解析部分. 每个命令将 以RequestInfo的形式存在。

Java代码

typedef struct RequestInfo {

int32_t token;//this is not RIL_Token

CommandInfo *pCI;

struct RequestInfo *p_next;

charcancelled;

charlocal;// responses to local commands do not go back to command process

} RequestInfo;

typedef struct RequestInfo {

int32_t token; //this is not RIL_Token

CommandInfo *pCI;

struct RequestInfo *p_next;

char cancelled;

char local; // responses to local commands do not go back to command process

} RequestInfo;

这里的pRI就是一个RequestInfo结构指针, 从socket过来的数据流, 前面提到是 Parcel处理过的序列化字节流, 这里会通过反序列化的方法提取出来. 最前面的是 request号, 以及token域(request的递增序列号). 我们更关注这个request号, 前 面提到, 上层和rild之间, 这个号是统一的. 它的定义是一个包含ril_commands.h 的枚举, 在ril.cpp中

Java代码

staticCommandInfo s_commands[] = {

#include"ril_commands.h"

};

pRI 直接访问这个数组, 来获取自己的pCI.

这是一个CommandInfo结构:

typedef struct {

intrequestNumber;

void(*dispatchFunction) (Parcel &p, struct RequestInfo *pRI);

int(*responseFunction) (Parcel &p,void*response, size_t responselen);

} CommandInfo;

static CommandInfo s_commands[] = {

#include "ril_commands.h"

};

pRI 直接访问这个数组, 来获取自己的pCI.

这是一个CommandInfo结构:

typedef struct {

int requestNumber;

void (*dispatchFunction) (Parcel &p, struct RequestInfo *pRI);

int(*responseFunction) (Parcel &p, void *response, size_t responselen);

} CommandInfo;

基本解析到这里就完成了, 接下来, pRI被挂入pending的request队列, 执行具体 的pCI->dispatchFunction, 进行详细解析.

对dial而言, CommandInfo结构是这样初始化的:

{RIL_REQUEST_DIAL, dispatchDial, responseVoid},

这里执行dispatchFunction, 也就是dispatchDial这一函数.我们可以看到其实有 很多种类的dispatch function, 比如dispatchVoid, dispatchStrings, dispatchSIM_IO等等, 这些函数的区别, 在于Parcel传入的参数形式,Void就是不带参数的,Strings是以string[]做参数,又如Dial等,有自己的参数解析方式,以此类推。

request号和参数现在都有了,那么可以进行具体的request函数调用了 s_callbacks.onRequest(pRI->pCI->requestNumber, xxx, len, pRI)完成这一操作。

s_callbacks是上篇文章中提到的获取自libreference-ril的 RIL_RadioFunctions 结构指针,request请求在这里转入底层的libreference-ril处理,handler是 reference-ril.c中的onRequest。

onRequest进行一个简单的switch分发,我们依然来看 RIL_REQUEST_DIAL,流程是 onRequest-->requestDial-->at_send_command-->at_send_command_full-->at_send_command_full_nolock-->writeline。

requestDial中将命令和参数转换成对应的AT命令,调用公共send command接口 at_send_command。

除了这个接口之外,还有at_send_command_singleline,at_send_command_sms, at_send_command_multiline等,这是根据at返回值,以及发命令流程的类型来区别 的.比如at+csq这类,需要 at_send_command_singleline,而发送短信,因为有 prompt提示符">",传裸数据,结束符等一系列操作,需要专门用 at_send_command_sms来实现。

然后执行at_send_command_full,前面几个接口都会最终到这里,再通过一个互斥的 at_send_command_full_nolock调用,然后完成最终的写出操作,在writeline中,写出到初始化时打开的设备中。writeline返回之后,还有一些操作,如保存type等信息,供response回来时候使用, 以及一些超时处理. 不再详述。到这里,request的详细流程,就分析完毕了。

response信息的获取,是在第一篇初始化分析中,提到的 readerLoop中。由readline函数以‘行’为单位接收上来。

AT的response有两种,一是主动上报的,比如网络状态,短信,来电等都不需要经过请求,有一unsolicited词语专门描述。另一种才是真正意义上的 response,也就是命令的响应。

这里我们可以看到,所有的行,首先经过sms的自动上报筛选,因为短信的AT处理通常比较麻烦,无论收发都单独列出。这里是因为要即时处理这条短信消息(两行,标志+pdu),而不能拆开处理。处理函数为onUnsolicited(由 s_unsolHandler指向),我们等下介绍。

除开sms的特例,所有的line都要经过processLine,我们来看看这个流程:

processLine

|----no cmd--->handleUnsolicited //主动上报

|----isFinalResponseSuccess--->handleFinalResponse //成功,标准响应

|----isFinalResponseError--->handleFinalResponse //失败,标准响应

|----get '>'--->send sms pdu //收到>符号,发送sms数据再继续等待响应

|----switch s_type--->具体响应 //命令有具体的响应信息需要对应分析

我们这里主要关注handleUnsolicited自动上报(会调用到前面 smsUnsolicite也调用的onUnsolicite),以及switch s_type具体响应信息,另外具体响应需要handleFinalResponse这样的标准响应来最终完成。

1. onUnsolicite(主动上报响应)

static void onUnsolicited (const char *s, const char *sms_pdu);

短信的AT设计真是麻烦的主,以致这个函数的第二个参数完全就是为它准备的。response的主要的解析过程,由at_tok.c中的函数完成,其实就是字符串按块解析,具体的解析方式由每条命令或上报信息自行决定。这里不再详述,onUnsolicited只解析出头部(一般是+XXXX的形式),然后按类型决定下一步操作,操作为 RIL_onUnsolicitedResponse和RIL_requestTimedCallback两种。

a)RIL_onUnsolicitedResponse:

将unsolicited的信息直接返回给上层。通过Parcel传递,将 RESPONSE_UNSOLICITED,unsolResponse(request号)写入Parcel先,然后通过 s_unsolResponses数组,查找到对应的responseFunction完成进一步的的解析,存入Parcel中。最终通过 sendResponse将其传递回原进程。流程:

sendResponse-->sendResponseRaw-->blockingWrite-->write to s_fdCommand(前面建立起来的和上层框架的socket连接)

这些步骤之后有一些唤醒系统等其他操作。不再详述。

b)RIL_requestTimedCallback:

通过event机制(参考文章二)实现的timer机制,回调对应的内部处理函数。通过internalRequestTimedCallback将回调添加到event循环,最终完成callback上挂的函数的回调。比如 pollSIMState,onPDPContextListChanged等回调, 不用返回上层, 内部处理就可以。

2. switch s_type(命令的具体响应)及handleFinalResponse(标准响应):

命令的类型(s_type)在 send command的时候设置(参考文章二),有NO_RESULT,NUMERIC,SINGLELINE,MULTILINE几种,供不同的AT使用。比如AT+CSQ是singleline, 返回at+csq=xx,xx,再加一行OK,比如一些设置命令,就是no_result, 只有一行OK或ERROR。

这几个类型的解析都很相仿,通过一定的判断(比较AT头标记等),如果是对应的响应,就通过 addIntermediate挂到一个临时结果sp_response->p_intermediates队列里。如果不是对应响应,那它其实应该是穿插其中的自动上报,用onUnsolicite来处理。

具体响应,只起一个获取响应信息到临时结果,等待具体分析的作用。无论有无具体响应,最终都得以标准响应handleFinalResponse来完成,也就是接受到OK,ERROR等标准response来结束,这是大多数 AT命令的规范。

handleFinalResponse会设置s_commandcond这一object,也就是 at_send_command_full_nolock等待的对象。到这里,响应的完整信息已经完全获得,send command可以进一步处理返回的信息了(临时结果,以及标准返回的成功或失败,都在sp_response中)。

pp_outResponse参数将sp_response返回给调用at_send_command_full_nolock的函数。

继续我们在文章二的分析的话,这个函数其实是requestDial,不过requestDial忽略了响应,所以我们另外看个例子,如 requestSignalStrength,命令其实就是前面提到的at+csq:可以看到确实是通过 at_send_command_singleline来进行的操作,response在p_response中。p_response如果返回失败(也就是标准响应的ERROR等造成),则通过RIL_onRequestComplete发送返回数据给上层,结束命令。

如果成功,则进一步分析p_response->p_intermediates,同样是通过at_tok.c里的函数进行分析。并同样将结果通过RIL_onRequestComplete返回。

RIL_onRequestComplete:

RIL_onRequestComplete 和RIL_onUnsolicitedResponse很相仿,功能也一致。

通过Parcel来传递回上层,同样是先写入 RESPONSE_SOLICITED(区别于RESPONSE_UNSOLICITED),pRI->token(上层传下的request 号),错误码(send command的错误,不是AT响应)。如果有AT响应,通过访问pRI->pCI->responseFunction来完成具体 response的解析,并写入Parcel。

然后通过同样的途径:

sendResponse-->sendResponseRaw-->blockingWrite-->write to s_fdCommand

完成最终的响应传递。

ril.java_Android RIL的java框架相关推荐

  1. android ril.java_Android RIL的java框架

    Android RIL的Java部分也被分为了两个模块,RIL模块与Phone模块.其中RIL模块负责进行请求以及相应的处理,它将直接与RIL的原声代码进行通信.而Phone模块则向应用程序开发者提供 ...

  2. android ril.java_Android RIL学习

    1.Android RIL概念 Android RIL是基于telephony服务和raido硬件层的抽象层.Android的rild库是介于HAL接口与baseband modem之间,它提供了语音 ...

  3. 一个比 Spring Boot 快 44 倍的 Java 框架

    点击关注公众号,Java干货及时送达 转自:jdon 链接:http://jdon.com/48511 light-java是内置了undertow http服务器的REST微服务轻量Java框架,它 ...

  4. 别再用那些已经淘汰的技术了!2020 年 9 大顶级 Java 框架出炉!!

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 来源:Patricia Neil towardsdatascien ...

  5. 别再用那些已经淘汰的技术了!2020年9大顶级Java框架出炉!!

    作者:Patricia Neil https://towardsdatascience.com/9-top-java-frameworks-for-2020-1cc9d3c21f4c 诞生于1995年 ...

  6. Java框架JSON-RPC项目demo代码实战 + JAVA WEB配置虚拟目录(转自21天java web开发)

    Java框架JSON-RPC项目demo代码实战 备注  JAVA WEB配置虚拟目录(转自21天java web开发) https://blog.csdn.net/wjxbj/article/det ...

  7. Quarkus:一个Kubernetes原生Java框架

    Red Hat发布了Quarkus,这是一个为GraalVM和OpenJDK HotSpot量身定制的Kubernetes原生Java框架.Quarkus的目标是使Java成为Kubernetes和无 ...

  8. 如何在Eclipse中查看JDK以及JAVA框架的源码(转载)

    原文链接:http://www.cnblogs.com/outlooking/p/5243415.html 设置步骤如下: 1.点 "window"-> "Pref ...

  9. java框架知识_java框架知识点总结

    java框架知识点总结 面对Java丰富的知识资料,很多初学者难免觉得迷惘,下面是小编为大家整理的java框架知识点总结,欢迎参考~ 1 对象的初始化 (1) 非静态对象的初始化在创建对象时,对象所在 ...

最新文章

  1. 经常使用的时间同步server地址
  2. 【模板】 最小生成树
  3. 精通Android3笔记--第四章
  4. linux 压缩命令
  5. spring基础——外部引入属性文件创建bean
  6. iptables命令结构之目标
  7. UITableViewCell delete button 上有其它覆盖层
  8. 医疗器械信号输入输出部分,以及电气绝缘图,环境试验后的标准
  9. 免费剪辑音乐的软件都有哪些?音频剪辑方法分享?
  10. 360浏览器自动填充表单
  11. SVN:One or more files are in a conflicted state
  12. gateway网关调用报 reactor.netty.http.client.HttpClient.chunkedTransfer(Z)Lreactor/netty/http/client/HttpC
  13. trove mysql 镜像_trove 基于 centos7 制作 mysql5.5 镜像
  14. 三维重建中经常遇到的拓扑学概念的通俗解释
  15. 汇编语言各“标志位”含义
  16. 【论文翻译】使用区块链的非阻塞两阶段提交
  17. 《kafka权威指南》学习记录1
  18. MES系统具有哪些优势
  19. I/O多路复用之select、epoll的实现和区别 ,ET与LT模式
  20. 二进制和十进制之间的换算

热门文章

  1. 2009NOIP Hankson的趣味题(数学)
  2. 你怎么忍心让母亲等你那么久
  3. java正则表达式小括号的作用是啥_正则表达式小括号的作用
  4. java swing游戏源码-宇宙战争游戏,Java飞船射击游戏
  5. 分布式系统(四) 组播 Multicast
  6. 三菱PLC程序 三菱F5U 系列PLC程序,ST语言编程,配触摸屏程序,恒压测试设备,可递增,可一直保持恒压,递增的阶梯和递增的先后顺序都可在触摸屏上设置
  7. 11.22 这周的CTF刷题
  8. 2017 ICPC Naning Rake It In
  9. 什么是peer review
  10. 计算机主板pci插槽,PCI插槽:濒临淘汰依然有用