开机动画位置:device\xxx\common\logo\bootanimation\bootanimation.zip

desc.txt用于描述动画如何显示

文件格式如下:
WIDTH HEIGHT FPS
//WIDTH 图片宽度(px)
//HEIGHT 图片高度(px)
//FPS:每秒帧数
 
TYPE COUNT PAUSE PATH
//TYPE:动画类型,p:如果系统启动完毕,会中断播放。默认;

c:一直播放完毕,无论系统有没启动完毕
//COUNT:播放次数,0-无限循环
//PAUSE:本组播放完毕后,停留的帧数。

第一行的三个数字分别表示开机动画在屏幕中的显示宽度、高度以及帧速(fps)。

剩余的每一行都用来描述一个动画片断,这些行必须要以指定字符开头,后面紧跟着两个数字以及一个文件目录路径名称。第一个数字表示一个片断的循环显示次数,如果它的值等于0,那么就表示无限循环地显示该动画片断。第二个数字表示每一个片断在两次循环显示之间的时间间隔。这个时间间隔是以一个帧的时间为单位的。文件目录下面保存的是一系列png文件,这些png文件会被依次显示在屏幕中

动画的Start和stop控制:

动画的开始与结束是由属性控制的,由/system/bin/surfaceflinger来控制,然后相关的动画处理程序为/system/bin/bootanimation,在init.rc中指定。

bootanimation 需要 由property_set(“ctl.start”, “bootanim”);来启动进程,

由property_set(“ctl.stop”, “bootanim”);来关掉进程。

”service.bootanim.exit”:这个属性在bootanimation进程里会周期检查,=1时就退出动画,=0表示要播放动画。

主要过程:SurfaceFlinger 服务启动的过程中会修改系统属性"ctl.start"的值,以通知init进程启动bootanim来显示开机动画。当系统关键服务启动完毕后,由AMS通知SurfaceFlinger修改系统属性"ctl.stop"来通知init进程停止执行bootanim关闭动画。

bootanimation

开机动画是由应用程序bootanimation来负责显示的,先看一下其rc文件。

frameworks\base\cmds\bootanimation\bootanim.rc

service bootanim /system/bin/bootanimationclass core animationuser graphicsgroup graphics audiodisabled //系统启动时,不会自动启动bootanimationoneshot  //只启动一次ioprio rt 0task_profiles MaxPerformance

surfaceflinger

frameworks\native\services\surfaceflinger\surfaceflinger.rc

service surfaceflinger /system/bin/surfaceflingerclass core animationuser systemgroup graphics drmrpc readproccapabilities SYS_NICEonrestart restart zygotetask_profiles HighPerformancesocket pdx/system/vr/display/client     stream 0666 system graphics u:object_r:pdx_display_client_endpoint_socket:s0socket pdx/system/vr/display/manager    stream 0666 system graphics u:object_r:pdx_display_manager_endpoint_socket:s0socket pdx/system/vr/display/vsync      stream 0666 system graphics u:object_r:pdx_display_vsync_endpoint_socket:s0

surfaceflinger的启动时机

在高版本的Android上,如AndroidP,surfaceflinger进程并不是直接在init.rc文件中启动的,而是通过Android.bp文件去包含启动surfaceflinger.rc文件,然后在该文件中再去启动surfaceflinger:

frameworks\native\services\surfaceflinger\Android.bp

