分类 编程相关 下的文章

Eclipse虽老,但仍然好用,特别是对于老Java码农来说。

1. 版本选择

一般Java的Web开发,选择Eclipse IDE for Enterprise Java and Web Developers。官方下载链接如下:
https://www.eclipse.org/downloads/packages/

2. 实用的初始化设置

1)修改默认字符集为UTF-8

windows -> Preferences -> General -> Workspace -> Text file encoding -> 选UTF-8

2)修改默认字体,改善中文显示太小

windows -> Preferences -> General -> Appearance -> Colors and Fonts -> Basic -> Text Font -> Edit -> 选Courier New

3)代码自动提示功能失效的解决方法

Window -> Preferences -> Java -> Editor -> Content Assist -> Advanced -> Select the proposal kinds contained in the 'default' content assist list -> 勾选"Java Non-Type Proposals"、"Java Proposals"、"Java Type Proposals"三个选项

4)在输错方法后仍然出现代码自动提示

Window -> Preferences -> Java -> Editor -> Content Assist -> Auto Activation triggers for java -> 改为.abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ

5)禁用Language Servers,提高Eclipse性能

Window -> Preferences -> Language Servers -> 取消所有勾选

6)安装插件Quick Bookmarks plugin,快速的标记和访问书签

  • 安装:Help -> Eclipse Marketplace -> 搜“Quick Bookmarks plugin”并安装
  • 使用:Alt+[数字],标记书签。Alt+Shift+[数字],跳转到书签。

工作上遇到需要在浏览器展示PDF文件,于是找到了Mozilla开发的PDF.js。玩了一下,结论是,直接使用其自带的示例是简单的方法。

PDF.js的官网:https://mozilla.github.io/pdf.js/
github项目页面:https://github.com/mozilla/pdf.js

主要是没找到有用的开发文档,而自己也不会Node.js的开发。所以尝试自己写的页面,只能利用HTML 5的canvas展示PDF内容。官方自带的示例,会把PDF转换成HTML,并且有显示目录、缩略图、打印、等各种实用的功能,媲美很多完整的PDF阅读软件。

这里记下写过的页面,可以翻页、放大缩小:

<h4>显示PDF</h4>
<p>
    <button id="prePage">上一页</button> <input type="text" id="curPage" value="" readonly /> <button id="nextPage">下一页</button>
    <br />
    <button id="zoomIn">+</button> <input type="text" id="zoomScale" value="1" readonly /> <button id="zoomOut">-</button>
</p>
<p>
    <canvas id="the-canvas" style="border: 1px solid black; direction: ltr;"></canvas>
</p>

<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
<script src="/js/pdfjs/pdf.js"></script>
<script>
//加载PDF按钮
pdfjsLib.GlobalWorkerOptions.workerSrc = '/js/pdfjs/pdf.worker.js';//自己的路径

var loadingTask = pdfjsLib.getDocument('/pdfjs/docker.pdf'); 
var pdfDoc = null;
var totalpage = 0;
var curPage = $("#curPage");
var zoomScale = $("#zoomScale");

loadingTask.promise.then(function (pdf) {
    //加载指定界面(第一页)
    pdfDoc = pdf;
    totalpage = pdfDoc.numPages;
    getPage(1);
});

function getPage(pageNum) {
    if(pageNum < 1) {
        pageNum = 1;
    } else if(pageNum > totalpage) {
        pageNum = totalpage;
    }
    setCurPageNum(pageNum);
    pdfDoc.getPage(pageNum).then(function (page) {
        //var scale = 1;
        var viewport = page.getViewport({ scale: getZoomScale() });
        var canvas = document.getElementById('the-canvas');
        var context = canvas.getContext('2d');
        canvas.height = viewport.height;
        canvas.width = viewport.width;
        var renderContext = {
            canvasContext: context,
            viewport: viewport,
        };
        page.render(renderContext);
    });
}

function makeThumb(page) {
    // draw page to fit into 96x96 canvas
    var vp = page.getViewport(1);
    var canvas = document.createElement("canvas");
    canvas.width = canvas.height = 96;
    var scale = Math.min(canvas.width / vp.width, canvas.height / vp.height);
    return page.render({canvasContext: canvas.getContext("2d"), viewport: page.getViewport(scale)}).promise.then(function () {
        return canvas;
    });
}

