在 macOS 上用 VirtualBox 安装 CentOS

最近为了更好的实践 Linux,决定在 mac 上使用 VirtualBox 安装一个 CentOS,主要是参考鸟哥的这篇安裝 CentOS7.x

安装之后打开系统出现闪屏,英语应该是称为 screen flicker,google 之后在 VirtualBox 的论坛找到解决方法:

  1. 进入单用户维护模式

     a. 重启系统
     b. 在菜单选择界面键入 e,进入 grub2 的指令编辑模式
     c. 在指定内核和根文件系统这行最后加上 systemd.unit=rescue.target
     d. 键入 ctrl + x 进入系统
    
  2. 强制使用 Xorg

      a. 用 vim 打开 /etc/gdm3/custom.conf
      b. 删除 WaylandEnable=false 前的 # 注释符号
      c. 保存文件后,systemctl default 来进入正常模式 
    

解决了闪屏之后,想通过虚拟机菜单中的调整窗口大小来让系统的屏幕全屏发现无用,想起来应该要安装 VirtualBox Guest Additions,于是插入虚拟机提供的光盘来安装。

首先是提示 kernel headers not found for target kernel 的错误,也提示详细的错误信息位于 /var/log/vboxadd-setup.log,我们可以通过查看该错误日志来找到对应解决方法。于是尝试安装对应的内核头文件,命令为 yum install kernel-headers kernel-devel,之后执行 /sbin/rcvboxadd setup.

仍然提示 kernel headers not found for target kernel,通过 uname -r 和 rpm -q kernel-headers 发现版本不一致,于是重启系统选择最新的内核版本。

再次尝试安装,提示 Error building the module,查看错误日志提示需要安装 libelf-dev, libelf-devel or elfutils-libelf-devel ,CentOS 上只有 elfutils-libelf-devel ,安装之后再次安装 VirtualBox Guest Additions。

提示

1
2
3
ValueError: File context for /opt/VBoxGuestAdditions-6.0.14/other/mount.vboxsf already defined
VirtualBox Guest Additions: Running kernel modules will not be replaced until
the system is restarted

这个问题暂时没找到解决方法,但是可以让 CentOS 全屏了,就暂时先不管这个问题了。

释放虚拟机硬盘空间

在虚拟机使用过程中硬盘的空间会慢慢增加,但是即使虚拟机中删除了文件实际占用空间减少,外部的硬盘文件大小仍然没有减少,这对小硬盘电脑可伤不起,于是想办法释放虚拟机磁盘空间。大前提是虚拟机的硬盘类型是 Dynamically allocated storage,主要分为两大步:

  1. 在虚拟机寄主系统(如 CentOS)中删除文件释放空间并压缩硬盘
  2. 在虚拟机宿主系统(如 macOS)中压缩硬盘文件

下面以 Windows 10 为例:

  1. 开始按钮 > 设置 > 系统 > 存储空间 > 根据空间占用选择删除无用的文件释放之间
  2. 在左下方搜索框中搜索 Defragment ,然后打开 Defragment and Optimize Drives,选择想要压缩的硬盘进行压缩;
  3. 从微软下载 SDelete 助手
  4. 使用 sdelete 填充释放的硬盘空间,假设 SDelete 下载之后的放在 Downloads 目录下,我们想压缩 c 盘
1
2
cd "C:\Users\bob\Downloads"
sdelete.exe c: -z
  1. 最后在宿主系统中压缩硬盘文件,例如我是 macOS:
1
2
3
4
$ /Applications/VirtualBox.app/Contents/MacOS/VBoxManage list hdds
# 找到想要压缩的硬盘文件路径 

$ /Applications/VirtualBox.app/Contents/MacOS/VBoxManage modifymedium disk /Users/meiliang/VirtualBox\ VMs/Windows\ 10/Windows\ 10.vdi --compact

修改记录

  • 2020/10/02:增加释放虚拟机硬盘空间的方法
  • 2020/01/04:第一次完成

Reference:

创建 Cordova Plugin 及其 Ionic Native

本文介绍如何创建 Cordova plugin 及其 Ionic Native,主要内容如下:

  • Cordova Plugin 的工作原理
  • 如何创建 Cordova Plugin
  • 如何创建 Cordova Plugin 对应的 Ionic Native

Cordova Plugin 的工作原理

我们简要介绍下 Cordova Plugin 的工作原理,这样我们才能解决在开发中遇到的问题。

Plugin js 端的入口方法签名形式如下:

1
exec(<successFunction>, <failFunction>, <service>, <action>, [<args>]);

successFunction, failFunction 是成功和失败的回调函数,args 则是传递给原生端的参数。service, action 则是用来映射到原生端的对象和方法。这个映射是通过 plugin.xml 建立起来的。在 plugin.xml 中我们有如下元数据信息:

1
2
3
<feature name="<service_name>">
    <param name="android-package" value="<full_name_including_namespace>" />
</feature>

service 就是对应 service_name 指定的对象,而 action 则是该对象能处理方法。

具体是怎么映射的呢?

