当前位置: 首页 > 编程日记 > 正文

Android组件框架:Android组件管理者ActivityManager

关于作者

郭孝星,程序员,吉他手,主要从事Android平台基础架构方面的工作,欢迎交流技术方面的问题,可以去我的Github提issue或者发邮件至guoxiaoxingse@163.com与我交流。

第一次阅览本系列文章,请参见导读,更多文章请参见文章目录。

文章目录

  • 一 组件管家ActivityManagerService
    • 1.1 ActivityManagerService启动流程
    • 1.1 ActivityManagerService工作流程
  • 二 应用主线程ActivityThread
    • 2.1 ActivityThread启动流程
    • 2.2 ActivityThread工作

ActivityManagerService是贯穿Android系统组件的核心服务,在ServiceServer执行run()方法的时候被创建,运行在独立的线程中,负责Activity、Service、BroadcastReceiver的启动、切换、调度以及应用进程的管理和调度工作。

Activity、Service、BroadcastReceiver的启动、切换、调度都有着相似的流程,我们来看一下。

Activity的启动流程图(放大可查看)如下所示:

主要角色有:

  • Instrumentation: 监控应用与系统相关的交互行为。
  • AMS:组件管理调度中心,什么都不干,但是什么都管。
  • ActivityStarter:处理Activity什么时候启动,怎么样启动相关问题,也就是处理Intent与Flag相关问题,平时提到的启动模式都可以在这里找到实现。
  • ActivityStackSupervisior:这个类的作用你从它的名字就可以看出来,它用来管理Stack和Task。
  • ActivityStack:用来管理栈里的Activity。
  • ActivityThread:最终干活的人,是ActivityThread的内部类,Activity、Service、BroadcastReceiver的启动、切换、调度等各种操作都在这个类里完成。

Service的启动流程图(放大可查看)如下所示:

主要角色有:

  • AMS:组件管理调度中心,什么都不干,但是什么都管。
  • ApplicationThread:最终干活的人,是ActivityThread的内部类,Activity、Service、BroadcastReceiver的启动、切换、调度等各种操作都在这个类里完成。
  • ActiveServices:主要用来管理Service,内部维护了三份列表:将启动Service列表、重启Service列表以及以销毁Service列表。

BroadcastReceiver的启动流程图(放大可查看)如下所示:

主要角色有:

  • AMS:组件管理调度中心,什么都不干,但是什么都管。
  • BroadcastQueue:广播队列,根据广播的优先级来管理广播。
  • ApplicationThread:最终干活的人,是ActivityThread的内部类,Activity、Service、BroadcastReceiver的启动、切换、调度等各种操作都在这个类里完成。
  • ReceiverDispatcher:广播调度中心,采用反射的方式获取BroadcastReceiver的实例,然后调用它的onReceive()方法。

可以发现,除了一些辅助类外,最主要的组件管家AMS和应用主线程ActivityThread。本篇文章重点分析这两个类的实现,至于其他类会在 Activity、Service与BroadcastReceiver启动流程的文章中一一分析。

通过上面的分析,AMS的整个调度流程就非常明朗了。

ActivityManager相当于前台接待,她将客户的各种需求传达给大总管ActivityMangerService,但是大总管自己不干活,他招来了很多小弟,他最信赖的小弟ActivityThread 替他完成真正的启动、切换、以及退出操作,至于其他的中间环节就交给ActivityStack、ActivityStarter等其他小弟来完成。

一 组件管家ActivityManagerService

1.1 ActivityManagerService启动流程

我们知道所有的系统服务都是在SystemServer的run()方法里启动的,SystemServer 将系统服务分为了三类:

  • 引导服务
  • 核心服务
  • 其他服务

ActivityManagerService属于引导服务,在startBootstrapServices()方法里被创建,如下所示:

mActivityManagerService = mSystemServiceManager.startService(ActivityManagerService.Lifecycle.class).getService();
复制代码

SystemServiceManager的startService()方法利用反射来创建对象,Lifecycle是ActivityManagerService里的静态内部类,它继承于SystemService,在它的构造方法里 它会调用ActivityManagerService的构造方法创建ActivityManagerService对象。