function loadThumb() {
    var pages = []; while (pages.length < pdfDoc.numPages) pages.push(pages.length + 1);
    return Promise.all(pages.map(function (num) {
        // create a div for each page and build a small canvas for it
        var div = document.createElement("div");
        document.body.appendChild(div);
        return pdfDoc.getPage(num).then(makeThumb).then(function (canvas) {
            div.appendChild(canvas);
        });
    }));
}

function getCurPageNum() {
    return parseInt(curPage.val());
}
function setCurPageNum(pageNum) {
    return curPage.val(pageNum);
}
function getZoomScale() {
    return parseFloat(zoomScale.val());
}
function setZoomScale(scale) {
    return zoomScale.val(scale);
}
$("#prePage").click(function(){
    var pageNum = getCurPageNum() - 1;
    getPage(pageNum);
})
$("#nextPage").click(function(){
    var pageNum = getCurPageNum() + 1;
    getPage(pageNum);
})
$("#zoomIn").click(function(){
    var scale = getZoomScale();
    scale = Math.round((scale + 0.2) * 100) / 100;
    scale > 2 && (scale = 2);
    setZoomScale(scale);
    getPage(getCurPageNum());
})
$("#zoomOut").click(function(){
    var scale = getZoomScale();
    scale = Math.round((scale - 0.2) * 100) / 100;
    scale < 0.6 && (scale = 0.6);
    setZoomScale(scale);
    getPage(getCurPageNum());
})
</script>

最近接触了两款开源、跨平台、支持多种SQL数据库的数据库管理工具,值得记录一下。

DBeaver
官网:https://dbeaver.io/
2020年疫情期间,在家办公,想找个数据库管理工具,可以在Linux上访问SQL Server数据库,于是遇到DBeaver。界面像Eclipse,容易上手;基于Java,可以跨平台使用;使用JDBC,几乎支持所有数据库。在Linux上,几乎是万能的数据库管理工具了。

HeidiSQL
官网:https://www.heidisql.com/
在Windows上安装MariaDB 10.4.12时,发现自带了HeidiSQL数据库管理工具。界面及操作都跟MySQL Workbench相似,清晰明了,而且支持各种SQL数据。比较意外的是,其基于Delphi开发,所以Linux上需要利用Wine运行。

Google Camera(简称gcam)自推出以来,一直是最喜爱的摄影应用(可惜后来变成Pixel机型专属应用)。幸好有开发者移植到其它机型,特别是像红米Note 4X那种渣拍照的手机,极大提升了拍照能力。近来在红米K30 5G上也装上了gcam,并默认开启了“动态照片(Motion Photo)”功能。本来没啥影响,只是拍出来一堆文件名以MVIMG开头或者扩展名前带有“.MP”的照片,昨天好奇研究了一下,才发现就是动态照片。

动态照片,简单来说,就是把拍照前几秒录下视频,并把视频与拍出来的照片整合在一起,生成一个jpg文件。目前发现两种格式:文件名后面带有“.MP”的,在EXIF信息里会说明mp4文件在整个jpg文件中位置;文件名以“MVIMG_”开头的,jpg文件里有个“ftypmp4”标识,该标识后面的数据就是mp4文件。

其实动态照片也没什么坏处,但是找了一圈也找不到可以直接浏览动态图片的Android应用。如果是看不到的视频,那保存在照片中,有什么意义?还浪费了存储空间,也不利于分享、传输。最后找了个工具,叫GoMoPho,把所有拍摄的动态照片都转为普通jpg文件。其实就是把动态文件切割为jpg和mp4两个文件,我保留了jpg并删除了mp4。GoMoPho的相关网址如下:

Google motion photos video extractor.
https://github.com/cliveontoast/GoMoPho

GoMoPho虽然只有命令界面,但是支持上面提到的两种动态照片,而且已移植到多种操作系统,还能支持批量处理文件夹的文件。

作为MIUI恐惧者,无奈LineageOS官方没有支持这台红米K30 5G,只能诚惶诚恐地使用欧版MIUI。直到某天发现了Resurrection Remix OS官方支持这款手机,才终于脱离MIUI。

查了资料,才知道Resurrection Remix OS是基于LineageOS的一款开源ROM,基本体验与LineageOS一致,并增加了很多设置(基本上是界面的,个人感觉用途不大),适合喜欢原生Android的用户。