以 android 为例,我们安装的 plugin 信息会保存在 config.xml 中,cordova prepare android 命令会将 config.xml 在 android 的资源文件目录中生成一个同名文件,两个文件内容大致相同,plugin 的信息会改成如下形式表示:

1
2
3
<feature name="<service_name>">
    <param name="android-package" value="<full_name_including_namespace>" />
</feature>

这个形式就是我们上面见过的形式。

当 js 端调用 exec 方法时,它会通过 webview 建立的通信通道(通常是用 WebView.addJavascriptInterface)调用 PluginManager 的 exec 方法,PluginManager 则根据 service_name 查找或创建 plugin ,然后调用 plugin 的 exec 方法,并将 action 作为参数传入,于是我们便可按需响应 action 请求。

如何创建 Cordova Plugin

安装 plugman

1
$ npm install -g plugman

创建 cordova plugin

1
2
3
4
$ plugman create --name <pluginName> --plugin_id <pluginID> --plugin_version <version> [--path <directory>] [--variable NAME=VALUE]

eg.
$plugman create --name cordova-plugin-onsite-signature --plugin_id cordova-plugin-onsite-signature --plugin_version 0.0.1

添加 platform

1
$plugman platform add --platform_name android

创建 package.json 文件

1
2
3
4
$ plugman createpackagejson <directory>

eg.
$plugman createpackagejson .

安装 cordova plugin

1
2
3
4
5
// 方法一:这种方式的命令和添加官方的插件类似,个人推荐此方法,可以少记一个命令
$cordova plugin add git+ssh://git@192.168.8.91/git/cordova-plugin-onsite-signature.git

// 方法二:
$ plugman install --platform android --project platforms/android --plugin ../LogicLinkPlugin/

卸载 cordova plugin

1
2
3
4
5
// 与安装的方法对应有两种卸载方法
// 方法一:$cordova plugin remove cordova-plugin-onsite-signature

// 方法二:
$ plugman uninstall --platform android --project platforms/android --plugin ../LogicLinkPlugin/

发布

1
2
3
4
5
// Create a tag
$git tag <tagname>

// Push to repository
$git push origin master

升级 cordova plugin

现在暂时没有直接升级的命令,采用的是先卸载后安装新版本的方法。

如何创建 Cordova Plugin 对应的 Ionic Native

Creating Plugin Wrappers

1
2
3
4
5
6
7
8
9
10
11
12
// Navigate to ionic-native root path
$cd  ionic-native

// Install dependencies first time
$npm install

// Create plugin wrapper
// When gulp installed locally
$npx gulp plugin:create -n PluginName

// When gulp installed globally
$gulp plugin:create -n PluginName

安装

1
2
3
4
5
// You need to run npm run build in the ionic-native project, this will create a dist directory. The dist directory will contain a sub directory @ionic-native with all the packages compiled in there.
$npm run build

//Copy the package(s) you created/modified to your app's node_modules under the @ionic-native directory.
$cp -r dist/@ionic-native/plugin-name ../my-app/node_modules/@ionic-native/

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// Import the plugin in a @NgModule and add it to the list of Providers. 
// app.module.ts
import { APIClient } from '@ionic-native/api-client/ngx';
...

@NgModule({
  ...

  providers: [
    ...
    APIClient
    ...
  ]
  ...
})
export class AppModule { }

// After the plugin has been declared, it can be imported and injected like any other service:

// login.service.ts
import { APIClient } from '@ionic-native/api-client/ngx';
import { ServiceName } from '@ionic-native/api-client/ngx';

constructor(private apiClient: APIClient) { }

this.apiClient.get(ServiceName.Login, JSON.stringify(user))
      .then((result: string) => {
        console.log('api client login:', result);
        //TODO: Parse server return json to UserExt object
        const routePath = this.simulateLogin(username);        
        resolve(routePath);
      })
      .catch((error: string) => {
        console.log('api client login error:', error);

        reject(error);
      });

Reference:

Web 开发问题汇总(四)

1.如何实现如下布局:两个元素A和B,其中A的宽度为包裹其内容,B则占用剩余的宽度?

A:A 元素利用 float 的包裹性,B 元素则利用 BFC。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div class="container">
    <div class="right"></div>
    <div class="left"></div>
</div>

.container {
    width:600px;
    height:200px;
    border:1px solid;
}
.left {
    width:auto;
    height:200px;
    background:red;
    overflow:hidden;
}
.right {
    height:200px;
    width:200px;
    background:blue;
    float:left;
}

Reference:Expand a div to fill the remaining width

2.Meaning of ~ in import of scss files

A:

From documentation on a sass-loader#imports project,

webpack provides an advanced mechanism to resolve files. The sass-loader uses node-sass' custom importer feature to pass all queries to the webpack resolving engine. Thus you can import your Sass modules from node_modules. Just prepend them with a ~ to tell webpack that this is not a relative import

So if you have a file named foo.css and a module foo then you would use ~ if you want to include the module.

Reference:Meaning of ~ in import of scss files

继续阅读