本文代码基于Android 9.0

文章目录

  • 概述
  • Telephony应用层
    • PhoneApp简介
    • Phone相关业务
    • Phone的作用
    • PhoneApp的启动流程
  • Telephony框架层
    • Telephony应用框架层启动流程
  • 总结

概述

这里讲的PhoneApp启动过程其实是讲Telephony应用框架层的启动过程。Phone实例是Telephony框架对象,可以向该对象发起各种通讯相关的请求,Phone对象是Telephony整个框架的核心,负责与抽象出的中间层RIL交互,进而间接与modem端进行交互。
Phone对象是在PhoneApp这个application初始化过程中被创建的。

Telephony应用层

PhoneApp简介

首先PhoneApp的主要功能是什么呢?

  • 负责实例化创建Telephony应用框架层(phone),且和phone一起运行在com.android.phone进程中
  • 管理(CallManager),控制通话相关的UI、功能操作、业务逻辑实现

这里我们需要知道,Android系统中多个应用程序(Apk)是可以运行在同一个进程中的,PhoneApp就是这样,连同Stk、SMSProvider、TelephonyProvider等模块都运行在com.android.phone进程中(简称phone进程)。

Phone相关业务
业务名称 业务功能
CDMA 业务 支持CDMA业务的手机
GSM 业务 支持GSM业务的手机
IMS业务 支持IMS业务的手机
EUICC业务 支持EUICC的手机,embedded SIM,嵌入式SIM卡
MBMS业务 MBMS:Multimedia Broadcast Multicast Service,多媒体广播多播业务
CAT业务 STK相关业务的支持
DataConnection 数据连接相关的业务
SIP业务 Session Initiation Protocol,VoLTE,ViLTE,VoWifi
Phone的作用
  • 注册监听事件,及时上报消息(Call状态变化、Service状态变化、新来电等等)
  • 间接的为其他类提供跟modem交互的服务
PhoneApp的启动流程

请看下面流程图:

根据此图可看出PhoneApp应用的整个启动过程以及其在启动后具体做了哪些工作,接下来我们根据这个类图进行PhoneApp启动流程梳理:
PhoneApp的路径是:packages\services\Telephony,我们来看看它的AndroidManifest.xml内容:

 <application android:name="PhoneApp"android:persistent="true"android:label="@string/phoneAppLabel"android:icon="@mipmap/ic_launcher_phone"android:allowBackup="false"android:supportsRtl="true"android:usesCleartextTraffic="true"android:defaultToDeviceProtectedStorage="true"android:directBootAware="true">......</application>
  • persistent属性的作用:开机时被系统自动初始化;该模块所在的进程(com.android.phone)由于任何原因被kill掉之后,都会自动重启(这种情况只针对系统内置app,第三方安装的app不会被重启)
  • directBootAware属性的作用:该属性使得用户在加密状态(未解锁)下能够正常使用一些手机功能,如闹钟、接电话等,但不允许访问私有应用数据

系统在启动时会先启动SystemService,SystemService会启动各种系统服务,包括AMS:
private void startBootstrapServices() {
......
mActivityManagerService = mSystemServiceManager.startService(
ActivityManagerService.Lifecycle.class).getService(); mActivityManagerService.setSystemServiceManager(mSystemServiceManager); mActivityManagerService.setInstaller(installer);
......
}
而ActivityManagerService的SystemReady方法中就有一个启动PersistentApps的方法,且该应用还需要MATCH_DIRECT_BOOT_AWARE,而PhoneApp正满足此需求:
public void systemReady(final Runnable goingCallback, TimingsTraceLog traceLog) {
......
startPersistentApps(PackageManager.MATCH_DIRECT_BOOT_AWARE);
......
}

由此可见,PhoneApk会在系统启动起来的时候由系统自动启动,启动后会先执行PhoneApp的onCreate()方法:

public class PhoneApp extends Application {PhoneGlobals mPhoneGlobals;TelephonyGlobals mTelephonyGlobals;public PhoneApp() {}@Overridepublic void onCreate() {if (UserHandle.myUserId() == 0) {// We are running as the primary user, so should bring up the// global phone state.mPhoneGlobals = new PhoneGlobals(this);// 创建PhoneGlobals,即Phone的全局状态mPhoneGlobals.onCreate();// 创建TelephonyGlobals,即Telephony应用的全局状态// TelephonyGlobals在整个工程中没看到使用的地方,类中只是创建个实例和初始化TtyMananger,看解释是与PSTN call相关mTelephonyGlobals = new TelephonyGlobals(this);mTelephonyGlobals.onCreate();}}
}

PhoenApp的onCreate()方法主要创建了mPhoneGlobals对象和mTelephonyGlobals对象,并执行了两个对象的onCreate()方法,接下来的流程也将围绕这两个对象的onCreate展开:

public void onCreate() {if (VDBG) Log.v(LOG_TAG, "onCreate()...");ContentResolver resolver = getContentResolver();// Cache the "voice capable" flag.// This flag currently comes from a resource (which is// overrideable on a per-product basis):// 缓存 "voice capable" 标志,来自资源文件的配置sVoiceCapable = getResources().getBoolean(com.android.internal.R.bool.config_voice_capable);if (mCM == null) {// Initialize the telephony frameworkPhoneFactory.makeDefaultPhones(this);// 初始化telephony framework// Start TelephonyDebugService After the default phone is created.Intent intent = new Intent(this, TelephonyDebugService.class);startService(intent);mCM = CallManager.getInstance();// 实例化通话管理类CallManager,并把phone注册进去for (Phone phone : PhoneFactory.getPhones()) {mCM.registerPhone(phone);}// Create the NotificationMgr singleton, which is used to display// status bar icons and control other status bar behavior.notificationMgr = NotificationMgr.init(this);// 创建NotificationMgr单例,用于显示状态栏图标和控制其他状态栏行为// If PhoneGlobals has crashed and is being restarted, then restart.// 发送EVENT_RESTART_SIP信号,如果PhoneGlobals已崩溃并正在重新启动,则重新启动。// 走到这里已经存在phone进程,mHandler在处理EVENT_RESTART_SIP消息时,会从UserManager// 中查找当前用户的状态,如果处于unlocked状态,才调用startSipServicemHandler.sendEmptyMessage(EVENT_RESTART_SIP);// Create an instance of CdmaPhoneCallState and initialize it to IDLE// 创建一个CdmaPhoneCallState实例并将其初始化为IDLE, 通话状态包括IDLE/SINGLE_ACTIVE/THRWAY_ACTIVE/CONF_CALLcdmaPhoneCallState = new CdmaPhoneCallState();cdmaPhoneCallState.CdmaPhoneCallStateInit();// before registering for phone state changes// 获取电源管理器mPowerManager、唤醒锁mWakeLock、锁屏管理器mKeyguardManagermPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);mWakeLock = mPowerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, LOG_TAG);// 当我们不关心显示器时,用于保持处理器唤醒的锁定mPartialWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK| PowerManager.ON_AFTER_RELEASE, LOG_TAG);mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);// Get UpdateLock to suppress system-update related events (e.g. dialog show-up)// during phone calls.// 为了屏蔽在通话过程中提示系统更新相关事件,创建UpdateLock对象mUpdateLock = new UpdateLock("phone");if (DBG) Log.d(LOG_TAG, "onCreate: mUpdateLock: " + mUpdateLock);CallLogger callLogger = new CallLogger(this, new CallLogAsync());// 实例化通话路由管理器callGatewayManager = CallGatewayManager.getInstance();// Create the CallerInfoCache singleton, which remembers custom ring tone and// send-to-voicemail settings.// The asynchronous caching will start just after this call.// 实例化通话信息缓存类,它会记住自定义铃声发送到语音邮件设置。 异步缓存将在此调用之后立即启动callerInfoCache = CallerInfoCache.init(this);// 实例化phone接口管理类,这个类可用于访问/修改phone的状态,比如通话状态/数据连接/网络状态等等phoneMgr = PhoneInterfaceManager.init(this, PhoneFactory.getDefaultPhone());// 实例化CarrierConfigLoader(运营商配置加载相关)configLoader = CarrierConfigLoader.init(this);// Create the CallNotifier singleton, which handles// asynchronous events from the telephony layer (like// launching the incoming-call UI when an incoming call comes// in.)notifier = CallNotifier.init(this); // 实例化CallNotifier,它处理来自电话层的异步事件(比如在有来电时启动来电呼叫UI)PhoneUtils.registerIccStatus(mHandler, EVENT_SIM_NETWORK_LOCKED); // 注册ICC的EVENT_SIM_NETWORK_LOCKED状态监听处理// register for MMI/USSDmCM.registerForMmiComplete(mHandler, MMI_COMPLETE, null);// 注册MMI/USSD事件的监听处理// register connection tracking to PhoneUtilsPhoneUtils.initializeConnectionHandler(mCM);// 注册连接事件的监听处理,ConnectionHandler处理PHONE_STATE_CHANGED事件// Register for misc other intent broadcasts// 注册一些广播事件处理IntentFilter intentFilter =new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);intentFilter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);intentFilter.addAction(TelephonyIntents.ACTION_RADIO_TECHNOLOGY_CHANGED);intentFilter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);intentFilter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);intentFilter.addAction(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);intentFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);registerReceiver(mReceiver, intentFilter);IntentFilter sipIntentFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);sipIntentFilter.addAction(SipManager.ACTION_SIP_SERVICE_UP);sipIntentFilter.addAction(SipManager.ACTION_SIP_CALL_OPTION_CHANGED);sipIntentFilter.addAction(SipManager.ACTION_SIP_REMOVE_PHONE);registerReceiver(mSipReceiver, sipIntentFilter);mCarrierVvmPackageInstalledReceiver.register(this);//set the default values for the preferences in the phone.// 设置手机中偏好设置的默认值PreferenceManager.setDefaultValues(this, R.xml.network_setting_fragment, false);PreferenceManager.setDefaultValues(this, R.xml.call_feature_setting, false);// Make sure the audio mode (along with some// audio-mode-related state of our own) is initialized// correctly, given the current state of the phone.PhoneUtils.setAudioMode(mCM);// 在给定手机的当前状态的情况下,确保正确初始化音频模式(以及我们自己的一些音频模式相关状态)}// XXX pre-load the SimProvider so that it's readyresolver.getType(Uri.parse("content://icc/adn"));// 尝试加载SimProvider以使其准备就绪// TODO: Register for Cdma Information Records// phone.registerCdmaInformationRecord(mHandler, EVENT_UNSOL_CDMA_INFO_RECORD, null);// Read HAC settings and configure audio hardware// 读取HAC设置并配置音频硬件if (getResources().getBoolean(R.bool.hac_enabled)) {int hac = android.provider.Settings.System.getInt(getContentResolver(),android.provider.Settings.System.HEARING_AID,0);AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);audioManager.setParameter(SettingsConstants.HAC_KEY,hac == SettingsConstants.HAC_ENABLED? SettingsConstants.HAC_VAL_ON : SettingsConstants.HAC_VAL_OFF);}}

PhoneGlobals.onCreate()主要做了以下工作:

  • 初始化 telephony framework
  • 实例化各种管理类CallManager,NotificationMgr,PowerManager,KeyguardManager,CallGatewayManager,PhoneInterfaceManager
  • 设置Phone的一些初始化配置,注册相关广播,初始化SimProvider,配置音频硬件等

其中,各大管理类和各种配置可根据注释以及源码自己查看,这里继续解析Phone,即Telephony framework (PhoneFactory.makeDefaultPhones(this));

Telephony框架层

Telephony应用框架层启动流程

代码位置:/frameworks/opt/telephony/src/java/com/android/internal/telephony/PhoneFactory.java

public static void makeDefaultPhone(Context context) {synchronized (sLockProxyPhones) {if (!sMadeDefaults) {sContext = context;// create the telephony device controller.TelephonyDevController.create();// 实例化telephony硬件资源控制接口,这里的硬件资源包括SIM/MODEM/RILint retryCount = 0;for(;;) {// 通过尝试创建地址为"com.android.internal.telephony"的LocalServerSocket来中断循环boolean hasException = false;retryCount ++;try {// use UNIX domain socket to// prevent subsequent initializationnew LocalServerSocket("com.android.internal.telephony");// 使用UNIX域套接字来防止后续初始化} catch (java.io.IOException ex) {hasException = true;}if ( !hasException ) {break;} else if (retryCount > SOCKET_OPEN_MAX_RETRY) {throw new RuntimeException("PhoneFactory probably already running");} else {try {Thread.sleep(SOCKET_OPEN_RETRY_MILLIS);} catch (InterruptedException er) {}}}// Phone的状态通知器,对外发出通知,包括通话状态/网络服务状态/信号强度/SS(运营商补充服务--呼叫转移等)/网络小区信息/数据连接            等等// DefaultPhoneNotifier其实就是个中转站,本质上各接口都是通过ITelephonyRegistry代理来访问"telephony.registry"服务sPhoneNotifier = new DefaultPhoneNotifier();int cdmaSubscription = CdmaSubscriptionSourceManager.getDefault(context);// CDMA subscription状态管理Rlog.i(LOG_TAG, "Cdma Subscription set to " + cdmaSubscription);/* In case of multi SIM mode two instances of Phone, RIL are created,where as in single SIM mode only instance. isMultiSimEnabled() function checkswhether it is single SIM or multi SIM mode */// 在多SIM模式的情况下,创建两个Phone,RIL实例,isMultiSimEnabled()检查它是单SIM还是多SIM模式int numPhones = TelephonyManager.getDefault().getPhoneCount();// Return whether or not the device should use dynamic binding or the static// implementation (deprecated)boolean isDynamicBinding = sContext.getResources().getBoolean(com.android.internal.R.bool.config_dynamic_bind_ims);// 返回设备是否应使用动态绑定或静态实现(不建议使用)// Get the package name of the default IMS implementation.String defaultImsPackage = sContext.getResources().getString(com.android.internal.R.string.config_ims_package);// 获取默认IMS实现的包名称// Start ImsResolver and bind to ImsServices.Rlog.i(LOG_TAG, "ImsResolver: defaultImsPackage: " + defaultImsPackage);sImsResolver = new ImsResolver(sContext, defaultImsPackage, numPhones,isDynamicBinding);sImsResolver.initPopulateCacheAndStartBind();// 启动ImsResolver并绑定到ImsServicesint[] networkModes = new int[numPhones];sPhones = new Phone[numPhones];// 创建Phones数组,用来存储Phone实例sCommandsInterfaces = new RIL[numPhones];// 创建RIL数组,用来存储RIL实例// TelephonyNetworkFactory是创建网络代理的工厂,默认网络代理的评判根据分数和能力来进行,也可以通过重载来实现更复杂的计算来评判sTelephonyNetworkFactories = new TelephonyNetworkFactory[numPhones];for (int i = 0; i < numPhones; i++) {// reads the system properties and makes commandsinterface// Get preferred network type.// 读取系统属性并生成命令接口获取首选网络类型。// 初始化网络模式和RIL,支持几张卡就初始化几个RILnetworkModes[i] = RILConstants.PREFERRED_NETWORK_MODE;Rlog.i(LOG_TAG, "Network Mode set to " + Integer.toString(networkModes[i]));sCommandsInterfaces[i] = new RIL(context, networkModes[i],cdmaSubscription, i);}Rlog.i(LOG_TAG, "Creating SubscriptionController");// SubscriptionController提供访问SubscriptionInfo的IPC接口,SubscriptionInfo是framework层对sim卡信息的抽象表示// 数据都存储在数据库中,比如设置SIM卡的icon/名称/号码,读取plmn/mccmnc等等SubscriptionController.init(context, sCommandsInterfaces);// Instantiate UiccController so that all other classes can just// call getInstance()// 实例化UiccController/assets/images/android/tele/用于访问UICC卡的相关信息,通过在RIL中注册事件监听来实现sUiccController = UiccController.make(context, sCommandsInterfaces);if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY_EUICC)) {sEuiccController = EuiccController.init(context);sEuiccCardController = EuiccCardController.init(context);}// 根据SIM卡数量创建Phone实例,Phone实例分为两种类型GSM和CDMA// 7.0开始没有CDMAPhone类,把之前的GSMPhone和CDMAPhone合并成了GsmCdmaPhone// 同样的就有了GsmCdmaCallTracker/GsmCdmaConnectionfor (int i = 0; i < numPhones; i++) {Phone phone = null;int phoneType = TelephonyManager.getPhoneType(networkModes[i]);if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {phone = new GsmCdmaPhone(context,sCommandsInterfaces[i], sPhoneNotifier, i,PhoneConstants.PHONE_TYPE_GSM,TelephonyComponentFactory.getInstance());} else if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {phone = new GsmCdmaPhone(context,sCommandsInterfaces[i], sPhoneNotifier, i,PhoneConstants.PHONE_TYPE_CDMA_LTE,TelephonyComponentFactory.getInstance());}Rlog.i(LOG_TAG, "Creating Phone with type = " + phoneType + " sub = " + i);sPhones[i] = phone;}// Set the default phone in base class.// FIXME: This is a first best guess at what the defaults will be. It// FIXME: needs to be done in a more controlled manner in the future.sPhone = sPhones[0];// 在基类中设置默认PhonesCommandsInterface = sCommandsInterfaces[0];// Ensure that we have a default SMS app. Requesting the app with// updateIfNeeded set to true is enough to configure a default SMS app.// 确保我们有一个默认的短信应用程序。 请求将updateIfNeeded设置为true的应用程序足以配置默认的SMS应用程序。ComponentName componentName =SmsApplication.getDefaultSmsApplication(context, true /* updateIfNeeded */);String packageName = "NONE";if (componentName != null) {packageName = componentName.getPackageName();}Rlog.i(LOG_TAG, "defaultSmsApplication: " + packageName);// Set up monitor to watch for changes to SMS packagesSmsApplication.initSmsPackageMonitor(context);// 设置监视器以监视SMS程序包的更改sMadeDefaults = true;Rlog.i(LOG_TAG, "Creating SubInfoRecordUpdater ");// SubscriptionInfoUpdater主要监听sim卡的插入/拔出/上锁/加载等情况sSubInfoRecordUpdater = new SubscriptionInfoUpdater(BackgroundThread.get().getLooper(), context, sPhones, sCommandsInterfaces);SubscriptionController.getInstance().updatePhonesAvailability(sPhones);// Start monitoring after defaults have been made.// Default phone must be ready before ImsPhone is created because ImsService might// need it when it is being opened. This should initialize multiple ImsPhones for// ImsResolver implementations of ImsService.// 在默认设置完成后开始监控。 在创建ImsPhone之前,必须准备好默认Phone,因为ImsService在打开时可能需要它// 这应该为ImsService的ImsResolver实现初始化多个ImsPhones。for (int i = 0; i < numPhones; i++) {sPhones[i].startMonitoringImsService();}// ITelephonyRegistry提供了访问通话状态/网络服务状态/信号强度/SS(运营商补充服务--呼叫转移等)/网络小区信息/数据连接等等的接口ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService("telephony.registry"));SubscriptionController sc = SubscriptionController.getInstance();sSubscriptionMonitor = new SubscriptionMonitor(tr, sContext, sc, numPhones);//PhoneSwitcher监听subscription的变化和网络请求来决定启用哪个phonesPhoneSwitcher = new PhoneSwitcher(MAX_ACTIVE_PHONES, numPhones,sContext, sc, Looper.myLooper(), tr, sCommandsInterfaces,sPhones);// ProxyController类用于get/set radio相关的操作sProxyController = ProxyController.getInstance(context, sPhones,sUiccController, sCommandsInterfaces, sPhoneSwitcher);sIntentBroadcaster = IntentBroadcaster.getInstance(context);sNotificationChannelController = new NotificationChannelController(context);// 网络数据连接辅助类sTelephonyNetworkFactories = new TelephonyNetworkFactory[numPhones];for (int i = 0; i < numPhones; i++) {sTelephonyNetworkFactories[i] = new TelephonyNetworkFactory(sPhoneSwitcher, sc, sSubscriptionMonitor, Looper.myLooper(),sContext, i, sPhones[i].mDcTracker);}}}}

总结一下上面对makeDefaultPhone()方法的分析:

  • 初始化各种控制接口SubscriptionController,UiccController,主要负责SIM卡状态信息的控制监听

  • 根据SIM卡数量创建Phone和RIL实例,并且设置默认Phone和RIL,Phone分为GSM和CDMA两大种类

  • 启动ImsResolver并绑定到ImsServices

Android中有三种PhoneFactory
1.PhoneFactory.java ——–>用于创建GsmCdmaPhone对象
2.ImsPhoneFactory.java ——–>用于创建ImsPhone对象
3.SipPhoneFactory.java ——–>用于创建SipPhone对象

总结

这篇文章主要讲述了Telephony从PhoneApp创建开始,以及初始化Phone对象、唤起telephony框架所做的一些工作,借助了许多网络查询的资料和经验,还有不少不足之处,希望读者能批评指正

PhoneApp的启动过程相关推荐

  1. Windows Phone开发(二)-- 框架结构和启动过程

    一 Windows Phone 框架结构 在进行Windows Phone开发之前有必要了解一下整个全新平台的结构.对于Windows Phone平台来说,区别于之前的Windows Mobile平台 ...

  2. Android系统的启动过程

    Android系统的启动过程可以简单地总结为以下几个流程: 加载BootLoader -> 初始化内核 -> 启动init进程 -> init进程fork出Zygote(孵化器)进程 ...

  3. Android系统默认Home应用程序(Launcher)的启动过程源代码分析

    在前面一篇文章中,我们分析了Android系统在启动时安装应用程序的过程,这些应用程序安装好之后,还需要有一个Home应用程序来负责把它们在桌面上展示出来,在Android系统中,这个默认的Home应 ...

  4. Linux0.11内核引导启动过程概述

    Linux0.11仅支持x86架构.它的内核引导启动程序在文件夹boot内,共有三个汇编代码文件.按照启动流程依次是: (1)bootsect.s.boot是启动引导的意思,sect即sector,是 ...

  5. linux启动sql server数据库,SQL Server数据库启动过程详解及启动不起来的问题分析及解决方法...

    第五步.启动系统数据库model model系统数据库同样也是SQL Server启动过程中用到的一个非常关键的数据库,如果这个库损坏,SQL Server启动也会失败,关于model数据不能启动的原 ...

  6. Linux必知必会的目录与启动过程

    第1章 /etc/目录 1.1 /etc/sysconfig/network-scripts/ifcfg-eth0 linux第一块网卡的配置文件 [root@znix ~]# cat /etc/sy ...

  7. Spring Boot启动过程(二)

    书接上篇 该说refreshContext(context)了,首先是判断context是否是AbstractApplicationContext派生类的实例,之后调用了强转为AbstractAppl ...

  8. Linux X Window System运行原理和启动过程

    本文主要说明X Window System的基本运行原理,其启动过程,及常见的跨网络运行X Window System. 一) 基本运行原理 X Window System采用C/S结构,但和我们常见 ...

  9. Spring 容器的启动过程

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 本文来源:http://r6f.cn/b47K 一. 前言 ...

最新文章

  1. Django 静态资源路径问题(一)
  2. 7、ShardingSphere 之 Sharding-Proxy
  3. SMACH专题(一)----安装与初探
  4. android官方文档中文版_Now in Android:01 - 如何掌握最新的 Android 技术?
  5. sap相关性不能被编译_经典综述编译丨生物硝化抑制丨NAT PLANTS:现代农业中的氮转化和生物硝化抑制作用...
  6. linux 系统显示很大,在Linux中可视化显示内存占用情况的方法
  7. tp5 queue.php,tp5(think-queue)消息队列+supervisor进程管理实现队列常驻进程
  8. pytorch 卷积
  9. JAVA Pattern和Matcher 的用法
  10. MDaemon12.X特殊注意事项和新功能
  11. 前端工程化(Vue-cli3和Element-ui)
  12. Java学习4:方法的重载(Overload)详解及实例说明
  13. 计算机用老毛桃u盘备份系统,如何用老毛桃u盘备份系统
  14. python灰产路子有哪些_Python3 网络爬虫(四):视频下载,那些事儿!-后台/架构/数据库-敏捷大拇指-一个敢保留真话的IT精英社区...
  15. halcon例程学习笔记(10)---图像灰度共生矩阵cooc_feature_image.hdev
  16. ORCLE导出数据,加不加owner,ORACLE常用的导出语句
  17. 科学计算机常用按键,电脑计算器里面的“科学型”的里面所有的按键的功能
  18. ITK入门教程(11)点集之创建一个点集
  19. 计算机组成原理哈工大期末_计算机组成原理(哈工大)——课程知识点总结
  20. 《欢乐颂2》狗血的剧情才是生活该有的模样

热门文章

  1. 创建create-react-app myapp项目报错
  2. mtk处理器强开教程 不收费
  3. 神经功能缺损评分 css,不同神经功能缺损程度脑梗死病人血尿酸水平变化及其与近期预后关系分析...
  4. 【思维】SCU 4437 Carries
  5. 【攻防世界】十七、ics-05
  6. R语言并行计算beta-NTI值
  7. golang构建htpp服务
  8. 每周一看:16份文档资料,程序员软硬实力全概览,总有一个适合你
  9. 【8月】100个HC3i优秀医疗信息化资源推荐
  10. 简体中文与繁体中文互转