在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-整体结构(三)的更多相关文章

  1. Nginx基本功能及其原理,配置原理

    Nginx基本功能及其原理,配置原理 一.正向代理.反向代理 二.Nginx配置文件的整体结构 三.Nginx配置SSL及HTTP跳转到HTTPS 四.nginx 配置管理 [nginx.conf 基 ...

  2. 阅读笔记:ImageNet Classification with Deep Convolutional Neural Networks

    概要: 本文中的Alexnet神经网络在LSVRC-2010图像分类比赛中得到了第一名和第五名,将120万高分辨率的图像分到1000不同的类别中,分类结果比以往的神经网络的分类都要好.为了训练更快,使 ...

  3. 车牌识别LPR(三)-- LPR系统整体结构

    第三篇:系统的整体架构 LPR系统大体上可由图像采集系统,图像处理系统,数据库管理系统三个子系统组成.它综合了通讯.信息.控制.传感.计算机等各种先进技术,构成一个智能电子系统. 图像采集系统:图像采 ...

  4. 浅谈Excel开发:三 Excel 对象模型

    前一篇文章介绍了Excel中的菜单系统,在创建完菜单和工具栏之后,就要着手进行功能的开发了.不论您采用何种方式来开发Excel应用程序,了解Excel对象模型尤其重要,这些对象是您与Excel进行交互 ...

  5. lucene学习笔记:三,Lucene的索引文件格式

    Lucene的索引里面存了些什么,如何存放的,也即Lucene的索引文件格式,是读懂Lucene源代码的一把钥匙. 当我们真正进入到Lucene源代码之中的时候,我们会发现: Lucene的索引过程, ...

  6. 《InsideUE4》UObject(三)类型系统设定和结构

    垃圾分类,从我做起! 引言 上篇我们谈到了为何设计一个Object系统要从类型系统开始做起,并探讨了C#的实现,以及C++中各种方案的对比,最后得到的结论是UE采用UHT的方式搜集并生成反射所需代码. ...

  7. Lucene.Net 2.3.1开发介绍 —— 二、分词(三)

    原文:Lucene.Net 2.3.1开发介绍 -- 二.分词(三) 1.3 分词器结构 1.3.1 分词器整体结构 从1.2节的分析,终于做到了管中窥豹,现在在Lucene.Net项目中添加一个类关 ...

  8. 【Beta】 第三次Daily Scrum Meeting

    一.本次会议为第三次meeting会议 二.时间:10:00AM-10:20AM 地点:禹州楼 三.会议站立式照片 四.今日任务安排 成员 昨日任务 今日任务 林晓芳 查询app提醒功能模块和用户登录 ...

  9. SSM框架开发web项目系列(三) MyBatis之resultMap及关联映射

    前言 在上篇MyBatis基础篇中我们独立使用MyBatis构建了一个简单的数据库访问程序,可以实现单表的基本增删改查等操作,通过该实例我们可以初步了解MyBatis操作数据库需要的一些组成部分(配置 ...

  10. 操作系统内核Hack:(三)引导程序制作

    操作系统内核Hack:(三)引导程序制作 关于本文涉及到的完整源码请参考MiniOS的v1_bootloader分支. 1.制作方法 现在我们已经了解了关于BootLoader的一切知识,让我们开始动 ...

随机推荐

  1. Shell test 命令

    Shell中的 test 命令用于检查某个条件是否成立,它可以进行数值.字符和文件三个方面的测试. 数值测试 参数 说明 -eq 等于则为真 -ne 不等于则为真 -gt 大于则为真 -ge 大于等于 ...

  2. js 获取字符串中所有的数字和汉字

    var re1 = /(\d{1,3})+(?:\.\d+)?/g var re2 = /[\u4e00-\u9fa5]{2,}/g var str="11我22们33两个"; v ...

  3. electron-vue初始桌面应用

    1.安装vue-cli脚手架 npm install -g vue-cli 2.创建项目:vue init simulatedgreg/electron-vue my-project 3.安装依赖 : ...

  4. php的array数组 -------方法array_column()

    array_column($arr,'valColumn','keyColumn'); 此方法是用户二维数组,如下例子: $arr=array( array('id'=>1,'name'=> ...

  5. Qt设置创建部分半透明,上面控件不透明

    //头文件#pragma once #include <QWidget> #include "ui_widgetFullAD.h" class widgetFullAD ...

  6. Linux 驱动——Button驱动3(poll机制)

    button_drv.c驱动文件: #include <linux/module.h>#include <linux/kernel.h>#include <linux/f ...

  7. JIRA 7.8 版本的安装与破解

    jira的运行是依赖java环境的,也就是说需要安装jdk并且要是1.8以上版本 除此之外,我们还需要安装MySQL,为jira创建对应的数据库.用户名和密码,如下: 注意:建库名jira,字符集为U ...

  8. android 事件绑定

    layout布局设计了页面,如何绑定事件,与用户进行交互需要在Activity中进行处理. 下面的layout,有两个按钮. <LinearLayout android:layout_width ...

  9. android 版本更新适配8.0,解决8.0手机无法更新自动安装apk

    随着android 7.0的普及android 8.0的也逐渐流行起来,那么google对权限方面又有了新的修改.而且我发现在android8.0中除了一些bug,比如说:在小米6(Android 8 ...

  10. 查看win10系统产品密钥

    查看win10系统产品密钥 1.win+R 输入Regedit运行注册表 2.找到(在HKEY_LOCAL_MACHINE–>SOFTWARE–>Microsoft–>Windows ...