LineageOS的优点基本继承了,总结一下缺点吧:

  • 1)国内支付应用,基本不能使用指纹支付。历史原因,一直遗留下来的问题。跟LineageOS一样。
  • 2)关屏后不能双击屏幕打开锁屏界面
  • 3)不能双击桌面锁屏。可以设置双击任务栏或者三大金刚键进行锁屏。
  • 4)不能拍摄6400万像素的照片。ROM自带相机和Google Camera移植版,最高都只支持1610万像素。不过即使是MIUI的自带相机,拍出来的6400万像素照片基本直出(纯粹自我安慰)。
  • 5)刚开始使用,比较耗电。做了一些优化后,后面变得相对省电一点。不过,这手机本身也不怎么省电。
  • 6)长时间使用的应用,容易自动退出。比如Chrome开个视频后,很大几率会自动关掉并回到桌面。可能是电源管理自动优化吧。

总的来讲,曾经的LineageOS用户可以放心刷,也没有遇到影响日常使用的bug 。

刷机过程,跟LineageOS一样。重点还是那句:刷机前先备份好数据。Recovery备份分区(刷机失败时可以还原系统) + Ti Backup备份应用(用于迁移应用及数据)。

1. 下载相关数据

2. 刷机

  • 备份数据。最好连sdcard的数据也备份一下
  • wipe手机,即Recovery格式化data分区
  • 解锁、刷recovery
  • 刷ROM
  • Recovery里Root系统
  • 刷Opengapps
  • 清cache
  • 重启进入系统

要注意,进入系统后,需要连Google验证。

3. 耗电优化的处理。当前的节电设置如下:

  • a)屏幕刷新率设为 60Hz,在“设置”->“系统”->Device-specific settings->Minimum Refresh Rate。120Hz屏幕其实很丝滑,但60Hz确实够用。
  • b)关闭 5G 网络,只用 4G 。设置移动网络的首选网络类型为“LTE/WCDMA”。当前来说,5G除了耗电比较快,没感受到什么优势。
  • c)限制应用使用电量。在电池管理器设置受限应用。目前设了 Google Play 商店。
  • d)关闭 Google 账户的自动同步功能,只开启了 Chrome 和 Gmail 。
  • e)冻结了一些不常用但不可缺的应用。冰箱、Shelter 、island 等。其中 island 的名称太中二,个人接受不了而放弃。用 Shelter 的话,如果工作空间的应用没用到 GMS,最好冻结它,并尽量安装非无依赖 GMS 的应用。

由于贪便宜,上一部手机红米Note4x(代号:mido)买了3G内存+32G存储的版本,导致用了两年半就内部空间不足。即使插上128GB的TF卡,并转为内部存储,也要面对偶尔出现的SD卡错误,导致装在卡上的应用不能运行。抓狂……然后CFO的批准下,一起换了红米K30 5G(代号:picasso)。

这个时间点,要换手机,就肯定选5G网络的了。然后,考虑高性价比,能解BL锁,能刷第三方ROM,就剩下红米了。最后在K30i 5G与K30 5G之间,选择了拥有主摄6400万像素的K30 5G。低配版6GB + 128GB,目测用3年应该没问题。

关于此手机的一切说明,可以参考这个文章:
老妈钦点,我买了一部Redmi K30 5G版
https://pockies.github.io/2020/03/27/redmi-k30-5g/

按照文章的刷机操作,就是手机绑定小账号、解BL锁、刷recovery、刷欧版MIUI、折腾流氓应用。但是解锁后直接刷最新版Recovery,进去时会黑屏。参考了网上的经验分享,需要先刷旧版Recovery,然后刷ROM,再刷新版Recovery。由于该Recovery自带Magisk,所以不用单独下载。相关软件如下:

1)小米官方解锁工具
https://www.miui.com/unlock/index.html

2)非官方TWRP Recovery
https://mifirm.net/model/picasso.ttt#twrp
旧版:TWRP-3.4.0B-0209-REDMI_K30_5G-CN-wzsx150-fastboot
目前最新版:TWRP-3.4.2B-0623-REDMI_K30_5G-CN-wzsx150-fastboot

3)欧版MIUIv12
https://sourceforge.net/projects/xiaomi-eu-multilang-miui-roms/files/xiaomi.eu/MIUI-STABLE-RELEASES/MIUIv12/

简单的刷机流程是:刷旧版Recovery,格式化Data分区,刷入欧版MIUI,双清,在Recovery刷入最新版Recovery后,再进入Recovery进行root,重启进入系统。

刷完欧版ROM,我选择了冰箱(主要冻结无用的系统App,曾经买了付费版) + Island(主要是使用工作空间隔离流氓App,并且能进行冻结)结合使用。由于Shelter不能在MIUI上运行,只能暂时使用Island(缺点是没有自动冻结,用起来没有Shelter顺手)。