cc_binary {name: "surfaceflinger",defaults: ["libsurfaceflinger_binary"],init_rc: ["surfaceflinger.rc"],srcs: [":surfaceflinger_binary_sources",// Note: SurfaceFlingerFactory is not in the filegroup so that it// can be easily replaced."SurfaceFlingerFactory.cpp",],shared_libs: ["libSurfaceFlingerProp",],logtags: ["EventLog/EventLogTags.logtags"],
}

surfaceflinger启动了,就会跑到它的main函数:

SurfaceFlinger服务的入口在main_surfaceflinger.cpp中

frameworks\native\services\surfaceflinger\main_surfaceflinger.cpp

int main(int, char**) {signal(SIGPIPE, SIG_IGN);hardware::configureRpcThreadpool(1 /* maxThreads */,false /* callerWillJoin */);startGraphicsAllocatorService();// When SF is launched in its own process, limit the number of// binder threads to 4.ProcessState::self()->setThreadPoolMaxThreadCount(4);// start the thread poolsp<ProcessState> ps(ProcessState::self());ps->startThreadPool();// instantiate surfaceflingersp<SurfaceFlinger> flinger = surfaceflinger::createSurfaceFlinger();setpriority(PRIO_PROCESS, 0, PRIORITY_URGENT_DISPLAY);set_sched_policy(0, SP_FOREGROUND);// Put most SurfaceFlinger threads in the system-background cpuset// Keeps us from unnecessarily using big cores// Do this after the binder thread pool initif (cpusets_enabled()) set_cpuset_policy(0, SP_SYSTEM);// initialize before clients can connectflinger->init();// publish surface flingersp<IServiceManager> sm(defaultServiceManager());sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false,IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL | IServiceManager::DUMP_FLAG_PROTO);startDisplayService(); // dependency on SF getting registered aboveif (SurfaceFlinger::setSchedFifo(true) != NO_ERROR) {ALOGW("Couldn't set to SCHED_FIFO: %s", strerror(errno));}// run surface flinger in this threadflinger->run();return 0;
}

frameworks\native\services\surfaceflinger\SurfaceFlinger.cpp

init方法中 start mStartPropertySetThread

    const bool presentFenceReliable =!getHwComposer().hasCapability(hal::Capability::PRESENT_FENCE_IS_NOT_RELIABLE);mStartPropertySetThread = getFactory().createStartPropertySetThread(presentFenceReliable);if (mStartPropertySetThread->Start() != NO_ERROR) {ALOGE("Run StartPropertySetThread failed!");}ALOGV("Done initializing");

frameworks\native\services\surfaceflinger\SurfaceFlingerDefaultFactory.cpp

sp<StartPropertySetThread> DefaultFactory::createStartPropertySetThread(bool timestampPropertyValue) {return new StartPropertySetThread(timestampPropertyValue);
}

frameworks\native\services\surfaceflinger\StartPropertySetThread.cpp

#include <cutils/properties.h>
#include "StartPropertySetThread.h"namespace android {StartPropertySetThread::StartPropertySetThread(bool timestampPropertyValue):Thread(false), mTimestampPropertyValue(timestampPropertyValue) {}status_t StartPropertySetThread::Start() {return run("SurfaceFlinger::StartPropertySetThread", PRIORITY_NORMAL);
}bool StartPropertySetThread::threadLoop() {// Set property service.sf.present_timestamp, consumer need check its readinessproperty_set(kTimestampProperty, mTimestampPropertyValue ? "1" : "0");// Clear BootAnimation exit flagproperty_set("service.bootanim.exit", "0");// Start BootAnimation if not startedproperty_set("ctl.start", "bootanim");// Exit immediatelyreturn false;
}} /

这里设置属性【service.bootanim.exit】并采用【ctl.start】的方式启动开机动画:

在这之后,开机动画就会启动,由bootanimation进程实现具体动画播放

bootAnimation的启动

名称等于"bootanim"的服务所对应的应用程序为/system/bin/bootanimation,应用程序入口函数的实现在frameworks/base/cmds/bootanimation/bootanimation_main.cpp

int main()
{setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);bool noBootAnimation = bootAnimationDisabled();ALOGI_IF(noBootAnimation,  "boot animation disabled");if (!noBootAnimation) {sp<ProcessState> proc(ProcessState::self());ProcessState::self()->startThreadPool();// create the boot animation object (may take up to 200ms for 2MB zip)sp<BootAnimation> boot = new BootAnimation(audioplay::createAnimationCallbacks());waitForSurfaceFlinger();boot->run("BootAnimation", PRIORITY_DISPLAY);ALOGV("Boot animation set up. Joining pool.");IPCThreadState::self()->joinThreadPool();}return 0;
}

首先检查系统属性“debug.sf.nobootnimaition”的值是否等于0。如果不等于的话,那么接下来就会启动一个Binder线程池,并且创建一个BootAnimation对象。这个Binder线程用于同SurfaceFlinger服务通信。

frameworks\base\cmds\bootanimation\BootAnimation.cpp

BootAnimation类间接地继承了RefBase类,并且重写了RefBase类的成员函数onFirstRef,因此,当一个BootAnimation对象第一次被智能指针引用的时,这个BootAnimation对象的成员函数onFirstRef就会被调用。其中几个重要的函数说明如下:

onFirstRef()—— 属于其父类RefBase,该函数在强引用sp新增引用计数時调用,就是当有sp包装的类初始化的时候调用;
binderDied() ——当对象死掉或者其他情况导致该Binder结束时,就会回调binderDied()方法;
readyToRun() ——Thread执行前的初始化工作;
threadLoop() ——每个线程类都要实现的,在这里定义thread的执行内容。这个函数如果返回true,且没有调用requestExit(),则该函数会再次执行;如果返回false,则threadloop中的内容仅仅执行一次,线程就会退出。
其他函数简述如下:

android()——显示系统默认的开机画面;
movie()——显示用户自定义的开机动画;
loadAnimation(const String8&)——加载动画;
playAnimation(const Animation&)——播放动画;
checkExit()——检查是否退出动画;

void BootAnimation::onFirstRef() {status_t err = mSession->linkToComposerDeath(this);SLOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err));if (err == NO_ERROR) {// Load the animation content -- this can be slow (eg 200ms)// called before waitForSurfaceFlinger() in main() to avoid waitALOGD("%sAnimationPreloadTiming start time: %" PRId64 "ms",mShuttingDown ? "Shutdown" : "Boot", elapsedRealtime());preloadAnimation();ALOGD("%sAnimationPreloadStopTiming start time: %" PRId64 "ms",mShuttingDown ? "Shutdown" : "Boot", elapsedRealtime());}
}

mSession是BootAnimation类的一个成员变量,它的类型为SurfaceComposerClient,是用来和SurfaceFlinger执行Binder进程间通信的,它是在BootAnimation类的构造函数中创建的

mSession = new SurfaceComposerClient();

由于BootAnimation类引用了SurfaceFlinger服务,因此,当SurfaceFlinger服务意外死亡时,BootAnimation类就需要得到通知,这是通过调用成员变量mSession的成员函数linkToComposerDeath来注册SurfaceFlinger服务的死亡接收通知来实现的。
 
        BootAnimation类继承了Thread类,因此,当bootanimation_main.cpp调用了Thread的成员函数run之后,系统就会创建一个线程,这个线程在第一次运行之前,会调用BootAnimation类的成员函数readyToRun来执行一些初始化工作,后面再调用BootAnimation类的成员函数threadLoop来显示第三个开机画面。

bool BootAnimation::threadLoop() {bool result;// We have no bootanimation file, so we use the stock android logo// animation.if (mZipFileName.isEmpty()) {result = android();} else {result = movie();}mCallbacks->shutdown();eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);eglDestroyContext(mDisplay, mContext);eglDestroySurface(mDisplay, mSurface);mFlingerSurface.clear();mFlingerSurfaceControl.clear();eglTerminate(mDisplay);eglReleaseThread();IPCThreadState::self()->stopProcess();return result;
}

如果mZipFileName不为空,那么接下来就会调用BootAnimation类的成员函数android来显示系统默认的开机动画,否则的话,就会调用BootAnimation类的成员函数movie来显示用户自定义的开机动画

bool BootAnimation::findBootAnimationFileInternal(const std::vector<std::string> &files) {for (const std::string& f : files) {if (access(f.c_str(), R_OK) == 0) {mZipFileName = f.c_str();return true;}}return false;
}

bootanim的关闭

init启动zygote进程之后,由zygote孵化出了system_server,然后system_server启动了各种各种的系统所需的服务,其中就有AMS,AMS启动并ready后,会执行startHomeActivityLocked:

void SurfaceFlinger::bootFinished()
{if (mBootFinished == true) {ALOGE("Extra call to bootFinished");return;}mBootFinished = true;if (mStartPropertySetThread->join() != NO_ERROR) {ALOGE("Join StartPropertySetThread failed!");}const nsecs_t now = systemTime();const nsecs_t duration = now - mBootTime;ALOGI("Boot is finished (%ld ms)", long(ns2ms(duration)) );mFrameTracer->initialize();mTimeStats->onBootFinished();// wait patiently for the window manager deathconst String16 name("window");mWindowManager = defaultServiceManager()->getService(name);if (mWindowManager != 0) {mWindowManager->linkToDeath(static_cast<IBinder::DeathRecipient*>(this));}if (mVrFlinger) {mVrFlinger->OnBootFinished();}// stop boot animation// formerly we would just kill the process, but we now ask it to exit so it// can choose where to stop the animation.property_set("service.bootanim.exit", "1");const int LOGTAG_SF_STOP_BOOTANIM = 60110;LOG_EVENT_LONG(LOGTAG_SF_STOP_BOOTANIM,ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));sp<IBinder> input(defaultServiceManager()->getService(String16("inputflinger")));static_cast<void>(schedule([=] {if (input == nullptr) {ALOGE("Failed to link to input service");} else {mInputFlinger = interface_cast<IInputFlinger>(input);}readPersistentProperties();mPowerAdvisor.onBootFinished();mBootStage = BootStage::FINISHED;if (property_get_bool("sf.debug.show_refresh_rate_overlay", false)) {enableRefreshRateOverlay(true);}}));
}

AMS在systemReady后会启动launcher

  if (bootingSystemUser) {t.traceBegin("startHomeOnAllDisplays");mAtmInternal.startHomeOnAllDisplays(currentUserId, "systemReady");t.traceEnd();}

frameworks\base\services\core\java\com\android\server\wm\ActivityTaskManagerInternal.java

public abstract boolean startHomeOnAllDisplays(int userId, String reason);

frameworks\base\services\core\java\com\android\server\wm\ActivityTaskManagerService.java

final class LocalService extends ActivityTaskManagerInternal {
    @Overridepublic boolean startHomeOnAllDisplays(int userId, String reason) {synchronized (mGlobalLock) {return mRootWindowContainer.startHomeOnAllDisplays(userId, reason);}}

frameworks\base\services\core\java\com\android\server\wm\RootWindowContainer.java