public static final class Lifecycle extends SystemService {private final ActivityManagerService mService;public Lifecycle(Context context) {super(context);mService = new ActivityManagerService(context);}@Overridepublic void onStart() {mService.start();}public ActivityManagerService getService() {return mService;}
}
复制代码

ActivityManagerService的构造方法如下所示:

public ActivityManagerService(Context systemContext) {mContext = systemContext;mFactoryTest = FactoryTest.getMode();mSystemThread = ActivityThread.currentActivityThread();Slog.i(TAG, "Memory class: " + ActivityManager.staticGetMemoryClass());//创建并启动系统线程以及相关HandlermHandlerThread = new ServiceThread(TAG,android.os.Process.THREAD_PRIORITY_FOREGROUND, false /*allowIo*/);mHandlerThread.start();mHandler = new MainHandler(mHandlerThread.getLooper());mUiHandler = new UiHandler();/* static; one-time init here */if (sKillHandler == null) {sKillThread = new ServiceThread(TAG + ":kill",android.os.Process.THREAD_PRIORITY_BACKGROUND, true /* allowIo */);sKillThread.start();sKillHandler = new KillHandler(sKillThread.getLooper());}//创建用来存储各种组件Activity、Broadcast的数据结构mFgBroadcastQueue = new BroadcastQueue(this, mHandler,"foreground", BROADCAST_FG_TIMEOUT, false);mBgBroadcastQueue = new BroadcastQueue(this, mHandler,"background", BROADCAST_BG_TIMEOUT, true);mBroadcastQueues[0] = mFgBroadcastQueue;mBroadcastQueues[1] = mBgBroadcastQueue;mServices = new ActiveServices(this);mProviderMap = new ProviderMap(this);mAppErrors = new AppErrors(mContext, this);//创建system等各种文件夹,用来记录系统的一些事件...//初始化一些记录工具...
}
复制代码

可以发现,ActivityManagerService的构造方法主要做了两个事情:

  • 创建并启动系统线程以及相关Handler。
  • 创建用来存储各种组件Activity、Broadcast的数据结构。

这里有个问题,这里创建了两个Hanlder(sKillHandler暂时忽略,它是用来kill进程的)分别是MainHandler与UiHandler,它们有什么区别呢??

我们知道Handler是用来向所在线程发送消息的,也就是说决定Handler定位的是它构造方法里的Looper,我们分别来看下。

MainHandler里的Looper来源于线程ServiceThread,它的线程名是"ActivityManagerService",该Handler主要用来处理组件调度相关操作。

mHandlerThread = new ServiceThread(TAG,android.os.Process.THREAD_PRIORITY_FOREGROUND, false /*allowIo*/);
mHandlerThread.start();
mHandler = new MainHandler(mHandlerThread.getLooper());
复制代码

UiHandler里的Looper来源于线程UiThread(继承于ServiceThread),它的线程名"android.ui",该Handler主要用来处理UI相关操作。


private UiThread() {super("android.ui", Process.THREAD_PRIORITY_FOREGROUND, false /*allowIo*/);// Make sure UiThread is in the fg stune boost groupProcess.setThreadGroup(Process.myTid(), Process.THREAD_GROUP_TOP_APP);
}public UiHandler() {super(com.android.server.UiThread.get().getLooper(), null, true);
}
复制代码

以上便是整个ActivityManagerService的启动流程,还是比较简单的。

1.2 ActivityManagerService工作流程

ActivityManagerService就是ActivityManager家族 的核心类了,四大组件的启动、切换、调度都是在ActivityManagerService里完成的。

ActivityManagerService类图如下所示:

  • ActivityManager:AMS给客户端调用的接口。
  • ActivityManagerNative:该类是ActivityManagerService的父类,继承与Binder,主要用来负责进程通信,接收ActivityManager传递过来的信息,这么写可以将通信部分分离在ActivityManagerNative,使得 ActivityManagerService可以专注组件的调度,减小了类的体积。
  • ActivityManagerProxy:该类定义在ActivityManagerNative内部,正如它的名字那样,它是ActivityManagerService的代理类,