用了一段时间,一开始感觉是比较耗电,渐渐感觉跟红米Note4x差不多。已调低屏幕刷新率60mHz,没什么感觉。玩过Minecraft,手机发烫比较严重,耗电也比较快。

希望后面能刷上LineageOS。但是XDA-Developers上的网友说,小米官方一直没更新这手机的kernel源码,导致第三方系统不能完善。目前也就先这样了。


后记 2023-06-22

不经不觉三年了。这手机的缺点总结如下:

  1. 重。贴上“钢化玻璃膜”和插上两个SIM卡,K30 5G整体重量超过220g。对比红米 Note4X是170g。
  2. 玻璃后盖容易碎。
  3. 5G网络“鸡肋”。开了5G网络,除了流量飞快,还容易发热。而且室内一般没信号,自动切换到4G。直接后果是耗电。
  4. 后置主摄6400万像素,可以说是欺骗。由于传感器是SONY IMX686,采用4合1像素,一般相机应用只能识别成1600万像素。只有MIUI自带的相机应用才能拍摄出6400万像素的照片。
  5. 可刷的开源ROM较少。没办法,毕竟不是销外国际的机型。

一个只有自己在用的新闻阅读器,终于改为使用OkHttp发起请求了。原来一直用Volley,但是改用OkHttp之后,做更多的请求定制,为后面添加更多功能做准备。记录一下一些知识点:

首先是Android官方教程,关于执行网络操作的部分。有教授使用HttpsURLConnection链接网络的方法。
Android官方教程 - 连接到网络
https://developer.android.com/training/basics/network-ops/connecting

然后是主线程不能发起网络请求的问题。主要是为了提高程序的性能,把不占用CPU的IO操作(例如网络请求)都移到子线程执行。网络请求执行结束后,调用runOnUiThread方法回到主线程更新UI。
Android 子线程更新UI了解吗?
https://juejin.im/post/5da14e8ae51d45782b0c1c20

最后是OkHttp的教程。其异步请求,用起来有点像jQuery的ajax方法。
1)okhttp代码
https://github.com/square/okhttp
2)OkHttp使用完全教程
https://www.jianshu.com/p/ca8a982a116b

已经不是第一次遇到Android Studio提示“gradle sync failed”的错误了,还是记录下来,免得又得烦。

原因一般是网络错误,导致Gradle不能下载项目相关的依赖包。于是配置网络代理,Android Studio的网络代理配置界面没有支持socks5。

解决方法,参考文章:
gradle代理 - 简书 - 蒸汽飞船
https://www.jianshu.com/p/7b3bc89d26e5

简单来说,就是不要用Android Studio设置网络代理。我选择修改$HOME/.gradle/gradle.properties文件,加上以下配置:

org.gradle.jvmargs=-DsocksProxyHost\=127.0.0.1 -DsocksProxyPort\=1080

昨天发现Debian服务器上的Aria2居然不能下载https的链接,才发现编译安装时,忘了设置开启SSL的参数。还是记录一下,以免后面又犯错了。

关于编译安装的教程,可以直接查看官方说明:
https://github.com/aria2/aria2/blob/master/README.rst
https://aria2.github.io/manual/en/html/README.html#how-to-build

1. 安装相关依赖

详见官方文档。注意的是,Linux上,开启SSL,要安装openssllibssl-dev

2. 编译

官方文档已经很详细了,总结脚本如下:

$ git clone https://github.com/aria2/aria2.git
$ cd aria2
$ ./configure --without-gnutls --with-openssl
$ make
$ sudo cp ./src/aria2c /usr/local/bin/

3. 部署服务

关于Aria2配置文件的说明,参考官方文档:
https://aria2.github.io/manual/en/html/aria2c.html#aria2-conf
示例配置文件如下(参考路径:/etc/aria2/aria2.conf):

#OPTIONS
#下载路径
dir=/opt/aria2_download
#log路径
log=/var/log/aria2/aria2.log
#log-level: debug, info, notice, warn or error
log-level=warn
console-log-level=warn
#session
input-file=/var/cache/aria2/aria2.session
#最大下载数,默认5
max-concurrent-downloads=5
#校验完整性,只在bt下有效果,默认false
check-integrity=true
#断点续传
continue=true
 
#HTTP/FTP/SFTP Options
#同时连接的服务器数量,默认1
max-connection-per-server=5
#最大尝试次数,默认5
max-tries=20
#最小文件分割大小,默认20M
#min-split-size=20M
#单个文件最大线程,默认5
#split=5
#超时时间,默认60
#timeout=60
 