    boolean startHomeOnAllDisplays(int userId, String reason) {boolean homeStarted = false;for (int i = getChildCount() - 1; i >= 0; i--) {final int displayId = getChildAt(i).mDisplayId;homeStarted |= startHomeOnDisplay(userId, reason, displayId);}return homeStarted;}void startHomeOnEmptyDisplays(String reason) {for (int i = getChildCount() - 1; i >= 0; i--) {final DisplayContent display = getChildAt(i);for (int tdaNdx = display.getTaskDisplayAreaCount() - 1; tdaNdx >= 0; --tdaNdx) {final TaskDisplayArea taskDisplayArea = display.getTaskDisplayAreaAt(tdaNdx);if (taskDisplayArea.topRunningActivity() == null) {startHomeOnTaskDisplayArea(mCurrentUser, reason, taskDisplayArea,false /* allowInstrumenting */, false /* fromHomeKey */);}}}}boolean startHomeOnDisplay(int userId, String reason, int displayId) {return startHomeOnDisplay(userId, reason, displayId, false /* allowInstrumenting */,false /* fromHomeKey */);}boolean startHomeOnDisplay(int userId, String reason, int displayId, boolean allowInstrumenting,boolean fromHomeKey) {// Fallback to top focused display or default display if the displayId is invalid.if (displayId == INVALID_DISPLAY) {final ActivityStack stack = getTopDisplayFocusedStack();displayId = stack != null ? stack.getDisplayId() : DEFAULT_DISPLAY;}final DisplayContent display = getDisplayContent(displayId);boolean result = false;for (int tcNdx = display.getTaskDisplayAreaCount() - 1; tcNdx >= 0; --tcNdx) {final TaskDisplayArea taskDisplayArea = display.getTaskDisplayAreaAt(tcNdx);result |= startHomeOnTaskDisplayArea(userId, reason, taskDisplayArea,allowInstrumenting, fromHomeKey);}return result;}

frameworks\base\services\core\java\com\android\server\wm\ActivityTaskManagerService.java

    void postFinishBooting(boolean finishBooting, boolean enableScreen) {mH.post(() -> {if (finishBooting) {mAmInternal.finishBooting();}if (enableScreen) {mInternal.enableScreenAfterBoot(isBooted());}});}
        @Overridepublic void enableScreenAfterBoot(boolean booted) {synchronized (mGlobalLock) {writeBootProgressEnableScreen(SystemClock.uptimeMillis());mWindowManager.enableScreenAfterBoot();updateEventDispatchingLocked(booted);}}

frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.java

    public void enableScreenAfterBoot() {synchronized (mGlobalLock) {ProtoLog.i(WM_DEBUG_BOOT, "enableScreenAfterBoot: mDisplayEnabled=%b "+ "mForceDisplayEnabled=%b mShowingBootMessages=%b mSystemBooted=%b. "+ "%s",mDisplayEnabled, mForceDisplayEnabled, mShowingBootMessages, mSystemBooted,new RuntimeException("here").fillInStackTrace());if (mSystemBooted) {return;}mSystemBooted = true;hideBootMessagesLocked();// If the screen still doesn't come up after 30 seconds, give// up and turn it on.mH.sendEmptyMessageDelayed(H.BOOT_TIMEOUT, 30 * 1000);}mPolicy.systemBooted();performEnableScreen();}

enableScreenAfterBoot()经过多次调用就会执行WMS的performEnableScreen()方法,在此方法中我们就可以看到surfaceflinger的身影了,通过transact调用发送BOOT_FINISHED的消息给surfaceflinger。

    frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.javaprivate void performEnableScreen() {...try {IBinder surfaceFlinger = ServiceManager.getService("SurfaceFlinger");if (surfaceFlinger != null) {Slog.i(TAG_WM, "******* TELLING SURFACE FLINGER WE ARE BOOTED!");Parcel data = Parcel.obtain();data.writeInterfaceToken("android.ui.ISurfaceComposer");surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION, // BOOT_FINISHEDdata, null, 0);data.recycle();}} catch (RemoteException ex) {Slog.e(TAG_WM, "Boot completed: SurfaceFlinger is dead!");}...}

frameworks\native\libs\gui\include\gui\ISurfaceComposer.h

class BnSurfaceComposer: public BnInterface<ISurfaceComposer> {
public:enum ISurfaceComposerTag {// Note: BOOT_FINISHED must remain this value, it is called from// Java by ActivityManagerService.BOOT_FINISHED = IBinder::FIRST_CALL_TRANSACTION,

WMS最终通过binder调用,经过ISurfaceComposer处理,最终通知SurfaceFlinger关闭开机动画。

frameworks\native\services\surfaceflinger\SurfaceFlinger.cpp
void SurfaceFlinger::bootFinished()
{if (mStartPropertySetThread->join() != NO_ERROR) {ALOGE("Join StartPropertySetThread failed!");}const nsecs_t now = systemTime();const nsecs_t duration = now - mBootTime;ALOGI("Boot is finished (%ld ms)", long(ns2ms(duration)) );// wait patiently for the window manager deathconst String16 name("window");sp<IBinder> window(defaultServiceManager()->getService(name));if (window != 0) {window->linkToDeath(static_cast<IBinder::DeathRecipient*>(this));}if (mVrFlinger) {mVrFlinger->OnBootFinished();}// stop boot animation// formerly we would just kill the process, but we now ask it to exit so it// can choose where to stop the animation.property_set("service.bootanim.exit", "1");const int LOGTAG_SF_STOP_BOOTANIM = 60110;LOG_EVENT_LONG(LOGTAG_SF_STOP_BOOTANIM,ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));sp<LambdaMessage> readProperties = new LambdaMessage([&]() {readPersistentProperties();});postMessageAsync(readProperties);
}

至此开机动画结束。

frameworks\base\cmds\bootanimation\BootAnimation.cpp

void BootAnimation::checkExit() {// Allow surface flinger to gracefully request shutdownchar value[PROPERTY_VALUE_MAX];property_get(EXIT_PROP_NAME, value, "0");int exitnow = atoi(value);if (exitnow) {requestExit();}
}

requestExit(); kill掉bootanime进程

system\core\libutils\Threads.cpp

void Thread::requestExit()
{Mutex::Autolock _l(mLock);mExitPending = true;
}

至此bootanime进程死亡。

Android 开机动画的启动相关推荐

  1. Android 开机动画(bootanimation)启动

    Android 开机动画(bootanimation)启动 Android 开机动画启动 前言 一.简单的对话 二.过程 总结 前言 开机动画应该算是我接触的第一个AOSP的Native程序,网上讲解 ...

  2. Android 开机动画启动、播放、退出流程(android 10)

    Android 开机动画启动流程 (android 10) 1 开机动画启动流程 我们先来看一下开机动画是如何启动,并开始播放的. 通过系统启动流程分析可以得知,在系统内核启动后,会启动第一个init ...

  3. android开机动画多长时间_Android系统开机动画的一生

    前言 在上篇文章[Android从上电到加载launcher,都发生了啥]中,简单介绍了Android系统从上电到加载launcher的流程,但比较粗略,特别是init之后,开机动画如何启动,又如何结 ...

  4. android logo:内核、android开机动画

    android logo:内核.android开机动画 关键词:android 开机logo  开机动画 initlogo.rle   bootanimation  desc.txt 平台信息: 内核 ...

  5. Android开机动画bootanimation

    android开机动画详细分析可以参见http://blog.csdn.net/luoshengyang/article/details/7691321 引用老罗的文章,写的太好了. 以下介绍一些相关 ...

  6. android开机动画bootanimation 分析

    转载地址:http://blog.csdn.net/myvest/article/details/50856199 android开机动画详细分析可以参见http://blog.csdn.net/lu ...

  7. android+动画打包命令,Android 开机动画客制化

    Android开机动画总共有三个过程.第一个开机动画是在Kenel启动时显示的,第二个开机动画是在init进程启动时显示的,这两个都是静态图片.第三个动画是在系统服务启动过程中显示的,他是一个动态图片 ...

  8. android 刷机动画,Android开机动画修改方法

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 Android开机动画有两种修改方法,android 2.0及之后,使用bootanimation程序显示开机画面,如需修改开机画面,不用修改代码,只需按 ...

  9. 触觉智能分享-修改Android开机动画

    本文适用于我司基于RK PX30开发的系列主板(IDO-SOM3020/IDO-EVB3020)修改开机画面,搭载四核A35处理器,主频高达1.5  GHz,采用 Mali-G31 MP2 GPU,支 ...

  10. Android:Android开机动画

    Android系统的开机画面显示过程分析 三个开机画面修改方式:目录 DIY固件系列教程--实现开机LOGO三屏动画的完全替换 一.第一个开机画面是在内核启动的过程中出现的,它是一个静态的画面 二.第 ...

最新文章

  1. java 变量的线程可见性_Java多线程——变量可见性
  2. jconsole工具使用----jvm内存泄漏问题
  3. Git命令,合并分支到master,并提交远程仓库,将本地分支推送到远程仓库
  4. Spring应用架构
  5. linux系统运行powerbi,使用 Power BI 服务 - Power BI | Microsoft Docs
  6. 如何快速部署一个Elasticsearch集群?
  7. python get请求 url传参_requests的get请求url参数、url重定向处理及cookies
  8. 什么是模态窗口?本文带你了解模态窗口的本质
  9. STM32F7以太网HAL库源文件(stm32f7xx_hal_eth.c)笔记
  10. word如何一次性删除所有批注
  11. AI行为识别:安防主动预警
  12. Formal Verification (一) 形式验证的分类、发展、适用场景
  13. C++ 算术基本定理
  14. ubuntu使用minicom连接交换机/路由器Console口详细教程
  15. 写给20岁读者的一些人生建议
  16. Cool Edit之生成.pk文件问题
  17. 从技术 Leader 的招聘需求看,如何转岗为当前紧缺的大数据相关人才?
  18. mediasoup json 通信协议2--room.js和router.cpp信令
  19. Maipo for Mac新浪微博客户端
  20. 与计算机硬件本身密切相关的是什么语言,计算机硬件单选试题及答案

热门文章

  1. 微软iis服务器并发量,IIS并发连接数
  2. CocoStudio1.3 场景编辑器使用
  3. Struts+Hibernate系列教材 (一)- 整合Struts和Hibernate教程
  4. 读书-算法《程序设计导引及在线实践》-简单计算题1:鸡兔同笼
  5. JAVA视频MP4文件加密,Html5播放器调用
  6. SQL 格式化输出 千分位 ¥货币格式
  7. c语言判断奇偶素数,用C语言如何判断素数
  8. 中国营养与健康调查(CHNS)2018年最新数据
  9. 高德地图上加入坐标拾取、轨迹、点标记、poi搜索等
  10. linux(中文输入法,显示语言)