update_engine-整体结构(三)
在update_engine-整体结构(二)中分析到了Action,那么我们接着继续分析.
首先来看一下BuildUpdateActons(...)这个方法。
src/system/update_engine/update_attempter_android.cc
void UpdateAttempterAndroid::BuildUpdateActions(const string& url) {
CHECK(!processor_->IsRunning());
processor_->set_delegate(this); // Actions:
shared_ptr<InstallPlanAction> install_plan_action(
new InstallPlanAction(install_plan_)); HttpFetcher* download_fetcher = nullptr;
if (FileFetcher::SupportedUrl(url)) {
DLOG(INFO) << "Using FileFetcher for file URL.";
download_fetcher = new FileFetcher();
} else {
#ifdef _UE_SIDELOAD
LOG(FATAL) << "Unsupported sideload URI: " << url;
#else
LibcurlHttpFetcher* libcurl_fetcher =
new LibcurlHttpFetcher(&proxy_resolver_, hardware_);
libcurl_fetcher->set_server_to_check(ServerToCheck::kDownload);
download_fetcher = libcurl_fetcher;
#endif // _UE_SIDELOAD
}
shared_ptr<DownloadAction> download_action(
new DownloadAction(prefs_,
boot_control_,
hardware_,
nullptr, // system_state, not used.
download_fetcher)); // passes ownership
shared_ptr<FilesystemVerifierAction> filesystem_verifier_action(
new FilesystemVerifierAction()); shared_ptr<PostinstallRunnerAction> postinstall_runner_action(
new PostinstallRunnerAction(boot_control_, hardware_)); download_action->set_delegate(this);
download_action->set_base_offset(base_offset_);
download_action_ = download_action;
postinstall_runner_action->set_delegate(this); actions_.push_back(shared_ptr<AbstractAction>(install_plan_action));
actions_.push_back(shared_ptr<AbstractAction>(download_action));
actions_.push_back(shared_ptr<AbstractAction>(filesystem_verifier_action));
actions_.push_back(shared_ptr<AbstractAction>(postinstall_runner_action)); // Bond them together. We have to use the leaf-types when calling
// BondActions().
BondActions(install_plan_action.get(), download_action.get());
BondActions(download_action.get(), filesystem_verifier_action.get());
BondActions(filesystem_verifier_action.get(),
postinstall_runner_action.get()); // Enqueue the actions.
for (const shared_ptr<AbstractAction>& action : actions_)
processor_->EnqueueAction(action.get());
}
我们会发现processor_,InstallPlanAction,DownloadAction,FilesystemVerifierAction,PostinstallRunnerAction。首先分析processor_,它是在UpdateAttempterAndroid的构造方法中被赋值
UpdateAttempterAndroid::UpdateAttempterAndroid(
DaemonStateInterface* daemon_state,
PrefsInterface* prefs,
BootControlInterface* boot_control,
HardwareInterface* hardware)
: daemon_state_(daemon_state),
prefs_(prefs),
boot_control_(boot_control),
hardware_(hardware),
processor_(new ActionProcessor()) {
network_selector_ = network::CreateNetworkSelector();
}
ActionProcessor的数据结构为:
// An ActionProcessor keeps a queue of Actions and processes them in order. namespace chromeos_update_engine { class AbstractAction;
class ActionProcessorDelegate; class ActionProcessor {
public:
ActionProcessor() = default; virtual ~ActionProcessor(); // Starts processing the first Action in the queue. If there's a delegate,
// when all processing is complete, ProcessingDone() will be called on the
// delegate.
virtual void StartProcessing(); // Aborts processing. If an Action is running, it will have
// TerminateProcessing() called on it. The Action that was running and all the
// remaining actions will be lost and must be re-enqueued if this Processor is
// to use it.
void StopProcessing(); // Suspend the processing. If an Action is running, it will have the
// SuspendProcessing() called on it, and it should suspend operations until
// ResumeProcessing() is called on this class to continue. While suspended,
// no new actions will be started. Calling SuspendProcessing while the
// processing is suspended or not running this method performs no action.
void SuspendProcessing(); // Resume the suspended processing. If the ActionProcessor is not suspended
// or not running in the first place this method performs no action.
void ResumeProcessing(); // Returns true iff the processing was started but not yet completed nor
// stopped.
bool IsRunning() const { return current_action_ != nullptr || suspended_; } // Adds another Action to the end of the queue.
virtual void EnqueueAction(AbstractAction* action); // Sets/gets the current delegate. Set to null to remove a delegate.
ActionProcessorDelegate* delegate() const { return delegate_; }
void set_delegate(ActionProcessorDelegate *delegate) {
delegate_ = delegate;
} // Returns a pointer to the current Action that's processing.
AbstractAction* current_action() const {
return current_action_;
} // Called by an action to notify processor that it's done. Caller passes self.
void ActionComplete(AbstractAction* actionptr, ErrorCode code); private:
// Continue processing actions (if any) after the last action terminated with
// the passed error code. If there are no more actions to process, the
// processing will terminate.
void StartNextActionOrFinish(ErrorCode code); // Actions that have not yet begun processing, in the order in which
// they'll be processed.
std::deque<AbstractAction*> actions_; // A pointer to the currently processing Action, if any.
AbstractAction* current_action_{nullptr}; // The ErrorCode reported by an action that was suspended but finished while
// being suspended. This error code is stored here to be reported back to the
// delegate once the processor is resumed.
ErrorCode suspended_error_code_{ErrorCode::kSuccess}; // Whether the action processor is or should be suspended.
bool suspended_{false}; // A pointer to the delegate, or null if none.
ActionProcessorDelegate* delegate_{nullptr}; DISALLOW_COPY_AND_ASSIGN(ActionProcessor);
}; // A delegate object can be used to be notified of events that happen
// in an ActionProcessor. An instance of this class can be passed to an
// ActionProcessor to register itself.
class ActionProcessorDelegate {
public:
virtual ~ActionProcessorDelegate() = default; // Called when all processing in an ActionProcessor has completed. A pointer
// to the ActionProcessor is passed. |code| is set to the exit code of the
// last completed action.
virtual void ProcessingDone(const ActionProcessor* processor,
ErrorCode code) {} // Called when processing has stopped. Does not mean that all Actions have
// completed. If/when all Actions complete, ProcessingDone() will be called.
virtual void ProcessingStopped(const ActionProcessor* processor) {} // Called whenever an action has finished processing, either successfully
// or otherwise.
virtual void ActionCompleted(ActionProcessor* processor,
AbstractAction* action,
ErrorCode code) {}
}; }
从中可以看到ActionProcessor其实就是用来管理Action的,它的方法都比较简单,根据注释我们大体就能够明白每个方法的意思,在遇到的时候某一个方法再具体分析。接下来再看Action它所存在的继承关系如下
Aciton继承关系
FilesystemVerifierAction,PostinstallRunnerAction,DownloadAction都继承了InstallPlanAction,根据继承关系可以看出他们都会有PerformAction,ActionCompleted等方法。PerformAction()是在Action开始执行前进行调用,而ActionCompleted是在执行完成后进行调用。先来看看InstallPlanAction中的内容
src/system/update_engine/payload_consumer/install_plan.h
class InstallPlanAction : public Action<InstallPlanAction> {
public:
InstallPlanAction() {}
explicit InstallPlanAction(const InstallPlan& install_plan):
install_plan_(install_plan) {} void PerformAction() override {
if (HasOutputPipe()) {
SetOutputObject(install_plan_);
}
processor_->ActionComplete(this, ErrorCode::kSuccess);
} InstallPlan* install_plan() { return &install_plan_; } static std::string StaticType() { return "InstallPlanAction"; }
std::string Type() const override { return StaticType(); } typedef ActionTraits<InstallPlanAction>::InputObjectType InputObjectType;
typedef ActionTraits<InstallPlanAction>::OutputObjectType OutputObjectType; private:
InstallPlan install_plan_; DISALLOW_COPY_AND_ASSIGN(InstallPlanAction);
};
可以看到InstallAction比较简单,仅仅是将install_plan_设置为了输出对象,传递给了下一个Action,这是Action之间的一个通信方式,这个方式可以称之为pipe方式,下面来分析一下这种通信方式。先来看在Action这个类里面提到的ActionPipe
src/system/update_engine/common/action_pipe.h
namespace chromeos_update_engine { // Used by Actions an InputObjectType or OutputObjectType to specify that
// for that type, no object is taken/given.
class NoneType {}; template<typename T>
class Action; template<typename ObjectType>
class ActionPipe {
public:
virtual ~ActionPipe() {} // This should be called by an Action on its input pipe.
// Returns a reference to the stored object.
const ObjectType& contents() const { return contents_; } //获取管道中的内容 // This should be called by an Action on its output pipe.
// Stores a copy of the passed object in this pipe.
void set_contents(const ObjectType& contents) { contents_ = contents; } //设置管道中的内容 // Bonds two Actions together with a new ActionPipe. The ActionPipe is
// jointly owned by the two Actions and will be automatically destroyed
// when the last Action is destroyed.
template<typename FromAction, typename ToAction>
static void Bond(FromAction* from, ToAction* to) { //将两个Action连接通过pipe连接在一起
std::shared_ptr<ActionPipe<ObjectType>> pipe(new ActionPipe<ObjectType>);
from->set_out_pipe(pipe); to->set_in_pipe(pipe); // If you get an error on this line, then
// it most likely means that the From object's OutputObjectType is
// different from the To object's InputObjectType.
} private:
ObjectType contents_; // The ctor is private. This is because this class should construct itself
// via the static Bond() method.
ActionPipe() {}
DISALLOW_COPY_AND_ASSIGN(ActionPipe);
}; // Utility function
template<typename FromAction, typename ToAction>
void BondActions(FromAction* from, ToAction* to) {
static_assert(
std::is_same<typename FromAction::OutputObjectType,
typename ToAction::InputObjectType>::value,
"FromAction::OutputObjectType doesn't match ToAction::InputObjectType");
ActionPipe<typename FromAction::OutputObjectType>::Bond(from, to);
} }
可以看到ActionPipe主要就是将两个Action连接在一起。为什么说就会连接在一起呢?再来看Action中相关的方法
src/system/update_engine/common/action.h
template<typename SubClass>
class Action : public AbstractAction {
public:
~Action() override {} // Attaches an input pipe to this Action. This is optional; an Action
// doesn't need to have an input pipe. The input pipe must be of the type
// of object that this class expects.
// This is generally called by ActionPipe::Bond()
void set_in_pipe( //设置输入管道
// this type is a fancy way of saying: a shared_ptr to an
// ActionPipe<InputObjectType>.
const std::shared_ptr<ActionPipe<
typename ActionTraits<SubClass>::InputObjectType>>& in_pipe) {
in_pipe_ = in_pipe;
} // Attaches an output pipe to this Action. This is optional; an Action
// doesn't need to have an output pipe. The output pipe must be of the type
// of object that this class expects.
// This is generally called by ActionPipe::Bond()
void set_out_pipe( //设置输出管道
// this type is a fancy way of saying: a shared_ptr to an
// ActionPipe<OutputObjectType>.
const std::shared_ptr<ActionPipe<
typename ActionTraits<SubClass>::OutputObjectType>>& out_pipe) {
out_pipe_ = out_pipe;
} // Returns true iff there is an associated input pipe. If there's an input
// pipe, there's an input object, but it may have been constructed with the
// default ctor if the previous action didn't call SetOutputObject().
bool HasInputObject() const { return in_pipe_.get(); } //是否有输入管道 // returns a const reference to the object in the input pipe.
const typename ActionTraits<SubClass>::InputObjectType& GetInputObject() //获取输入的内容
const {
CHECK(HasInputObject());
return in_pipe_->contents();
} // Returns true iff there's an output pipe.
bool HasOutputPipe() const { //是否有输出管道
return out_pipe_.get();
} // Copies the object passed into the output pipe. It will be accessible to
// the next Action via that action's input pipe (which is the same as this
// Action's output pipe).
void SetOutputObject( //设置输出的内容
const typename ActionTraits<SubClass>::OutputObjectType& out_obj) {
CHECK(HasOutputPipe());
out_pipe_->set_contents(out_obj);
} // Returns a reference to the object sitting in the output pipe.
const typename ActionTraits<SubClass>::OutputObjectType& GetOutputObject() { //获取输出的内容
CHECK(HasOutputPipe());
return out_pipe_->contents();
} protected:
// We use a shared_ptr to the pipe. shared_ptr objects destroy what they
// point to when the last such shared_ptr object dies. We consider the
// Actions on either end of a pipe to "own" the pipe. When the last Action
// of the two dies, the ActionPipe will die, too.
std::shared_ptr<ActionPipe<typename ActionTraits<SubClass>::InputObjectType>>
in_pipe_;
std::shared_ptr<ActionPipe<typename ActionTraits<SubClass>::OutputObjectType>>
out_pipe_;
};
从这里我们就能够看出每个Action其实有两个ActionPipe,一个是输入ActionPipe,一个是输出ActionPipe,输入ActionPipe和前一个Action的输出ActionPipe其实是一个ActionPipe,输出Actionpipe和下一个Action的输出ActionPipe是一个ActionPipe.
ActionTraits在这个类里仅仅是为InstallPlan这个类型定义了一个新的类型
src/system/update_engine/payload_consumer/install_plan.h
template<>
class ActionTraits<InstallPlanAction> {
public:
// Takes the install plan as input
typedef InstallPlan InputObjectType;
// Passes the install plan as output
typedef InstallPlan OutputObjectType;
};
到这里Action机制也分析的差不多了,我们可以回到BuildUpdateActions中继续进行分析了。
void UpdateAttempterAndroid::BuildUpdateActions(const string& url) {
CHECK(!processor_->IsRunning());
processor_->set_delegate(this); // Actions:
shared_ptr<InstallPlanAction> install_plan_action(
new InstallPlanAction(install_plan_)); HttpFetcher* download_fetcher = nullptr;
if (FileFetcher::SupportedUrl(url)) {
DLOG(INFO) << "Using FileFetcher for file URL.";
download_fetcher = new FileFetcher();
} else {
#ifdef _UE_SIDELOAD
LOG(FATAL) << "Unsupported sideload URI: " << url;
#else
LibcurlHttpFetcher* libcurl_fetcher =
new LibcurlHttpFetcher(&proxy_resolver_, hardware_);
libcurl_fetcher->set_server_to_check(ServerToCheck::kDownload);
download_fetcher = libcurl_fetcher;
#endif // _UE_SIDELOAD
}
shared_ptr<DownloadAction> download_action(
new DownloadAction(prefs_,
boot_control_,
hardware_,
nullptr, // system_state, not used.
download_fetcher)); // passes ownership
shared_ptr<FilesystemVerifierAction> filesystem_verifier_action(
new FilesystemVerifierAction()); shared_ptr<PostinstallRunnerAction> postinstall_runner_action(
new PostinstallRunnerAction(boot_control_, hardware_)); download_action->set_delegate(this);
download_action->set_base_offset(base_offset_);
download_action_ = download_action;
postinstall_runner_action->set_delegate(this); actions_.push_back(shared_ptr<AbstractAction>(install_plan_action));
actions_.push_back(shared_ptr<AbstractAction>(download_action));
actions_.push_back(shared_ptr<AbstractAction>(filesystem_verifier_action));
actions_.push_back(shared_ptr<AbstractAction>(postinstall_runner_action)); // Bond them together. We have to use the leaf-types when calling
// BondActions().
BondActions(install_plan_action.get(), download_action.get());
BondActions(download_action.get(), filesystem_verifier_action.get());
BondActions(filesystem_verifier_action.get(),
postinstall_runner_action.get()); // Enqueue the actions.
for (const shared_ptr<AbstractAction>& action : actions_)
processor_->EnqueueAction(action.get());
}
在这个方法里主要做了:
1.为processor_设置delegate,其实也就是注册了回调方法,UpdateAttempterAndroid实现了ActionProcessDelegate中的方法.
2. 创建了InstallPlanAction
3.创建了download_fetcher,我们这里假定用的是本地的文件既使用file:///协议,所以download_fetcher即为FileFetcher,从这一部分的代码可以看HtppFetcher,FileFetcher,LibcurlHttpFetcher之间具有继承或实现的关系。
4.创建DownloadAction,注意在创建的时候传入了download_fetcher为FileFetcher类型
5.创建FilesystemVerifierAction,PostinstallRunnerAction.从这里可以看出升级流程的精华应该就是这三个Action了
6.为download_action设置delegate,设置开始下载的offfset等,因为代码中设置delegate的操作比较多,如果不注意很有可能记混乱了。
7.为postinstall_runner_action设置delegate
8.将Action加入到Action的集合中
9.使用BondActions方法为Action之间建立管道。
10.将action遍历放入到processor_的队列中,并且设置action的管理者为processor_。
在分析完这个方法所干的事情之后,再分析一下HtppFetcher,FileFetcher,LibcurlHttpFetcher这三者之间的关系
HtppFetcher,FileFetcher,LibcurlHttpFetcher这三者之间的关系
现在继续分析ApplyPayload中的最后一个方法UpdateBootFlags()
void UpdateAttempterAndroid::UpdateBootFlags() {
if (updated_boot_flags_) {
LOG(INFO) << "Already updated boot flags. Skipping.";
CompleteUpdateBootFlags(true);
return;
}
// This is purely best effort.
LOG(INFO) << "Marking booted slot as good.";
if (!boot_control_->MarkBootSuccessfulAsync(
Bind(&UpdateAttempterAndroid::CompleteUpdateBootFlags,
base::Unretained(this)))) {
LOG(ERROR) << "Failed to mark current boot as successful.";
CompleteUpdateBootFlags(false);
}
}
首先检查当前运行的slot是否已经被标记为successful状态,如果是则调用CompleteUpdateBootFlags方法,否则的就调用MarkBootSuccessfulAsync将当前的slot标记为successful。标记完成后调用CompleteUpdateBootFlags方法
void UpdateAttempterAndroid::CompleteUpdateBootFlags(bool successful) {
updated_boot_flags_ = true;
ScheduleProcessingStart();
}
从这里看出即使标记失败了仍然调用 ScheduleProcessingStart(),这个方法主要就是开始执行Action
void UpdateAttempterAndroid::ScheduleProcessingStart() {
LOG(INFO) << "Scheduling an action processor start.";
brillo::MessageLoop::current()->PostTask(
FROM_HERE,
Bind([](ActionProcessor* processor) { processor->StartProcessing(); },
base::Unretained(processor_.get())));
}
在来看看StartProcessing()方法的实现,首先是获取对列中的第一个action,打印action的类型,之后将action移出队列,并且调用PerformAction。
src/system/update_engine/common/action_processor.cc
void ActionProcessor::StartProcessing() {
CHECK(!IsRunning());
if (!actions_.empty()) {
current_action_ = actions_.front();
LOG(INFO) << "ActionProcessor: starting " << current_action_->Type();
actions_.pop_front();
current_action_->PerformAction();
}
}
分析到了这里就对整体的update_engine有了一定的了解,接下来只需要对各个Action逐个击破就好了。在之前已经看过了InstallPlanAction,它的内容很简单,仅仅是在输出管道中设置了install_plan_,接下来就调用了processor_->ActionComplete(this, ErrorCode::kSuccess),看一下ActionComplete的内容,它是如何让下一个action开始执行的。
void ActionProcessor::ActionComplete(AbstractAction* actionptr,
ErrorCode code) {
CHECK_EQ(actionptr, current_action_);
if (delegate_)
delegate_->ActionCompleted(this, actionptr, code);
string old_type = current_action_->Type();
current_action_->ActionCompleted(code);
current_action_->SetProcessor(nullptr);
current_action_ = nullptr;
LOG(INFO) << "ActionProcessor: finished "
<< (actions_.empty() ? "last action " : "") << old_type
<< (suspended_ ? " while suspended" : "")
<< " with code " << utils::ErrorCodeToString(code);
if (!actions_.empty() && code != ErrorCode::kSuccess) {
LOG(INFO) << "ActionProcessor: Aborting processing due to failure.";
actions_.clear();
}
if (suspended_) {
// If an action finished while suspended we don't start the next action (or
// terminate the processing) until the processor is resumed. This condition
// will be flagged by a nullptr current_action_ while suspended_ is true.
suspended_error_code_ = code;
return;
}
StartNextActionOrFinish(code);
}
其实这个方法中也就进行了善后和开始下一个Action的工作。包括:
1.判断是否注册了回调方法。这里的delegate_的类型为UpdateAttempterAndroid。如果注册了就回调ActionCompleted方法,在UpdateAttempterAndroid中它的内容为
void UpdateAttempterAndroid::ActionCompleted(ActionProcessor* processor,
AbstractAction* action,
ErrorCode code) {
// Reset download progress regardless of whether or not the download
// action succeeded.
const string type = action->Type();
if (type == DownloadAction::StaticType()) {
download_progress_ = ;
}
if (code != ErrorCode::kSuccess) {
// If an action failed, the ActionProcessor will cancel the whole thing.
return;
}
if (type == DownloadAction::StaticType()) {
SetStatusAndNotify(UpdateStatus::FINALIZING);
}
}
可以看到这个方法主要就是为重置下载的进度。
2.调用当前的Action的ActionCompleted,将Processor和当前Action置空等。
3.如果执行到某人Action的时候出了错,则停止执行其他的Action
4. processor如果被挂起,则暂停执行下一个Action
5.执行下一个Action或者是否完成了所有的Action,StartNextActionOrFinish(code),该方法比较简单,就不进行分析了。
到这里整体的Action的执行流程也就通了,下一篇开始会分析其他三个Action
.
update_engine-整体结构(三)的更多相关文章
- Nginx基本功能及其原理,配置原理
Nginx基本功能及其原理,配置原理 一.正向代理.反向代理 二.Nginx配置文件的整体结构 三.Nginx配置SSL及HTTP跳转到HTTPS 四.nginx 配置管理 [nginx.conf 基 ...
- 阅读笔记:ImageNet Classification with Deep Convolutional Neural Networks
概要: 本文中的Alexnet神经网络在LSVRC-2010图像分类比赛中得到了第一名和第五名,将120万高分辨率的图像分到1000不同的类别中,分类结果比以往的神经网络的分类都要好.为了训练更快,使 ...
- 车牌识别LPR(三)-- LPR系统整体结构
第三篇:系统的整体架构 LPR系统大体上可由图像采集系统,图像处理系统,数据库管理系统三个子系统组成.它综合了通讯.信息.控制.传感.计算机等各种先进技术,构成一个智能电子系统. 图像采集系统:图像采 ...
- 浅谈Excel开发:三 Excel 对象模型
前一篇文章介绍了Excel中的菜单系统,在创建完菜单和工具栏之后,就要着手进行功能的开发了.不论您采用何种方式来开发Excel应用程序,了解Excel对象模型尤其重要,这些对象是您与Excel进行交互 ...
- lucene学习笔记:三,Lucene的索引文件格式
Lucene的索引里面存了些什么,如何存放的,也即Lucene的索引文件格式,是读懂Lucene源代码的一把钥匙. 当我们真正进入到Lucene源代码之中的时候,我们会发现: Lucene的索引过程, ...
- 《InsideUE4》UObject(三)类型系统设定和结构
垃圾分类,从我做起! 引言 上篇我们谈到了为何设计一个Object系统要从类型系统开始做起,并探讨了C#的实现,以及C++中各种方案的对比,最后得到的结论是UE采用UHT的方式搜集并生成反射所需代码. ...
- Lucene.Net 2.3.1开发介绍 —— 二、分词(三)
原文:Lucene.Net 2.3.1开发介绍 -- 二.分词(三) 1.3 分词器结构 1.3.1 分词器整体结构 从1.2节的分析,终于做到了管中窥豹,现在在Lucene.Net项目中添加一个类关 ...
- 【Beta】 第三次Daily Scrum Meeting
一.本次会议为第三次meeting会议 二.时间:10:00AM-10:20AM 地点:禹州楼 三.会议站立式照片 四.今日任务安排 成员 昨日任务 今日任务 林晓芳 查询app提醒功能模块和用户登录 ...
- SSM框架开发web项目系列(三) MyBatis之resultMap及关联映射
前言 在上篇MyBatis基础篇中我们独立使用MyBatis构建了一个简单的数据库访问程序,可以实现单表的基本增删改查等操作,通过该实例我们可以初步了解MyBatis操作数据库需要的一些组成部分(配置 ...
- 操作系统内核Hack:(三)引导程序制作
操作系统内核Hack:(三)引导程序制作 关于本文涉及到的完整源码请参考MiniOS的v1_bootloader分支. 1.制作方法 现在我们已经了解了关于BootLoader的一切知识,让我们开始动 ...
随机推荐
- wps实现自动编码
1.打开wps,点击“开始”菜单,选择“标题1”样式,右键选择“修改样式” 2.在修改样式界面,左键点击“格式”选择“编号”选项 3.在打开的界面选择“多级编码”,选中一个样式,点击“自定义”按钮 4 ...
- KendoUi 学习笔记(二) Grid
Kendo.ui.Grid Kendo Ui Grid控件,继承至Widget. 一.构造 allowCopy Boolen|Object (默认:false) 当他设置true, ...
- 【原创】Arduino、arm、树莓派与单片机
Arduino是一个由开源软件环境支持的开源硬件原型平台.包含硬件(各种型号Arduino板)和软件(Arduino IDE).集成开发环境是Arduino IDE ,编写和编译sketch后通过US ...
- c# 连接数据库SqlHelper
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threa ...
- win7安装python3.6.1及scrapy
---恢复内容开始--- 第一篇博客,记录自己自学python的过程及问题. 首先下载python3.6.1及所需资料 百度云:https://pan.baidu.com/s/1geOEp6z 密码: ...
- Mac 安装Python3 facewap环境
参考网上大神的方法 1 官网下载安装 2 下载指定版本的源码cmake安装 3 Mac上使用homebrew进行安装(强烈推荐,主要是前两种的openssl模块我没有搞定链接什么的一直报错,一个个下载 ...
- git教程:工作区和暂存区
Git和其他版本控制系统如SVN的一个不同之处就是有暂存区的概念. 先来看名词解释. 工作区(Working Directory) 就是你在电脑里能看到的目录,比如我的learngit文件夹就是一个工 ...
- OpenSSL生成RSA公私钥(java)
生成私钥:genrsa -out rsa_private_key.pem 1024 生成公钥:rsa -in rsa_private_key.pem -out rsa_public_key.pem - ...
- ubuntu两个conda安装和切换
1. 下载anaconda2安装,一路默认,注意添加/home/wang/.bashrc选择yes 2. 在/home/wang/envs/py3安装anaconda3,其他同anaconda2 3. ...
- struts1和struts2和springMVC的区别和介绍
MVC是web开发常用的模式,M即模型层(Model):主要由javabean来实现.V即视图层(View):主要由jsp.velocity.freemarker等.C即控制层(Controller) ...