这两天有空尝试了一下用golang的walk开发一个桌面应用,主要实现可以实时监控本机的内存使用率和CPU使用率。效果如下
然后我总结下我遇见的问题:
- 桌面程序还是老老实实用C或者C#开发比较友好一点
- golang可以获取到各项数据,同时效率也并没有太大问题,可能是协程YYDS,但是CPU会占用较多。特别是用walk绘制了桌面程序之后,达到了20%,个人理解是因为一直在重绘列表,但是不重绘列表就无法实现我想要的功能,这是个问题。
- 再说下walk。文档实在是少得可怜。官方文档只有一个方法名,demo里面也没有注释,只能一步步打断点才知道当前方法是干嘛的。不知道是不是因为使用go开发桌面应用程序的人比较少,还是因为用了别的库
- 当前大部分能找到的walk资料都是停留在第一层,就是打开一个输入框和显示一个按钮。其他方法还需要自行研究
例如以下:
## 关于walk打包
* 安装walk go get github.com/lxn/walk
* 安装walk的ui库 go get github.com/lxn/win
> walk文档 https://pkg.go.dev/github.com/lxn/walk#section-documentation
* 安装一个工具rsrc
> go get github.com/akavel/rsrc
> 同时,把exe下载下来并放到目录文件
https://github.com/akavel/rsrc/releases/tag/v0.10.2
* rsrc工具安装好后,我们还需建一个manifest文件 test.manifest
```
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<assemblyIdentity version="1.0.0.0" processorArchitecture="*" name="SomeFunkyNameHere" type="win32"/>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
</dependentAssembly>
</dependency>
<asmv3:application>
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
<dpiAware>true</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
</assembly>
```
* 生成 rsrc.syso
> rsrc.exe -manifest test.manifest -o rsrc.syso
* 然后打包成exe文件
> go build
* 此时打开exe,会带着cmd命令窗口,接下来我们把这命令窗口去除
> go build -ldflags="-H windowsgui"
目录结构:
以下是完整代码:
// main.go
package main
import (
"fmt"
"log"
"os"
"sort"
"time"
"github.com/lxn/walk"
. "github.com/lxn/walk/declarative"
"github.com/shirou/gopsutil/process"
)
type ProcessInfo struct {
Name string
MemUsage float32 // 内存使用率
Mem uint64 // 使用的内存
}
type ProcessCPUInfo struct {
Name string // 进程名称
CPU float64 // CPU使用率
}
// ProcessModel 用于在TableView中显示进程信息
type ProcessModel struct {
walk.TableModelBase
items []ProcessInfo
}
// ProcessCPUModel 用于在TableView中显示CPU进程信息
type ProcessCPUModel struct {
walk.TableModelBase
items []ProcessCPUInfo
}
func (m *ProcessModel) RowCount() int {
return len(m.items)
}
func (m *ProcessModel) Value(row, col int) interface{} {
item := m.items[row]
switch col {
case 0:
return item.Name
case 1:
return item.MemUsage
case 2:
return float64(item.Mem) / 1024 / 1024 // 转换为MB
}
panic("unexpected col")
}
func (m *ProcessModel) setItems(items []ProcessInfo) {
m.items = items
m.PublishRowsReset() // 通知视图数据已重置,需要刷新
}
func (m *ProcessCPUModel) RowCount() int {
return len(m.items)
}
func (m *ProcessCPUModel) Value(row, col int) interface{} {
item := m.items[row]
switch col {
case 0:
return item.Name
case 1:
return item.CPU
}
panic("unexpected col")
}
func (m *ProcessCPUModel) setCpuItems(items []ProcessCPUInfo) {
m.items = items
m.PublishRowsReset() // 通知视图数据已重置,需要刷新
}
var mw *walk.MainWindow
var model *ProcessModel
var cpuModel *ProcessCPUModel
func getProcessList() {
for {
processes, err := process.Processes()
if err != nil {
fmt.Printf("获取进程信息失败: %v\n", err)
os.Exit(1)
}
var procs []ProcessInfo
for _, p := range processes {
name, err := p.Name()
if err != nil {
continue
}
memInfo, err := p.MemoryInfo()
if err != nil {
continue
}
memPercent, err := p.MemoryPercent()
if err != nil {
continue
}
procs = append(procs, ProcessInfo{
Name: name,
MemUsage: memPercent,
Mem: memInfo.RSS,
})
}
// 排序,根据内存使用量从高到低
sort.Slice(procs, func(i, j int) bool {
return procs[i].Mem > procs[j].Mem
})
// 只保留前五条
if len(procs) > 5 {
procs = procs[:10]
}
// 在UI线程中更新表格
mw.Synchronize(func() {
model.setItems(procs)
})
// 等待一段时间后再次更新
time.Sleep(18)
}
}
func getProcessCpuList() {
for {
processes, err := process.Processes()
if err != nil {
fmt.Printf("获取进程列表时出错: %v\n", err)
os.Exit(1)
}
var procCPUInfo []ProcessCPUInfo
for _, p := range processes {
name, err := p.Name()
if err != nil {
continue
}
cpu, err := p.CPUPercent()
if err != nil {
continue
}
procCPUInfo = append(procCPUInfo, ProcessCPUInfo{
Name: name,
CPU: cpu,
})
}
// 排序
sort.Slice(procCPUInfo, func(i, j int) bool {
return procCPUInfo[i].CPU > procCPUInfo[j].CPU
})
// 只保留前五条
if len(procCPUInfo) > 5 {
procCPUInfo = procCPUInfo[:10]
}
// 在UI线程中更新表格
mw.Synchronize(func() {
cpuModel.setCpuItems(procCPUInfo)
})
// 等待一段时间后再次更新
time.Sleep(18)
}
}
func start() {
model = new(ProcessModel)
cpuModel = new(ProcessCPUModel)
// 初始化GUI应用程序
if err := (MainWindow{
AssignTo: &mw,
Title: "Process List",
Layout: VBox{},
Size: Size{Width: 600, Height: 400},
Children: []Widget{
TableView{
Columns: []TableViewColumn{
{Title: "进程"},
{Title: "内存使用率(%)"},
{Title: "内存使用量(MB)"},
},
Model: model,
},
TableView{
Columns: []TableViewColumn{
{Title: "进程"},
{Title: "CPU使用率(%)"},
},
Model: cpuModel,
},
},
}).Create(); err != nil {
log.Fatal(err)
return
}
// 启动后台goroutine定期更新进程信息
go getProcessList()
go getProcessCpuList()
mw.Run() // 启动事件处理循环
}
func main() {
start() // 启动UI并在后台获取进程信息
}