#BitTorrent Specific Options
#启用本地发现
bt-enable-lpd=true
#hash校验种子,默认true
bt-hash-check-seed=true
#最大打开文件数量,默认100
bt-max-open-files=200
#单个种子最大连接数
bt-max-peers=100
#在磁力下载中,保留torrent文件
bt-save-metadata=true
#监听端口,默认6881-6999
#listen-port=6881-6999
#最大上传限制,0是无限制
max-overall-upload-limit=100K
#下载完成后做种的设置
seed-ratio=1.0
seed-time=120
#bt-tracker=需要相关的服务地址
 
#RPC Options
#启用rpc
enable-rpc=true
#允许所有访问
rpc-allow-origin-all=true
#监听所有网络
rpc-listen-all=true
#监听端口
rpc-listen-port=6800
#rcp保存上传的元数据,默认false
rpc-save-upload-metadata=true
 
#Advanced Options
#下载时覆盖已经存在的文件,默认false
allow-overwrite=false
#此选项为true可能会导致下载进度丢失,默认false
allow-piece-length-change=true
#总是尝试恢复下载,默认true
always-resume=true
#指定dns服务器
#async-dns=true
#async-dns-server=8.8.4.4,208.67.222.222
#如果文件存在,自动重命名,仅适用于http,ftp
auto-file-renaming=true
#自动保存间隔,控制文件保存在.aria2中
auto-save-interval=60
#作为守护进程启用
daemon=true
#禁用ipv6
disable-ipv6=true
#磁盘缓存,默认16M
disk-cache=16M
#文件是否启用预先分配,默认prealloc
file-allocation=falloc
#最大下载结果在内存中保留数量,默认1000
max-download-result=500
#最大失败重试次数,默认0
max-resume-failure-tries=0
#下载完成时候执行的脚本
#on-bt-download-complete=/etc/aria2/on-bt-download-complete
#on-download-complete=/etc/aria2/on-download-complete
#on-download-error=/etc/aria2/on-download-error
#总体下载速度限制
max-overall-download-limit=1024K
#单个下载最大速度限制
max-download-limit=1024K
#保存下载进度,很有用的配置
save-session=/var/cache/aria2/aria2.session
#保存间隔,默认0
save-session-interval=60

# token验证
rpc-secret=123456

Systemd的服务配置文件(参考路径:/etc/systemd/system/aria2.service):

[Unit]
Description=Aria2 Service
After=network.target

[Service]
Type=forking
User=www-data
Group=www-data
WorkingDirectory=/var/cache/aria2
ExecStart=/usr/local/bin/aria2c --conf-path=/etc/aria2/aria2.conf -D
ExecReload=/usr/bin/kill -HUP $MAINPID
RestartSec=1min
Restart=on-failure

[Install]
WantedBy=multi-user.target

4. 客户端

我用yaaw,纯静态页面,服务器上部署个Nginx即可:
https://github.com/binux/yaaw

前几天打开Twitter,发现Chrome(版本80)的地址栏右边出现了十字图标,并提示“Install”。点击安装后,桌面和Chrome Apps都新增了Twitter图标。再双击该图标,就会以窗口形式打开Twitter。窗口像本地应用,但细看,只是没有了地址栏的Chrome窗口。好奇之下,发现这是PWA的最新形态!

Progressive Web Apps,简PWA,就是把网页应用化,或者是利用网页技术开发的应用。这家伙的好处是,对于绝大部分的网站来说,加几个文件就在原来网页的基础上,完成了客户端的开发。第一次看到这个技术提案时,非常激动,简直就是我们网站的救星,不用考虑如何开发iOS和Android的应用了。但其困难的地方是,统一标准和普及的问题。虽然得到越来越多浏览器的支持,但是Safari不太积极。

关于PWA相关的知识和教程,都可以在这里找到:
Progressive Web Apps
https://web.dev/progressive-web-apps/

比较齐全的PWA资源:
awesome-pwa
https://github.com/hemanth/awesome-pwa

收集了一堆PWA的网站:
https://pwa.rocks/

值得推荐的是,对PWA技术很积极的Twitter:
https://twitter.com/

注意的是,要使PWA被浏览器识别为可安装,需要一个正方形的图标。这个在做入门实例时折腾了很久才发现。

后面还想试试结合WASM,看看能否做出更好玩的东西~