关于ActivityManager

ActivityManager是提供给客户端调用的接口,日常开发中我们可以利用 ActivityManager来获取系统中正在运行的组件(Activity、Service)、进程(Process)、任务(Task)等信息,ActivityManager定义了相应的方法来获取和操作这些信息。

ActivityManager定义了很多静态内部类来描述这些信息,具体说来:

  • ActivityManager.StackId: 描述组件栈ID信息
  • ActivityManager.StackInfo: 描述组件栈信息,可以利用StackInfo去系统中检索某个栈。
  • ActivityManager.MemoryInfo: 系统可用内存信息
  • ActivityManager.RecentTaskInfo: 最近的任务信息
  • ActivityManager.RunningAppProcessInfo: 正在运行的进程信息
  • ActivityManager.RunningServiceInfo: 正在运行的服务信息
  • ActivityManager.RunningTaskInfo: 正在运行的任务信息
  • ActivityManager.AppTask: 描述应用任务信息

说道这里,我们有必要区分一些概念,以免以后混淆。

  • 进程(Process):Android系统进行资源调度和分配的基本单位,需要注意的是同一个栈的Activity可以运行在不同的进程里。
  • 任务(Task):Task是一组以栈的形式聚集在一起的Activity的集合,这个任务栈就是一个Task。

在日常开发中,我们一般是不需要直接操作ActivityManager这个类,只有在一些特殊的开发场景才用的到。

  • isLowRamDevice():判断应用是否运行在一个低内存的Android设备上。
  • clearApplicationUserData():重置app里的用户数据。
  • ActivityManager.AppTask/ActivityManager.RecentTaskInfo:我们如何需要操作Activity的栈信息也可以通过ActivityManager来做。

关于ActivityManagerNative与ActivityManagerProxy

这两个类其实涉及的是Android的Binder通信原理,后面我们会有专门的文章来分析Binder相关实现。

二 应用主线程ActivityThread

ActivityThread管理着应用进程里的主线程,负责Activity、Service、BroadcastReceiver的启动、切换、 以及销毁等操作。

2.1 ActivityThread启动流程

先来聊聊ActivityThread,这个类也厉害了?,它就是我们app的入口,写过Java程序的同学都知道,Java程序的入口类都会有一个main()方法,ActivityThread也是这样,它的main()方法在新的应用 进程被创建后就会被调用,我们来看看这个main()方法实现了什么东西。

public final class ActivityThread {public static void main(String[] args) {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");SamplingProfilerIntegration.start();// CloseGuard defaults to true and can be quite spammy.  We// disable it here, but selectively enable it later (via// StrictMode) on debug builds, but using DropBox, not logs.CloseGuard.setEnabled(false);Environment.initForCurrentUser();// Set the reporter for event logging in libcoreEventLogger.setReporter(new EventLoggingReporter());// Make sure TrustedCertificateStore looks in the right place for CA certificatesfinal File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());TrustedCertificateStore.setDefaultUserDirectory(configDir);Process.setArgV0("<pre-initialized>");//主线程的looperLooper.prepareMainLooper();//创建ActivityThread实例ActivityThread thread = new ActivityThread();//调用attach()方法将ApplicationThread对象关联给AMS,以便AMS调用ApplicationThread里的方法,这同样也是一个IPC的过程。thread.attach(false);//主线程的Handlerif (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}if (false) {Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, "ActivityThread"));}// End of event ActivityThreadMain.Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);//开始消息循环Looper.loop();throw new RuntimeException("Main thread loop unexpectedly exited");}   
}
复制代码

这里面还有关键的attach()方法,我们来看一下。

public final class ActivityThread {private void attach(boolean system) {sCurrentActivityThread = this;//判断是否为系统进程,上面传过来的为false,表明它不是一个系统进程mSystemThread = system;//应用进程的处理流程if (!system) {ViewRootImpl.addFirstDrawHandler(new Runnable() {@Overridepublic void run() {ensureJitEnabled();}});android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",UserHandle.myUserId());RuntimeInit.setApplicationObject(mAppThread.asBinder());final IActivityManager mgr = ActivityManagerNative.getDefault();try {//将ApplicationThread对象关联给AMS,以便AMS调用ApplicationThread里的方法,这//同样也是一个IPC的过程。mgr.attachApplication(mAppThread);} catch (RemoteException ex) {throw ex.rethrowFromSystemServer();}// Watch for getting close to heap limit.BinderInternal.addGcWatcher(new Runnable() {@Override public void run() {if (!mSomeActivitiesChanged) {return;}Runtime runtime = Runtime.getRuntime();long dalvikMax = runtime.maxMemory();long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();if (dalvikUsed > ((3*dalvikMax)/4)) {if (DEBUG_MEMORY_TRIM) Slog.d(TAG, "Dalvik max=" + (dalvikMax/1024)+ " total=" + (runtime.totalMemory()/1024)+ " used=" + (dalvikUsed/1024));mSomeActivitiesChanged = false;try {mgr.releaseSomeActivities(mAppThread);} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}}});} //系统进程的处理流程else {//初始化系统组件,例如:Instrumentation、ContextImpl、Application//系统进程的名称为system_processandroid.ddm.DdmHandleAppName.setAppName("system_process",UserHandle.myUserId());try {//创建Instrumentation对象mInstrumentation = new Instrumentation();//创建ContextImpl对象ContextImpl context = ContextImpl.createAppContext(this, getSystemContext().mPackageInfo);//创建Application对象mInitialApplication = context.mPackageInfo.makeApplication(true, null);//调用Application.onCreate()方法,这个方法我们非常熟悉了,我们经常在这里做一些初始化库的工作。mInitialApplication.onCreate();} catch (Exception e) {throw new RuntimeException("Unable to instantiate Application():" + e.toString(), e);}}// add dropbox logging to libcoreDropBox.setReporter(new DropBoxReporter());//注册Configuration变化后的回调通知,当系统配置发生变化时,例如:语言切换,触发该回调。ViewRootImpl.addConfigCallback(new ComponentCallbacks2() {//配置发生变化@Overridepublic void onConfigurationChanged(Configuration newConfig) {synchronized (mResourcesManager) {// We need to apply this change to the resources// immediately, because upon returning the view// hierarchy will be informed about it.if (mResourcesManager.applyConfigurationToResourcesLocked(newConfig, null)) {updateLocaleListFromAppContext(mInitialApplication.getApplicationContext(),mResourcesManager.getConfiguration().getLocales());// This actually changed the resources!  Tell// everyone about it.if (mPendingConfiguration == null ||mPendingConfiguration.isOtherSeqNewer(newConfig)) {mPendingConfiguration = newConfig;sendMessage(H.CONFIGURATION_CHANGED, newConfig);}}}}//低内存@Overridepublic void onLowMemory() {}@Overridepublic void onTrimMemory(int level) {}});}
}
复制代码

从上面这两个方法我们可以看出ActivityThread主要做了两件事情:

  • 创建并开启主线程的消息循环。
  • 将ApplicationThread对象(Binder对象)关联给AMS,以便AMS调用ApplicationThread里的方法,这同样也是一个IPC的过程。

2.2 ActivityThread工作流程

ActivityThread工作流程图如下所示:

通过前面的分析,ActivityThread的整个工作流程就非常明朗了。ActivityThread内部有个Binder对象ApplicationThread,AMS可以调用ApplicationThread里的方法,而 ApplicationThread里的方法利用mH(Handler)发送消息给ActivityThread里的消息队列,ActivityThread再去处理这些消息,进而完成诸如Activity启动等各种操作。

到这里我们已经把ActivityManager家族的主要框架都梳理完了,本篇文章并没有大篇幅的去分析源码,我们的重点是梳理整体框架,让大家有整体上的认识,至于具体的细节,可以根据自己的需要有的 放矢的去研究。这也是我们提倡的阅读Android源码的方法:不要揪着细节不放,要有整体意识。

理解了AMS的内容,后续就接着来分析Activity、Service、BroadcastReceiver的启动、切换和销毁等流程,分析的过程中也会结合着日常开发中经常遇到的一些问题,带着这些问题,我们去看看源 码里怎么写的,为什么会出现这些问题。应该如何去解决。

相关文章:

[转]HTTP协议详解

当今web程序的开发技术真是百家争鸣&#xff0c;ASP.NET, PHP, JSP&#xff0c;Perl, AJAX 等等。 无论Web技术在未来如何发展&#xff0c;理解Web程序之间通信的基本协议相当重要, 因为它让我们理解了Web应用程序的内部工作. 本文将对HTTP协议进行详细的实例讲解&#xff0c;内…

【Whalepaper】NLP论文研读 - Keyword-Attentive Deep Semantic Matching

Whalepaper是由周郴莲负责的一个每周分享论文的活动&#xff0c;带你研读AI领域的论文&#xff0c;快来一起开源学术科研吧&#xff01; NLP 论文分享&#xff1a;每周日 晚上 九点CV 论文分享&#xff1a; 每周日 晚上 九点Res 论文分享&#xff1a;每周六 晚上 九点半 欢迎…

web前端的就业前景好不好

web前端的就业前景好不好?一直有人都想知道这个答案&#xff0c;其实放眼互联网未来&#xff0c;web前端的发展前景都是非常好的&#xff0c;那么它的就业前景自热也是不错&#xff0c;具体来看看下面的详细介绍就知道了。 web前端的就业前景好不好?近几年的热门行业里&#…

android:HTTP通信 .

HTTP&#xff1a; 超文本传送协议&#xff08;hypertext transport protocol&#xff09;&#xff0c;用于传送WWW方式的数据。属于应用层的面向对象的协议。HTTP采用了请求/响应模型。客户端向服务器发送的请求包含了&#xff1a;请求的方法、URL、协议版本、请求修饰符、客户…

【青少年编程】【三级】打气球游戏

「青少年编程竞赛交流群」已成立&#xff08;适合6至18周岁的青少年&#xff09;&#xff0c;公众号后台回复【Scratch】或【Python】&#xff0c;即可进入。如果加入了之前的社群不需要重复加入。 微信后台回复“资料下载”可获取以往学习的材料&#xff08;视频、代码、文档&…

(转)如何修改maven的默认jdk版本

背景&#xff1a;在maven的配置文件中配置编译的jdk插件&#xff0c;就不需要在eclipse中进行重新的指定了。 问题 1、创建maven项目的时候&#xff0c;jdk版本是1.5版本&#xff0c;而自己安装的是1.7或者1.8版本。 2、每次右键项目名-maven->update project 时候&#xff…

Python适合初学者或者0基础学习吗?

Python适合初学者或者0基础小白学习吗?很多人都比较关注这个问题&#xff0c;因为近几年Python在互联网行业的发展显而易见&#xff0c;它的就业几率也非常高&#xff0c;具体来看看下面的详细介绍吧。 Python适合初学者或者0基础小白学习吗?与Java、C不同的是&#xff0c;Py…

CS5中动作和批处理

动作类似office里的宏。 窗口---动作。排列过多的图片可以窗口---排列。 先新组&#xff0c;然后新动作&#xff0c;完成后停止录制&#xff1b;点击新图片使其成为当前图片&#xff0c;再点击播放动作。 一个新组下可以有很多动作。 动作定义为快捷键&#xff0c;使用时可以双…

Django模板过滤器详解

Django 模板过滤器也是我们在以后基于 Django 网站开发过程中会经常遇到的&#xff0c;如显示格式的转换、判断处理等。以下是 Django 过滤器列表&#xff0c;希望对为大家的开发带来一些方便。 一、形式&#xff1a;小写   {{ name | lower }} 二、串联&#xff1a;先转义文…

【青少年编程】【三级】 魔术表演“开花”

「青少年编程竞赛交流群」已成立&#xff08;适合6至18周岁的青少年&#xff09;&#xff0c;公众号后台回复【Scratch】或【Python】&#xff0c;即可进入。如果加入了之前的社群不需要重复加入。 微信后台回复“资料下载”可获取以往学习的材料&#xff08;视频、代码、文档&…

Python培训教程分享:visual studio编写python怎么样?

本期小编要为大家介绍的Python培训教程就是关于“visual studio编写python怎么样?”的问题&#xff0c;但答案当然是可以的&#xff0c;据了解&#xff0c;vs2017、vs2019都集成了python开发&#xff0c;只不过需要在安装的时候单独勾选一下才行&#xff0c;下面小编简单介绍一…

HTTP头入门到精通(每一个HTTP消息头解释)

1. Accept&#xff1a;告诉WEB服务器自己接受什么介质类型&#xff0c;*/* 表示任何类型&#xff0c;type/* 表示该类型下的所有子类型&#xff0c;type/sub-type。 2. Accept-Charset&#xff1a; 浏览器申明自己接收的字符集 Accept-Encoding&#xff1a; 浏览器申明自己接收…

Day2 - Python基础2作业【文件操作--购物车程序(用户操作及商户操作)】

1 # ----user.txt----2 3 {已购商品: , 消费记录: , 余额: 0}4 5 6 # ----commodity.txt----7 8 iPhone,50009 HuaWei,3000 10 XiaoMi,2000 11 oppo,1000 1 #/usr/bin/env python2 #-*- coding:utf-8 -*-3 # Day2/file_shopping.py4 5 _author_ hepidong6 7 import time8 9 # …

【直播】林锦弘:CV赛事高分经验分享

CV赛事高分经验分享 直播信息 分享嘉宾&#xff1a;林锦弘&#xff0c;威斯康星麦迪逊大学&#xff0c;多赛事top10开源方案贡献者。 直播时间&#xff1a;2021年07月29日 20:00 直播内容&#xff1a; 计算机视觉比赛通用流程Top10方案讲解交流&答疑 直播网址&#xf…

UI培训教程分享:APP图标设计的6种风格都有哪些?

在从事UI设计师这个岗位的时候&#xff0c;相信大家会经常遇到关于产品设计的工作&#xff0c;尤其是APP图标这块&#xff0c;在日常工作中&#xff0c;常见的图标风格主要有渐变风格、剪影风格、长投影风格、卡通风格、轻质感风格和拟物风格6种。今天小编就为大家详细的介绍一…

图片基础知识梳理(3) BitmapBitmapFactory 解析

一、概述 今天这篇文章我们来了解一下两个类&#xff1a; BitmapBitmapFactory二、Bitmap 2.1 创建Bitmap 通过Bitmap的源码&#xff0c;我们可以看到它内部提供了很多.createBitmap(xxx)的静态方法&#xff0c;我们可以通过这些方法来获得一个Bitmap&#xff1a; 上述的方法最…

电路实验1-电容充放电

转载于:https://www.cnblogs.com/cutepig/archive/2013/01/17/2865289.html

【直播】陈信达:零基础计算机视觉之机器学习基础

零基础计算机视觉之机器学习基础 直播信息 分享嘉宾&#xff1a;陈信达&#xff0c;Datawhale成员&#xff0c;上海科技大学硕士。 直播时间&#xff1a;2021年07月30日 20:00 直播内容&#xff1a; 线性回归与指针读数识别逻辑回归原理与代码选讲人工神经网络与Fashion-MN…

UI培训分享:如何成为一名优秀的UI设计师

UI设计师在工作岗位中也是分等级的&#xff0c;如何成为一名优秀的UI设计师在如今的职业发展中是非常有必要的&#xff0c;各行各业都在内卷中&#xff0c;那么如何成为一名优秀的UI设计师呢?来看看下面的详细介绍。 UI培训分享&#xff1a;如何成为一名优秀的UI设计师? 好UI…

POJ 2418 Hardwood Species(trie 树)

题目链接 开始想用map的&#xff0c;字典序不会搞&#xff0c;还是老老实实的用trie树把。好久没写了&#xff0c;忘得差不多了。 1 #include <iostream>2 #include <cstdio>3 #include <cstdlib>4 #include <cstring>5 #include <map>6 #includ…

白盒测试实践-任务完成

这次的白盒测试实践已经圆满完成&#xff0c;每个人都分配到了相应的任务&#xff0c;并对各自的任务进行了具体的落实。 分配情况如下&#xff1a; 阶段任务名称任务负责人阶段一测试用例清单安秀芳阶段二静态评审罗阳刚、周成阶段三自动化静态检查滕怡天阶段四基于JUnit的单元…

【第23周复盘】懒癌犯了,拖到今天!

「青少年编程竞赛交流群」已成立&#xff08;适合6至18周岁的青少年&#xff09;&#xff0c;公众号后台回复【Scratch】或【Python】&#xff0c;即可进入。如果加入了之前的社群不需要重复加入。 微信后台回复“资料下载”可获取以往学习的材料&#xff08;视频、代码、文档&…

软件测试培训分享:软件测试的职业发展方向有哪些

很多人都觉得软件测试在互联网行业入门是比较轻松的&#xff0c;对于如此轻松的行业&#xff0c;它所在的职业发展前景怎么样呢?软件测试的职业发展方向有哪些呢?本期软件测试培训分享内容请看以下详细介绍。 软件测试的职业发展方向有哪些?职业的选择对于现在的年轻人来说相…

【青少年编程】【三级】换装

「青少年编程竞赛交流群」已成立&#xff08;适合6至18周岁的青少年&#xff09;&#xff0c;公众号后台回复【Scratch】或【Python】&#xff0c;即可进入。如果加入了之前的社群不需要重复加入。 微信后台回复“资料下载”可获取以往学习的材料&#xff08;视频、代码、文档&…

转程序员,都去写一写前端代码吧

转自: http://www.oschina.net/news/36972/programmer-write-frond-end-code 你可以认为我是一个极端的人&#xff0c;就像有许多人专注于自己的领域而不屑于其它“肤浅”的工作范畴一样。比如我见过不少认为做portal没有技术含量的 判定&#xff0c;做工程都是充满苦逼行为的言…

大数据之公开数据的价值

2019独角兽企业重金招聘Python工程师标准>>> 大数据按照访问权限来划分&#xff0c;可分为私有数据和公开数据。私有数据不是每个人都能够自由访问调用的数据&#xff0c;例如银行交易记录、抵押信息、医疗数据、通讯数据、电商交易数据等等。私有数据因其私有性&am…

UI培训教程分享:Ui设计的细节规范有哪些需要注意?

在职场中&#xff0c;有很多UI设计师是零经验的&#xff0c;都是刚学会技术就来入职的&#xff0c;与经验丰富的其他成员来说&#xff0c;新人一定要提高自己的工作能力&#xff0c;本篇UI培训教程为大家分享的是Ui设计的细节规范有哪些需要注意?希望能给大家在工作中带来帮助…

linux 安装输入法

IBus IBus是一个框架&#xff0c;支持多种输入法 IBus输入法安装和设置 IBus是一个框架&#xff0c;支持多种输入法。安裝IBus框架&#xff1a; 在终端输入: sudo apt-get install ibus ibus-clutter ibus-gtk ibus-gtk3 ibus-qt4 启用IBus框架&#xff1a; 在终端输入: im-swi…

记录在Spring-Boot中使用Fegin调用RESTfull的PATCH方法设置

使用了ZooKeeper&#xff0c;设置 spring.cloud.zookeeper.dependency.headers.enabledfalse 参考&#xff1a; https://github.com/spring-cloud/spring-cloud-netflix/issues/2550#issuecomment-353230054 http://blog.csdn.net/menggudaoke/article/details/77884674>如有…

【青少年编程】【四级】词语接龙

「青少年编程竞赛交流群」已成立&#xff08;适合6至18周岁的青少年&#xff09;&#xff0c;公众号后台回复【Scratch】或【Python】&#xff0c;即可进入。如果加入了之前的社群不需要重复加入。 微信后台回复“资料下载”可获取以往学习的材料&#xff08;视频、代码、文档&…