tag:blogger.com,1999:blog-60675565537118581742024-03-13T18:04:42.890+08:00老灰鴨的筆記本老灰鴨http://www.blogger.com/profile/07842107154192311741noreply@blogger.comBlogger567125tag:blogger.com,1999:blog-6067556553711858174.post-56845857128168398602024-02-26T22:42:00.003+08:002024-03-01T22:14:51.709+08:00【Kotlin】disable/enable ImageButton參考資料<span style="font-family: courier;"> ----</span><div><span style="font-family: courier;"><a href="https://developer.android.com/reference/kotlin/android/widget/ImageButton" target="_blank">ImageButton</a></span></div><div><span style="font-family: courier;"><a href="https://developer.android.com/reference/android/graphics/PorterDuff.Mode">PorterDuff.Mode</a></span></div><div><br /></div><div><br /></div><div>雖然在 <span style="font-family: courier;">layout.xml</span> 中定義 <span style="font-family: courier;">enabled="false"</span>,<span style="font-family: courier;">AndroidStudio</span> 並不會報錯,但在 <span style="font-family: courier;">app</span> 執行時並沒有產生作用;官網的 <span style="font-family: courier;">ImageButton</span> 參考頁也沒有找到 <span style="font-family: courier;">enabled</span> 的屬性,似乎只能在 <span style="font-family: courier;">app</span> 執行時設定。</div>
<pre class="prettyprint lang-xml linenums">
...
...
<ImageButton
android:id="@+id/Button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/圖檔" 本次採用 向量圖檔(vector asset), 所以可在程式中改變圖檔的顏色
android:background="@android:color/transparent" 指定按鈕的背景為透明
android:enabled="true" /> 這個屬性在 app 執行時並沒有產生作用
</pre>
<div><br /></div>
<div><br /></div>
<pre class="prettyprint lang-xml linenums">
...
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var button1 = findViewById<ImageButton>(R.id.Button1)
button1.isEnabled = false
// 即使被 disabled 了, button1 所引用的圖檔顏色並沒有任何變化
// 所以也要自行改變圖檔的顏色
button1.setColorFilter(getColor(R.color.black))
}
</pre>
<div><br /></div>
<div><br /></div>老灰鴨http://www.blogger.com/profile/07842107154192311741noreply@blogger.com0tag:blogger.com,1999:blog-6067556553711858174.post-37239557955643663432024-02-13T16:31:00.005+08:002024-02-15T18:38:13.406+08:00【MSSQLserver】日期的運算(適 MSSQL2016(含)後版本)參考資料<span style="font-family: courier;"> ----</span><div><span style="font-family: courier;"><a href="https://learn.microsoft.com/zh-tw/sql/t-sql/functions/datepart-transact-sql?view=sql-server-2016" target="_blank">DATEPART (Transact-SQL)</a><br /></span><div><span style="font-family: courier;"><a href="https://learn.microsoft.com/zh-tw/sql/t-sql/functions/eomonth-transact-sql?view=sql-server-2016" target="_blank">EOMONTH (Transact-SQL)</a></span></div><div><span style="font-family: courier;"><a href="https://learn.microsoft.com/zh-tw/sql/t-sql/functions/datefromparts-transact-sql?view=sql-server-2016" target="_blank">DATEFROMPARTS (Transact-SQL)</a></span></div><div><span style="font-family: courier;"><a href="https://learn.microsoft.com/zh-tw/sql/t-sql/functions/dateadd-transact-sql?view=sql-server-2016" target="_blank">DATEADD (Transact-SQL)</a></span></div>
<div><br /></div>
<pre class="prettyprint lang-sql linenums">
-- 假設給予日期:2024-2-2(五)
-- 求得 2024-2-2 是當年的第幾週
SELECT DATEPART(week, '2024-2-2') -- 第 5 週
-- 求得 2024-2-2 是星期幾
SELECT DATEPART(weekday, '2024-2-2') -- 星期五
-- 當月首日:2024-2-1
-- 方法一
-- EOMONTH 求得上個月的最末日
-- DATEADD 再運算加 1 日
SELECT DATEADD(day, 1, EOMONTH('2024-2-2',-1))
-- 方法二
SELECT DATEFROMPARTS(YEAR('2024-2-2'),MONTH('2024-2-2'),1)
-- 當月最末日:2024-2-29
SELECT EOMONTH('2024-2-2')
-- 當年首日:2024-1-1
SELECT DATEFROMPARTS(YEAR('2024-2-2'),1,1)
-- 當年最末日:2024-12-31
SELECT DATEFROMPARTS(YEAR('2024-2-2'), 12, 31)
</pre>
<div><br /></div>
<div><br /></div></div>老灰鴨http://www.blogger.com/profile/07842107154192311741noreply@blogger.com0tag:blogger.com,1999:blog-6067556553711858174.post-55123168534929785932024-02-07T13:46:00.017+08:002024-03-10T09:55:31.408+08:00【Kotlin】設為 深色/夜晚 模式<div>參考資料<span style="font-family: courier;"> ----</span></div><div><span style="font-family: courier;"><a href="https://developer.android.com/reference/androidx/appcompat/app/AppCompatDelegate#setDefaultNightMode(int)" target="_blank">AppCompatDelegate.setDefaultNightMode</a></span></div><div><span style="font-family: courier;"><a href="https://www.jianshu.com/p/1aaf0cee7a2f" target="_blank">Android深色模式适配原理分析</a></span>(這篇解說超清楚的)</div><div><br /></div><div><br /></div><div><span style="background-color: red; color: white;">踩到雷!</span></div><div>下面程式碼在陽春的 <span style="font-family: courier;">app</span> 可行,但 <span style="background-color: red;"><span style="color: white; font-family: courier;">強制深色模式會令 Activity 重建一個 instance(實例)</span></span>,以 <span style="background-color: black; color: #fcff01; font-family: courier;">onCreate()</span> 為例,會同時間執行 2 次;這次的雷就是 <span style="font-family: courier;">SQLite</span> 同時間寫入 2 筆相同的記錄,造成 <span style="background-color: red; color: white; font-family: courier;">primary key 衝突</span>才發現。</div><div><br /></div>以 <span style="font-family: courier;">Android Studio</span> 精靈建立新專案,若看到有 <span style="font-family: courier;"><span style="background-color: black;"><span style="color: #fcff01;">/res/values-night/</span></span> </span>目錄,且目錄下有 <span style="background-color: black; color: #fcff01; font-family: courier;">themes.xml</span>,就表示精靈有幫您建立 深色/夜晚 模式的主題配置。<br />
<pre class="prettyprint lang-java linenums">
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
...
...
// 設為 深色/夜晚 模式
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
...
...
}
</pre>
<div>這指令到底該在什麼場合使用...再說吧... 😝</div>
<div><br /></div>
<div><br /></div>
<div>目前找到的替代做法,是修改預設(白天) 的 <span style="font-family: courier;">themes.xml</span> ↓</div>
<pre class="prettyprint lang-xml linenums">
<style name="Theme.blablabla" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
...
...
<!-- Customize your theme here. -->
<item name="android:windowBackground">@color/black</item>
</pre>老灰鴨http://www.blogger.com/profile/07842107154192311741noreply@blogger.com0tag:blogger.com,1999:blog-6067556553711858174.post-6807049894097167712024-02-01T17:46:00.019+08:002024-03-10T10:00:19.651+08:00【LinuxMint20.3】以 docker 安裝 Apache Superset -- 免費,開源的 BI參考資料<span style="font-family: courier;"> ----</span>
<div><a href="https://superset.apache.org/" style="font-family: courier;" target="_blank">Apache Supersert</a></div>
<div><span style="font-family: courier;"><a href="https://docs.docker.com/engine/install/" target="_blank">Install Docker Engine</a></span></div>
<div><span style="font-family: courier;"><a href="https://superset.apache.org/docs/databases/docker-add-drivers/" target="_blank">Adding New Database Drivers in Docker</a></span></div>
<div><span style="font-family: courier;"><a href="https://superset.apache.org/docs/databases/sql-server" target="_blank">SQL Server connector library</a><br /></span></div>
<div><span style="font-family: courier;"><a href="https://superset.apache.org/docs/installation/installing-superset-using-docker-compose/" target="_blank">Installing Superset Locally Using Docker Compose</a></span></div><div><span style="font-family: courier;"><a href="https://www.restack.io/docs/superset-knowledge-apache-superset-0-0-0dev-info" target="_blank">Apache Superset 0.0.0dev Overview</a></span></div> <div><br /></div>
<div><br /></div>
<div><span style="font-family: courier;">Superset</span> 的安裝方式有:</div>
<div>
<ul>
<li><span style="font-family: courier;">Linux</span> 套件安裝</li>
<li>以 <span style="font-family: courier;">Docker Compose</span> 佈署</li>
<li>以 <span style="font-family: courier;">Kubernetes(K8S)</span> 佈署</li>
<li>在 <span style="font-family: courier;">Dockerhub</span> 執行</li>
<li>以 <span style="font-family: courier;">Pypi</span> 下載安裝</li>
<li>自 <span style="font-family: courier;">GitHub</span> 下載安裝</li>
<li>自 <span style="font-family: courier;">Apache</span> 基金會官網下載安裝</li>
</ul>
</div>
<div><br /></div>
<div><br /></div>
<div>本筆記採用 <span style="background-color: black;"><span style="color: #fcff01; font-family: courier;"><span>Docker Compose</span> 佈署</span></span></div>
<div><br /></div>
<div>安裝必要的相依套件</div>
<pre class="prettyprint lang-sh linenums">
[user]$ sudo apt install git docker docker-compose
</pre>
<div><br /></div>
<div><br /></div>
<div>複製資源庫</div>
<pre class="prettyprint lang-sh linenums">
# 預定裝在 /usr/share
[user]$ cd /usr/share
[user]$ sudo git clone https://github.com/apache/superset.git
# 會看到生成 /usr/share/superset/ 目錄
</pre>
<div><br /></div>
<div><br /></div>
<div>執行 <span style="background-color: black; color: #fcff01; font-family: courier;">docker daemon</span></div>
<pre class="prettyprint lang-sh linenums">
[user]$ cd superset
# 執行 docker daemon
[user]$ sudo systemctl start docker
# 檢查確認 docker daemon 執行狀態
[user]$ sudo systemctl status docker
</pre>
<div><br /></div>
<div><br /></div>
<div>啟動 <span style="font-family: courier;">superset</span></div>
<pre class="prettyprint lang-sh linenums">
# 第一次執行, 下傳 container, 一次性指令
[user]$ sudo docker-compose -f docker-compose-non-dev.yml pull
# 之後只要執行這指令
[user]$ sudo docker-compose -f docker-compose-non-dev.yml up
</pre>
<div><br /></div>
<div><br /></div>
<div><span style="font-family: courier;"><div><span style="font-family: "Times New Roman";">若要停止執行 </span>superset<span style="font-family: Times New Roman;">,則 </span><span style="background-color: black; color: #fcff01; font-family: courier;">CTRL + C</span></div><div style="font-family: "Times New Roman";">這時會看到螢幕顯示</div><div style="font-family: "Times New Roman";"><span style="background-color: black; color: #01ffff; font-family: courier;">Stopping superset_app...</span></div><div style="font-family: "Times New Roman";"><span style="background-color: black; font-family: courier;"><span style="color: #01ffff;">Stopping superset_worker_beat...</span></span></div><div style="font-family: "Times New Roman";"><span style="background-color: black; font-family: courier;"><span style="color: #01ffff;">...</span></span></div><div style="font-family: "Times New Roman";">等正在停止程序的訊息</div><div style="font-family: "Times New Roman";"><br /></div></span></div><div><span style="font-family: courier;">superset</span> 內建並不支援 <span style="font-family: courier;">Microsoft SQL Server</span>,若要連接 <span style="font-family: courier;">mssql</span>,則要安裝驅動程式,官方建議的驅動為 <span style="background-color: black; color: #fcff01; font-family: courier;">pymssql</span></div>
<pre class="prettyprint lang-sh linenums">
[user]$ sudo vim /usr/share/superset/docker/requirements-local.txt
# 輸入
pymssql
:x 存檔離開
# 再執行
[user]$ sudo docker-compose -f docker-compose-non-dev.yml up
# 就會自動安裝 pymssql 了
</pre>
<div><br /></div>
<div><br /></div>
<div>開啟瀏覽器,輸入網址</div>
<div><span style="background-color: black; color: #fcff01; font-family: courier;">http://localhost:8088</span></div>
<div>登入帳/密:<span style="font-family: courier;">admin / admin</span><br /></div>
<div><br /></div>
<h4 style="text-align: left;"><span style="color: #e69138;">建立資料庫連線</span></h4><div>點擊右上角的 "<span style="background-color: black; color: #fcff01; font-family: courier;">Settings</span>"</div><div>→ <span style="font-family: courier;">Database Connections</span></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhEwHBguXQWgwQDgNnbM_tbI27ldeEgzD1q0wvRIZT-8mKDkv0HQffopxs5XxXaG8x1tzXVNjuRXruh5LT_fZ0xfRtnk9rPMzHVG6-Ir2dcYbUl6jHA_NGVRGKBV8yihxvoHQV1hwSEAQISsC42Go68-VNAtzynOZbnLm7L5bH63xJEIrhNcQImPDFPGB0/s1920/01_superset_%E6%96%B0%E5%A2%9E%E8%B3%87%E6%96%99%E5%BA%AB.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="685" data-original-width="1920" height="228" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhEwHBguXQWgwQDgNnbM_tbI27ldeEgzD1q0wvRIZT-8mKDkv0HQffopxs5XxXaG8x1tzXVNjuRXruh5LT_fZ0xfRtnk9rPMzHVG6-Ir2dcYbUl6jHA_NGVRGKBV8yihxvoHQV1hwSEAQISsC42Go68-VNAtzynOZbnLm7L5bH63xJEIrhNcQImPDFPGB0/w640-h228/01_superset_%E6%96%B0%E5%A2%9E%E8%B3%87%E6%96%99%E5%BA%AB.png" width="640" /></a></div><br /><span style="font-family: courier;"><br /></span></div><div><span style="font-family: courier;"><br /></span></div><div>→ <span style="background-color: #6fa8dc; color: white; font-family: courier;">+DATABASE</span></div><div>→ 可以看到列出的預設支援資料庫沒有 <span style="font-family: courier;">MS SQL Server</span></div><div>→ 下拉下方的 <span style="font-family: courier;">Supported databases</span>,選擇最末的 <span style="background-color: black; color: #fcff01; font-family: courier;">Other</span></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIL22noVlnfyvzfxOa8EuPSR_ELE8KEjGs2DD9fDti0w7NogLnKqeSq8DMCcCOU_yVK4ZFGR5I4Ofmy6XhWfvJx7x1ogYUbnxeYdcVhb04KgvcTRKGbYbbNuZOGGKMWcOkPcQQArF0uJVN4Xk6YSEtyfAQ0Ms1rfdd7be1a8MTHjK7YLZAmLJ33oF5ziY/s1920/02_superset_%E6%96%B0%E5%A2%9E%E8%B3%87%E6%96%99%E5%BA%AB.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="810" data-original-width="1920" height="270" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIL22noVlnfyvzfxOa8EuPSR_ELE8KEjGs2DD9fDti0w7NogLnKqeSq8DMCcCOU_yVK4ZFGR5I4Ofmy6XhWfvJx7x1ogYUbnxeYdcVhb04KgvcTRKGbYbbNuZOGGKMWcOkPcQQArF0uJVN4Xk6YSEtyfAQ0Ms1rfdd7be1a8MTHjK7YLZAmLJ33oF5ziY/w640-h270/02_superset_%E6%96%B0%E5%A2%9E%E8%B3%87%E6%96%99%E5%BA%AB.png" width="640" /></a></div><br /><div><br /></div><div><br /></div><div>→ <span style="font-family: courier;">DISPLAY NAME</span> (<span style="background-color: red;"><span style="color: white;">必填</span></span>) 命名您的資料庫連線<span class="required"></span></div><div>→ <span style="font-family: courier;">SQLALCHEMY URI</span>(<span style="background-color: red; color: white;">必填</span>) 輸入連線字串,格式為</div><div><span class="required"></span></div>
<pre class="prettyprint lang-sh linenums">
mssql+pymssql://username:password@hostname:port/database_name
* 若 password 內含有 @ 字元,要改為 URL編碼(percent-encoding),改成 %40
* port 的部份,若您的 mssql 採用預設的 port,則可不填
</pre>
→ 點擊 <span style="font-family: courier;">TEST CONNECTION</span> 鈕確認連線正常<div>→ <span style="background-color: #6fa8dc; color: white; font-family: courier;">CONNECT</span> 建立連線</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgRVjCv2XNRf5MNXqaCaIshpVP9j4JCLnZ-sSVICHKCbcHBoXplQCRKZ7ItMStL993Jj8iLT67Df-CXxwli920cwt3r3bUUMK7t-OPiRkPeJEAZ-Q7AmkQbdIoHXW2w3u3JfsSLq3MtB9EvmCKXXimVGwYlkl0zqwBd65u0DuWi9k0OzKOK-yqliQQyKzE/s765/03_superset_%E6%96%B0%E5%A2%9E%E8%B3%87%E6%96%99%E5%BA%AB.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="765" data-original-width="502" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgRVjCv2XNRf5MNXqaCaIshpVP9j4JCLnZ-sSVICHKCbcHBoXplQCRKZ7ItMStL993Jj8iLT67Df-CXxwli920cwt3r3bUUMK7t-OPiRkPeJEAZ-Q7AmkQbdIoHXW2w3u3JfsSLq3MtB9EvmCKXXimVGwYlkl0zqwBd65u0DuWi9k0OzKOK-yqliQQyKzE/w263-h400/03_superset_%E6%96%B0%E5%A2%9E%E8%B3%87%E6%96%99%E5%BA%AB.png" width="263" /></a></div><br /><div><br /></div><div><br /></div><div><br /></div><div><br /></div><h4 style="text-align: left;"><span style="color: #e69138;">設定為開機自動執行 <span style="font-family: courier;">superset</span></span></h4>
<pre class="prettyprint lang-sh linenums">
[user]$ sudo vim /etc/systemd/system/superset.service
# 輸入下述指令
[Unit]
Description=Apache Superset
After=docker.service
[Service]
Type=simple
ExecStart=/usr/bin/docker-compose -f /usr/share/superset/docker-compose-non-dev.yml up
ExecStop=/usr/bin/docker-compose -f /usr/share/superset/docker-compose-non-dev.yml down
[Install]
WantedBy=multi-user.target
:x 存檔離開
# 啟用 docker.service 開機自動執行
[user]$ sudo systemctl enable docker.service
# 啟用 superset.service 開機自動執行
[user]$ sudo systemctl enable superset.service
</pre>
<div>重新開機,驗證 <span style="font-family: courier;">superset</span> 確實開機自動執行<br /></div><div><br /></div><div>版本號碼為 <span style="background-color: black; color: #fcff01; font-family: courier;">0.0.0-dev</span>,在 <span style="font-family: courier;"><a href="https://www.restack.io/docs/superset-knowledge-apache-superset-0-0-0dev-info" target="_blank">Apache Superset 0.0.0dev Overview</a></span> 有解釋。</div>
<div><br /></div>
<div><br /></div>
老灰鴨http://www.blogger.com/profile/07842107154192311741noreply@blogger.com0tag:blogger.com,1999:blog-6067556553711858174.post-29752982501696097322024-01-31T11:43:00.003+08:002024-01-31T11:43:39.655+08:00【Metabase】設定 Metabase 為開機即啟動服務 參考資料 <span style="font-family: courier;">----</span><div><a href="https://www.metabase.com/docs/latest/installation-and-operation/running-metabase-on-debian" target="_blank"><span style="font-family: courier;">Running Metabase on Debian as a service with nginx</span></a></div><div><span style="font-family: courier;"><a href="https://linux.vbird.org/linux_basic/centos7/0210filepermission.php" target="_blank">Linux 的檔案權限與目錄配置</a></span></div><div><br /></div><div>將 <span style="font-family: courier;">metabase.jar</span> 移至 <span style="background-color: black; color: #fcff01; font-family: courier;">/usr/share/metabase/</span> 目錄</div><div><br /></div><div><br /></div>
<pre class="prettyprint lang-sh linenums">
[user]$ sudo su
[root]# groupadd -r metabase
[root]# useradd -r -s /bin/false -g metabase metabase
[root]# chown -R metabase:metabase /usr/share/metabase
[root]# touch /var/log/metabase.log
[root]# chown syslog:adm /var/log/metabase.log
[root]# touch /etc/default/metabase
[root]# chmod 640 /etc/default/metabase
</pre>
<div><br /></div>
<div><br /></div>
<div>建立 <span style="font-family: courier;">Metabase Service</span> 服務檔</div>
<pre class="prettyprint lang-sh linenums">
[root]# cd /etc/systemd/system/
[root]# vim metabase.service
# 輸入下述內容
[Unit]
Description=Metabase server
After=syslog.target
After=network.target
[Service]
WorkingDirectory=/usr/share/metabase/
ExecStart=/usr/bin/java -jar /usr/share/metabase/metabase.jar
EnvironmentFile=/etc/default/metabase
User=metabase
Type=simple
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=metabase
SuccessExitStatus=143
TimeoutStopSec=120
Restart=always
[Install]
WantedBy=multi-user.target
:x 存檔離開
</pre>
<div><br /></div>
<div><br /></div>
<div>建立 syslog conf</div>
<pre class="prettyprint lang-sh linenums">
[root]# vim /etc/rsyslog.d/metabase.conf
# 輸入下述內容
if $programname == 'metabase' then /var/log/metabase.log
& stop
:x 存檔離開
# 重啟 rsyslog.service 以使 metabase.conf 生效
[root]# systemctl restart rsyslog.service
</pre>
<div><br /></div>
<div><br /></div>
<div>編輯 <span style="font-family: courier;">Metabase</span> 的環境變數</div>
<pre class="prettyprint lang-sh linenums">
[root]# vim /etc/default/metabase
MB_PASSWORD_COMPLEXITY=strong
MB_PASSWORD_LENGTH=密碼字串長度
MB_JETTY_HOST=0.0.0.0
MB_JETTY_PORT=3000
MB_DB_TYPE=mysql
MB_DB_DBNAME=metabase
MB_DB_PORT=3306
MB_DB_USER=metabase
MB_DB_PASS=MySQL的metabase帳戶密碼
MB_DB_HOST=localhost
MB_EMOJI_IN_LOGS=true # log 要不要有顏文字...這還蠻有趣的...若不要就填 false
# any other env vars you want available to Metabase
</pre>
<div><br /></div>
<div><br /></div>
<div>設定 metabase 為開機啟動</div>
<pre class="prettyprint lang-sh linenums">
# 先手動啟動 metabase.service
[root]# systemctl daemon-reload
[root]# systemctl start metabase.service
[root]# systemctl status metabase.service
# 確定執行沒問題了, 就改為開機自動啟動
[root]# systemctl enable metabase.service
</pre>
<div><br /></div>
<div><br /></div>
<hr />
相關筆記 ----<div><a href="https://oldgrayduck.blogspot.com/2024/01/metabase.html" target="_blank"><span style="font-family: courier;">社群版(Community) 將系統資料移至大型資料庫</span></a><br />
<div><br /></div>
<div><br /></div></div>老灰鴨http://www.blogger.com/profile/07842107154192311741noreply@blogger.com0tag:blogger.com,1999:blog-6067556553711858174.post-62951153229451241612024-01-29T14:11:00.014+08:002024-01-31T14:26:59.293+08:00【Metabase】社群版(Community) 將系統資料移至大型資料庫參考資料<span style="font-family: courier;"> ----</span><div><a href="https://www.metabase.com/docs/latest/installation-and-operation/migrating-from-h2" target="_blank"><span style="font-family: courier;">Migrating to a production application database</span></a></div><div><span style="font-family: courier;"><a href="https://www.metabase.com/docs/latest/installation-and-operation/configuring-application-database" target="_blank">Configuring the Metabase application database</a></span></div><div><br /></div><div><br /></div><div><span style="font-family: courier;">Metabase</span> 自帶的系統資料庫為 <span style="font-family: courier;">H2</span>,僅用於測試試用期間,若要轉為正式應用(<span style="font-family: courier;">production</span>),官方強烈將系統資料移至大型資料庫。</div><div><br /></div><div>建議版本為 <span style="background-color: black; color: #fcff01; font-family: courier;">MySQL 5.7.7(含)</span> 或 <span style="background-color: black; color: #fcff01; font-family: courier;">MariaDB 10.2.2(含)</span> 或 <span style="background-color: black; color: #fcff01; font-family: courier;">PostgreSQL 9.4(含)</span> 以上;本筆記為 <span style="font-family: courier;">MySQL 8.0</span>。</div><div><br /></div><div><span style="background-color: red; color: white;">注意:</span>避免同時做 <span style="font-family: courier;">Metabase </span>更新升級 及 資料庫移植</div><div><br /></div><div><br /></div><div>關閉 <span style="font-family: courier;">Metabase,目前我的練功主機是開終端機視窗,在家目錄以</span></div>
<pre class="prettyprint lang-sh linenums">
[user]~$ java -jar metabase.jar
</pre>
<div><span style="font-family: courier;">的指令啟動 </span><span style="font-family: courier;">Metabase</span></div><div><span style="font-family: courier;">所以只要在瀏覽器的 </span><span style="font-family: courier;">Metabase</span><span style="font-family: courier;"> 頁面登出,然後在</span><span style="font-family: courier;">終端機視窗 </span><span style="font-family: courier;">Ctrl+C</span><span style="font-family: courier;"> 中斷 </span><span style="font-family: courier;">Metabase</span><span style="font-family: courier;"> 即可。</span></div><div><br /></div><div><br /></div><div>備份 <span style="font-family: courier;">H2</span>︰複製<span style="font-family: courier;"> metabase.db.mv.db </span>到另一個安全的目錄存放。</div><div><br /></div><div><br /></div><div>操作 <span style="font-family: courier;">phpMyAdmin</span></div><div>在 <span style="font-family: courier;">MySQL </span>新增 <span style="font-family: courier;">metabase</span> 帳戶</div><div><br /></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8bGbdddL9CMQW9z-vF0Famz3C86VLO8sv4hnm4di_bZP84p7X_3RPlDdNgI1ULMS8xKLA8UHUE-nuYIasTZ_baYq9SB738eBbNHzDAfZAufv6kbEnGDH9RGCnrhCOYhhKzvU89FOyooBBjJB-UJrPbD_eim7oXxYZwtE_tnewlViTroSNpNksmcGoJJc/s669/%E6%96%B0%E5%A2%9E%E8%B3%87%E6%96%99%E5%BA%AB01.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="465" data-original-width="669" height="444" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8bGbdddL9CMQW9z-vF0Famz3C86VLO8sv4hnm4di_bZP84p7X_3RPlDdNgI1ULMS8xKLA8UHUE-nuYIasTZ_baYq9SB738eBbNHzDAfZAufv6kbEnGDH9RGCnrhCOYhhKzvU89FOyooBBjJB-UJrPbD_eim7oXxYZwtE_tnewlViTroSNpNksmcGoJJc/w640-h444/%E6%96%B0%E5%A2%9E%E8%B3%87%E6%96%99%E5%BA%AB01.png" width="640" /></a></div><div><br /></div><div>建立 <span style="font-family: courier;">metabase</span> 空白資料庫...我其實對資料庫的字集沒有概念,所以隨便選。</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiftjuFkjOvcIQ35mTHmNWoZ68waKpdDV9h1U_lLFNsVaGnK0DPEnKHH9jrV1SlcIYbTXZUui_StjJZ7xo6NvVGf54cHxkF3f4y33n8DhiofL2PCOxGyxhPcAdh5W2XvrbXhYvPbcIInpX04YGONwD8c3meUGlBshduGrqRyUEpEESpi9WyhpOgFVzZWTw/s788/%E6%96%B0%E5%A2%9E%E8%B3%87%E6%96%99%E5%BA%AB02.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="511" data-original-width="788" height="416" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiftjuFkjOvcIQ35mTHmNWoZ68waKpdDV9h1U_lLFNsVaGnK0DPEnKHH9jrV1SlcIYbTXZUui_StjJZ7xo6NvVGf54cHxkF3f4y33n8DhiofL2PCOxGyxhPcAdh5W2XvrbXhYvPbcIInpX04YGONwD8c3meUGlBshduGrqRyUEpEESpi9WyhpOgFVzZWTw/w640-h416/%E6%96%B0%E5%A2%9E%E8%B3%87%E6%96%99%E5%BA%AB02.png" width="640" /></a></div><div><br /></div><div><br /></div>授予 <span style="font-family: courier;">metabase 帳戶對 </span><span style="font-family: courier;">metabase 資料庫具有全部的權限</span></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-4R3IGEQHGdqE16VvZlg8ix0nb6SXVWGOk_Qn8Xi0pafMjEHIAbxLeqTNkeDjOUExCXmFHBVO4zPh0b9X5EgX02S4uioS9MSLikBp1ZSOQkSzm4M22NAKyTP1S9v5tJ_I1b0u1ZYX8DLb6SduanCLTJBRXhvALtZa50KCXnt31HHD6w-I4Jpizp8HSwY/s959/%E6%96%B0%E5%A2%9E%E8%B3%87%E6%96%99%E5%BA%AB03.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="450" data-original-width="959" height="300" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-4R3IGEQHGdqE16VvZlg8ix0nb6SXVWGOk_Qn8Xi0pafMjEHIAbxLeqTNkeDjOUExCXmFHBVO4zPh0b9X5EgX02S4uioS9MSLikBp1ZSOQkSzm4M22NAKyTP1S9v5tJ_I1b0u1ZYX8DLb6SduanCLTJBRXhvALtZa50KCXnt31HHD6w-I4Jpizp8HSwY/w640-h300/%E6%96%B0%E5%A2%9E%E8%B3%87%E6%96%99%E5%BA%AB03.png" width="640" /></a></div><div><br /></div><br /><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwGIzisQK3BO2MuejXKWWAijZS8GesRh1YIM_18kOEkYd8kVT3RbFA0exaYkjSRcKf78tJO3cjI-CQaIJqMrDfDSppttQubBbVlIHrYU2_Z-bi8hdy4N75AVDL9NJnO_ldUBZFD-8zrdx3yVhFzMWjN7l5i3318iBGWFUGxlygsoRgdy6trDpgM8y0vO0/s842/%E6%96%B0%E5%A2%9E%E8%B3%87%E6%96%99%E5%BA%AB04.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="409" data-original-width="842" height="310" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwGIzisQK3BO2MuejXKWWAijZS8GesRh1YIM_18kOEkYd8kVT3RbFA0exaYkjSRcKf78tJO3cjI-CQaIJqMrDfDSppttQubBbVlIHrYU2_Z-bi8hdy4N75AVDL9NJnO_ldUBZFD-8zrdx3yVhFzMWjN7l5i3318iBGWFUGxlygsoRgdy6trDpgM8y0vO0/w640-h310/%E6%96%B0%E5%A2%9E%E8%B3%87%E6%96%99%E5%BA%AB04.png" width="640" /></a></div></div><div><br /></div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYF78mrDPaZ046sPXX_DAgLAejPYkdS3t6CVLw4OnUPhFsxCIGxblC3oNq2opoa0XH-48N_zHrT-8l9Z3HjotU3mmYFfRLA2I4QOxIfUGGS8IaTdkJtPuf49l3g8t1I326lSdTE5TNfQcHk76gL_YNRf1rajlPZYZJh-NhhMXyNFHWLM3EcBZU2yUQay0/s1076/%E6%96%B0%E5%A2%9E%E8%B3%87%E6%96%99%E5%BA%AB05.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="679" data-original-width="1076" height="404" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYF78mrDPaZ046sPXX_DAgLAejPYkdS3t6CVLw4OnUPhFsxCIGxblC3oNq2opoa0XH-48N_zHrT-8l9Z3HjotU3mmYFfRLA2I4QOxIfUGGS8IaTdkJtPuf49l3g8t1I326lSdTE5TNfQcHk76gL_YNRf1rajlPZYZJh-NhhMXyNFHWLM3EcBZU2yUQay0/w640-h404/%E6%96%B0%E5%A2%9E%E8%B3%87%E6%96%99%E5%BA%AB05.png" width="640" /></a></div><br /><div><br /></div><div><span style="font-family: courier;"><br /></span></div><div><span style="font-family: courier;"><br /></span></div>
<div><br /></div>
<div><br /></div>
<div>執行移植指令</div>
<pre class="prettyprint lang-sh linenums">
# 切換到 metabase.jar 所在目錄, 在本例為 [user]~/metabase/
[user]~/metabase$ export MB_DB_TYPE=mysql
[user]~/metabase$ export MB_DB_CONNECTION_URI="jdbc:mysql://主機IP:3306/metabase?user=metabase帳戶&password=密碼"
[user]~/metabase$ java -DMB_DB_TYPE=mysql -DMB_DB_CONNECTION_URI="jdbc:mysql://主機IP:3306/metabase?user=帳號&password=密碼;" -jar metabase.jar load-from-h2 metabase.db # 注意檔名
</pre>
<div><br /></div>
<div><br /></div>
<div>因為有備份 <span style="font-family: courier;">H2</span>,所以可以放心刪除 <span style="font-family: courier;">metabase 所在目錄下的 H2 檔案,以確認 metabase 存取的是 MySQL。</span></div><div><span style="font-family: courier;"><br /></span></div><div>重新啟動 <span style="font-family: courier;">metabase</span></div>
<pre class="prettyprint lang-sh linenums">
[user]~/metabase$ export MB_DB_TYPE=mysql
[user]~/metabase$ export MB_DB_CONNECTION_URI="jdbc:mysql://主機IP:3306/metabase?user=metabase帳戶&password=密碼"
[user]~/metabase$ java -DMB_DB_TYPE=mysql -DMB_DB_CONNECTION_URI="jdbc:mysql://主機IP:3306/metabase?user=metabase帳號&password=密碼" -jar metabase.jar
</pre>
重登入 <span style="font-family: courier;">metabase</span>,可以看到之前製作的儀表板都在。<div><br /></div><div><br /></div><div>相關筆記<span style="font-family: courier;"> ----</span></div><div><a href="https://oldgrayduck.blogspot.com/2024/01/metabase-metabase.html" style="font-family: courier;" target="_blank">設定 Metabase 為開機即啟動服務</a></div><div><span style="font-family: courier;"><a href="https://oldgrayduck.blogspot.com/2022/02/databasemint-linux-203-mysql-80.html" target="_blank">Mint Linux 20.3 安裝設定 MySQL 8.0 + phpMyadmin</a></span></div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><br /></div>老灰鴨http://www.blogger.com/profile/07842107154192311741noreply@blogger.com0tag:blogger.com,1999:blog-6067556553711858174.post-22012287289797318652024-01-20T12:31:00.019+08:002024-01-29T16:41:13.066+08:00【CentOS 7.x】安裝 Filebeat 集中收集 防火牆 Fortigate 的 log參考資料 ----
<div><a href="https://www.cnblogs.com/scajy/p/15726298.html" target="_blank"><span style="font-family: courier;">安装 Beats 的 Filebeat 收集日志插件</span></a></div><div><br /></div><pre class="prettyprint lang-sh linenums">
[root]# rpm --import https://packages.elastic.co/GPG-KEY-elasticsearch
</pre>
<div><br /></div>
<div><span style="font-family: courier;">打開 CentOS 防火牆 <span style="background-color: black;"><span style="color: #fcff01;">9004 port</span></span></span></div><div><br /></div><div><br /></div>
編寫 <span style="background-color: black; color: #fcff01; font-family: courier;">filebeat repo</span>
<pre class="prettyprint lang-sh linenums">
[root]# vi /etc/yum.repos.d/filebeat.repo
# 寫入下列內容
[elastic-8.x]
name=Elastic repository for 8.x packages
baseurl=https://artifacts.elastic.co/packages/8.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md
# 存檔離開
</pre>
<div><br /></div>
<div><br /></div>
<div>安裝 <span style="font-family: courier;">filebeat</span></div>
<pre class="prettyprint lang-sh linenums">
[root]# yum install -y filebeat
# 安裝後, 建議關閉 filebeat.repo 自動更新功能
# enabled=0
</pre>
<div><br /></div>
<div><br /></div>
<div>設定為開機啟動</div>
<pre class="prettyprint lang-sh linenums">
[root]# systemctl enable filebeat
</pre>
<div><br /></div>
<div><br /></div>
<div>啟用收集 <span style="font-family: courier;">Fortinet</span> 防火牆 <span style="font-family: courier;">Fortigate</span> 的 <span style="font-family: courier;">log</span> 功能</div>
<pre class="prettyprint lang-sh linenums">
[root]# filebeat modules enable fortinet
</pre>
<div>切換到 <span style="background-color: black; color: #fcff01; font-family: courier;">/etc/filebeat/modules.d/</span> 下,會看到 <span style="background-color: black; color: #fcff01; font-family: courier;">fortinet.yml</span>,其他模組因為未啟用,所以會看到其他的檔名皆為 《<span style="font-family: courier;">模組名.yml.disabled》</span></div>
<div><br /></div>
<div><br /></div>
<div>修改設定檔 <span style="background-color: black; color: #fcff01; font-family: courier;">/etc/filebeat/modules.d/fortinet.yml</span></div>
<pre class="prettyprint lang-sh linenums">
- module: fortinet
firewall:
enabled: true 改成 true
var.input: udp 解除註解
var.syslog_host: 0.0.0.0 或 localhost
var.syslog_port: 9004 解除註解
var.internal_interfaces: [ "LAN" ] 解除註解
# 存檔離開
</pre>
<div><br /></div>
<div><br /></div>
<div>由於 <span style="font-family: courier;">Filebeat</span> 屬於 <span style="background-color: black; color: #fcff01; font-family: courier;">ELK</span> 系統的其中一個套件,預設是將收集到的 <span style="font-family: courier;">log</span> 再往 <span style="font-family: courier;">Elasticsearch</span> 或 <span style="font-family: courier;">Logstash</span> 送,所以要修改 <span style="background-color: black; color: #fcff01; font-family: courier;">/etc/filebeat/filebeat.yml</span>, 將 <span style="font-family: courier;">log</span> 存成檔案</div>
<pre class="prettyprint lang-sh linenums">
[root]# vi /etc/filebeat/filebeat.yml
# 必須將其他 output 的設定註解
# 亦即同一時間只能有一個 output 設定
output.file:
path: "/var/log/filebeat"
filename: filebeat # 檔名規則, 在本例:filebeat-西元年月日.ndjson, 內容為 json 格式
rotate_every_kb: 10240 # 每個 log 檔的 size, 若達設定值,
# 則換存新的 log 檔, 在本例, 產生的檔名依序為 filebeat-西元年月日.ndjson, filebeat-西元年月日-1.ndjson, filebeat-西元年月日-2.ndjson, ...
# 預設為 10240 KB
number_of_files: 7 # 保留的檔案數, 若達設定值, 則刪除最舊的檔案, 預設值為 7, 設定值範圍為 2~1024
permissions: 0600 # 檔案權限, 設定值 0600
rotate_on_startup: true # 是否啟動時就存新的 log 檔, 預設值 true
# 存檔離開
</pre>
要注意檔名雖然為 <span style="font-family: courier;">filebeat-西元年月日.ndjson</span>,但換檔的規則是當檔案達設定值才換,並不會每天生出一個新的檔,<div>所以我想到變通的做法<span style="font-family: courier;"> -- </span><span style="background-color: black; color: #fcff01; font-family: courier;">在 crontab 指定每天重啟 filebeat</span></div><div>只是若同一天重啟 <span style="font-family: courier;">filebeat</span> 一次以上,仍會產生 <span style="font-family: courier;">filebeat-西元年月日-1.ndjson, ...</span><br /><div><br /></div>
<div><br /></div>
<div>立即啟動</div>
<pre class="prettyprint lang-sh linenums">
[root]# systemctl start filebeat
</pre>
<div><br /></div>
<div><br /></div>
<div>確認 <span style="font-family: courier;">filebeat</span> 在運行中</div>
<pre class="prettyprint lang-sh linenums">
[root]# systemctl status filebeat
</pre>
<div>切換到 <span style="background-color: black; color: #fcff01; font-family: courier;">/var/log/filebeat/</span> 目錄下, 發現已生出檔案 <span style="background-color: black; color: #fcff01; font-family: courier;">filebeat-西元年月日.ndjson</span>,打開檔案觀察,格式 <span style="background-color: black;"><span style="color: #fcff01; font-family: courier;">json</span></span></div><div><br /></div><div><br /></div><div>到 <span style="font-family: courier;">Fortigate</span> 防火牆,設定將 <span style="font-family: courier;">log</span> 往 <span style="font-family: courier;">CentOS</span> 送,由於防火牆不歸老人家管,只聽說此功能為進階設定,在 <span style="font-family: courier;">Fortigate</span> 的圖形界面達不到此目的,必須下指令,陽春的指令如下:</div>
<pre class="prettyprint lang-sh linenums">
config log syslogd setting
set status enable
set format default
set server <安裝 Filebeat 的 CentOS 主機的 IP>
set port 9004
</pre>
<div>防火牆的 <span style="font-family: courier;">log</span> 數量非常驚人,不到 <span style="font-family: courier;">1 小時</span>就超過 <span style="background-color: black; color: #fcff01; font-family: courier;">1G</span> 了!!</div><div>管防火牆的高手夥伴還啟用了過濾條件,只將 <span style="background-color: black; color: #fcff01; font-family: courier;">critical、alert、emergency</span> 三個等級的 <span style="font-family: courier;">log</span> 往 <span style="font-family: courier;">CentOS</span> 送,這樣 <span style="font-family: courier;">log</span> 的數量大幅減少。</div><div><br /></div><div><span style="font-family: courier;">log 的等級如下:</span></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghW0MOjMzWLPvTlq5EtcGcX8HAo0DSvteve0acochRmaKmNyAbnf3nI54WCETWkOjLHLNc-g9aHLgL7_pj7cLDYyDSLn-zWQ4CL2r805pT0jvI0b6VFn0FouX87L_R39h-0xwYOAB-o7tqd13Uf9X8lQDcmry4Yhub5ezbnPpLWmA2BW2tr64pI08Z7QM/s681/1705653750468.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="275" data-original-width="681" height="258" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghW0MOjMzWLPvTlq5EtcGcX8HAo0DSvteve0acochRmaKmNyAbnf3nI54WCETWkOjLHLNc-g9aHLgL7_pj7cLDYyDSLn-zWQ4CL2r805pT0jvI0b6VFn0FouX87L_R39h-0xwYOAB-o7tqd13Uf9X8lQDcmry4Yhub5ezbnPpLWmA2BW2tr64pI08Z7QM/w640-h258/1705653750468.jpg" width="640" /></a></div><br /><span style="font-family: courier;"><br /></span></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div>
<div><br /></div></div>老灰鴨http://www.blogger.com/profile/07842107154192311741noreply@blogger.com0tag:blogger.com,1999:blog-6067556553711858174.post-41338335532194073052024-01-14T21:44:00.000+08:002024-01-14T21:44:58.336+08:00【LINE】解除 LIFF 連動程式<div class="separator" style="clear: both; text-align: left;">點擊 <span style="background-color: #2b00fe;"><span style="color: #fcff01;">主頁</span></span> → 右上角 <span style="background-color: #2b00fe;"><span style="color: #fcff01;">(設定)齒輪</span></span></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIDxhL7jY1NRiBBYtqPAuWoYsGwkR_SWOCDQbLX1Gt20nbe0UllncXcjtUX076Ee9WVjH43FDLc6_YryLFGAWNynerrV1TVM2qxd0HruoDD9u2JmK1gkHpS8kTYsfGHs5XsPB3jEMr5WPvB52TwhffHPG_gO8m-n23xLAwzn8X6CQ3aNACLwCnhwh6YKk/s960/01.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="960" data-original-width="480" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIDxhL7jY1NRiBBYtqPAuWoYsGwkR_SWOCDQbLX1Gt20nbe0UllncXcjtUX076Ee9WVjH43FDLc6_YryLFGAWNynerrV1TVM2qxd0HruoDD9u2JmK1gkHpS8kTYsfGHs5XsPB3jEMr5WPvB52TwhffHPG_gO8m-n23xLAwzn8X6CQ3aNACLwCnhwh6YKk/w320-h640/01.jpg" width="320" /></a></div><br /><div class="separator" style="clear: both; text-align: center;"><br /></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0Go_DSNba8iR-hQekCtUtMn1UyeCTiVqjMvMzFcyWP3fPDsUSxogo2rjYHdJJp3VAKhFQ4nSShxeqBpKJia17wH6Mg-kWr0MTguUirPwr-dexYB27tgyyDTHHe_mF4SAdUZPfV0Jjqt4AR7ue43zoNXxIFn7JdCpyDl6pHiRXCpiFgUSvluYmsgTdt30/s960/02.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="960" data-original-width="480" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0Go_DSNba8iR-hQekCtUtMn1UyeCTiVqjMvMzFcyWP3fPDsUSxogo2rjYHdJJp3VAKhFQ4nSShxeqBpKJia17wH6Mg-kWr0MTguUirPwr-dexYB27tgyyDTHHe_mF4SAdUZPfV0Jjqt4AR7ue43zoNXxIFn7JdCpyDl6pHiRXCpiFgUSvluYmsgTdt30/w320-h640/02.jpg" width="320" /></a></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkawbMA4R9ZrbpN4XDSkPoEdb3BbsoQNhARY5WIMtvCGGjsH1AHlPWoHbV01IeHFw7lGijdvpjohzqKmIE-dGWV4lhO_ZXISGccxBktRX03aVO-OMfp3jtOqpcgA2lcjUQsHX5y7ACgpajQ0YR6Ql4k9L-5WlQwwSYQJOj3SLqh4TOQdvsABvPb_vOo1g/s960/03.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="960" data-original-width="480" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkawbMA4R9ZrbpN4XDSkPoEdb3BbsoQNhARY5WIMtvCGGjsH1AHlPWoHbV01IeHFw7lGijdvpjohzqKmIE-dGWV4lhO_ZXISGccxBktRX03aVO-OMfp3jtOqpcgA2lcjUQsHX5y7ACgpajQ0YR6Ql4k9L-5WlQwwSYQJOj3SLqh4TOQdvsABvPb_vOo1g/w320-h640/03.jpg" width="320" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7nDA3uJ9p5U7kxlsuO7zhyphenhyphen7Yd0P-RWbf5rvK2M0mkjKK-n-pStdGRm1zdzKX78IPw84Bb1W2U5QK2EudKuUhFEwcMuVGoV0f_OM4ZFAKy8hQc-oQE8gY59r-B-AxfQxaRDEWJ0H7cSL7W4HSDxOGeLWIAjaVzG3HbMu_KAT7O_C8kAelWkOHB0WtyDPk/s960/04.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="960" data-original-width="480" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7nDA3uJ9p5U7kxlsuO7zhyphenhyphen7Yd0P-RWbf5rvK2M0mkjKK-n-pStdGRm1zdzKX78IPw84Bb1W2U5QK2EudKuUhFEwcMuVGoV0f_OM4ZFAKy8hQc-oQE8gY59r-B-AxfQxaRDEWJ0H7cSL7W4HSDxOGeLWIAjaVzG3HbMu_KAT7O_C8kAelWkOHB0WtyDPk/w320-h640/04.jpg" width="320" /></a></div><br /><div class="separator" style="clear: both; text-align: center;"><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjojbqkmVAx2vCFf386kAE4K34dCNfUpiZ_3_7n3wyWay9yJY3414nmHQDCrnVSU2HZvF0R8YAg5EQ4Bxe4JDflodpGB8dP6fZ2X2ezRmXDmfIspOhzz6KJJAEKapGz6W7X-xbFsjKPUrAlnBCQN8-uE0kQsloLvM9hbBp0g3yyNWp5uchAnRRR75mlIvA/s1145/05.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1145" data-original-width="480" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjojbqkmVAx2vCFf386kAE4K34dCNfUpiZ_3_7n3wyWay9yJY3414nmHQDCrnVSU2HZvF0R8YAg5EQ4Bxe4JDflodpGB8dP6fZ2X2ezRmXDmfIspOhzz6KJJAEKapGz6W7X-xbFsjKPUrAlnBCQN8-uE0kQsloLvM9hbBp0g3yyNWp5uchAnRRR75mlIvA/w268-h640/05.jpg" width="268" /></a></div><br /></div><br />老灰鴨http://www.blogger.com/profile/07842107154192311741noreply@blogger.com0tag:blogger.com,1999:blog-6067556553711858174.post-56463004242071825262023-12-07T20:36:00.002+08:002023-12-07T20:49:31.381+08:00【LINE Messaging API】主動傳送訊息給指定使用者<pre class="prettyprint lang-php linenums">
<php?
date_default_timezone_set('Asia/Taipei');
$sRootDir = $_SERVER['DOCUMENT_ROOT'];
$channelAccessToken = '您的 channel Access Token';
$bodyMsg = file_get_contents('php://input');
// LINE 不會幫我們記錄, 所以要自己寫 log
// 這段不是必要, 只是方便自己除錯
$sLogfile = 'log_'.date('Ymd').'.log'; // 指定 log 檔名, 一天一個檔, 檔名格式為: log_yyyymmdd.log
$fp = fopen($sLogfile, "a+");
fwrite($fp , print_r(date('Y-m-d H:i:s').', Recive: '.$bodyMsg."\n", true));
fclose($fp);
...
...
// 取得 user id, 管道有很多, 此處不贅述
$sUserId = ...;
$payload = [ 'to' => $sUserId,
'messages' => [
['type' => 'text',
'text' => '恭喜! 您中了特獎!',
]
]
];
SendMsg($payload);
// 傳送訊息給指定使用者
function SendMsg($payload)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.line.me/v2/bot/message/push');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json',
'Authorization: Bearer ' . $GLOBALS['channelAccessToken']
]);
$result = curl_exec($ch);
curl_close($ch);
// 寫 log
$sLogfile = 'log_'.date('Ymd').'.log';
$fp = fopen($sLogfile, "a+");
fwrite($fp , print_r(date('Y-m-d H:i:s').', send message result: '.$result."\n", true));
fclose($fp);
}
?>
</pre>老灰鴨http://www.blogger.com/profile/07842107154192311741noreply@blogger.com0tag:blogger.com,1999:blog-6067556553711858174.post-24524330185284427652023-12-07T20:23:00.003+08:002023-12-07T20:26:01.864+08:00【LINE Messaging API】以 user id 查詢 user name<pre class="prettyprint lang-php linenums">
<php?
date_default_timezone_set('Asia/Taipei');
$sRootDir = $_SERVER['DOCUMENT_ROOT'];
$channelAccessToken = '您的 channel Access Token';
$bodyMsg = file_get_contents('php://input');
// LINE 不會幫我們記錄, 所以要自己寫 log
// 這段不是必要, 只是方便自己除錯
$sLogfile = 'log_'.date('Ymd').'.log'; // 指定 log 檔名, 一天一個檔, 檔名格式為: log_yyyymmdd.log
$fp = fopen($sLogfile, "a+");
fwrite($fp , print_r(date('Y-m-d H:i:s').', Recive: '.$bodyMsg."\n", true));
fclose($fp);
$obj = json_decode($bodyMsg, true);
$payload = null;
$event = null;
foreach ($obj['events'] as &$event)
{
switch($event['type'])
{
// 如果您有在 LINE 官方帳號後台設定 歡迎詞,
// user 加入好友後也會收到
case 'follow':
// user 加入好友後, 程式自己要做的事
$userId = $event['source']['userId'];
$sUsername = getUsername($sUser);
break;
...
...
}
}
function getUsername($user_id) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://api.line.me/v2/bot/profile/$user_id");
curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json',
'Authorization: Bearer ' . $GLOBALS['channelAccessToken']
]);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$result = curl_exec($ch);
curl_close($ch);
if ($result===false) {
// 查詢失敗,
return 'NG';
}
$data = json_decode($result, true);
return $data["displayName"];
}
?>
</pre>老灰鴨http://www.blogger.com/profile/07842107154192311741noreply@blogger.com0tag:blogger.com,1999:blog-6067556553711858174.post-31620301359378127012023-09-09T11:06:00.001+08:002024-02-11T15:24:31.208+08:00【Kotlin】GeckoView 動態顯示本機網頁參考資料<span style="font-family: courier;"> ----</span><div><span style="font-family: courier;"><a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns" target="_blank">Match patterns</a><br /></span><div><span style="font-family: courier;"><a href="https://firefox-source-docs.mozilla.org/mobile/android/geckoview/consumer/geckoview-quick-start.html" target="_blank">Getting Started with GeckoView</a></span></div><div><a href="https://maven.mozilla.org/?prefix=maven2/org/mozilla/geckoview/" style="font-family: courier;" target="_blank">GeckoView Maven Repository</a></div><div><div><span style="font-family: courier;"><a href="https://wiki.mozilla.org/Mobile/GeckoView" target="_blank">Mobile/GeckoView</a></span></div><div><span style="font-family: courier;"><a href="https://firefox-source-docs.mozilla.org/mobile/android/geckoview/consumer/web-extensions.html" target="_blank">Interacting with Web content</a></span></div><div><a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts" target="_blank"><span style="font-family: courier;">(mdn web docs) Content scripts</span></a></div><div><span style="font-family: courier;"><a href="https://developer.mozilla.org/zh-TW/docs/Mozilla/Add-ons/WebExtensions/manifest.json" target="_blank">Firefox WebExtension API manifest.json</a></span></div><div><span style="font-family: courier;"><a href="https://developer.mozilla.org/zh-TW/docs/Mozilla/Add-ons/WebExtensions/Your_first_WebExtension" target="_blank">你的第一個 WebExtension</a></span></div><div><br /></div><div>動機:</div><div>因為 <span style="font-family: courier;">GeckoView</span> 的顯示效果比 <span style="font-family: courier;">Android</span> 系統內建 <span style="font-family: courier;">WebView</span> 元件強上許多,所以想探索是否能以 <span style="font-family: courier;">GeckoView</span> 取代 <span style="font-family: courier;">WebView</span>,達到 <span style="font-family: courier;"><a href="https://developer.android.com/reference/android/webkit/WebView#loadDataWithBaseURL(java.lang.String,%20java.lang.String,%20java.lang.String,%20java.lang.String,%20java.lang.String)" target="_blank">WebView.loadDataWithBaseURL()</a></span> 的目的。</div><div><br /></div><div>先說結論:截至目前為止(<span style="font-family: courier;">2023.09.09</span>), <span style="font-family: courier;">GeckoView</span> 版本為 <span style="font-family: courier;">117.0.20230824132758</span> 之際,</div><div><span style="background-color: red;"><span style="color: white;">無法</span></span>實作<span style="background-color: red;"><span style="color: white;">動態</span></span>顯示本機網頁,只能顯示靜態網頁,原因在:</div><div><br /></div><div><span style="font-family: courier;">GeckoView</span> 目前尚 <span style="background-color: red; color: white; font-family: courier;">不支援 url 為 resource://... 的 scheme</span></div><div><br /></div><div><br /></div><div>所以當您想顯示動態內容網頁,而網頁又引入了 <span style="font-family: courier;">css、javascript...</span> 等檔案,則這些 <span style="font-family: courier;">css、javascript...檔案必須置放在 internet 的網站上,然後呼叫 </span><span color="var(--highlight-color)" style="font-size: var(--_pr-code-fs); font-style: inherit; font-variant-caps: inherit; font-variant-ligatures: inherit; font-weight: inherit; white-space: inherit;"><span style="font-family: courier;">loadString(html string)。</span></span></div>
<br /><br /><hr />
<div><span color="var(--highlight-color)" style="font-size: var(--_pr-code-fs); font-style: inherit; font-variant-caps: inherit; font-variant-ligatures: inherit; font-weight: inherit; white-space: inherit;"><span style="font-family: courier;"><br /></span></span></div><div><span color="var(--highlight-color)" style="font-size: var(--_pr-code-fs); font-style: inherit; font-variant-caps: inherit; font-variant-ligatures: inherit; font-weight: inherit; white-space: inherit;"><span style="font-family: courier;"><br /></span></span></div><div><span color="var(--highlight-color)" style="font-size: var(--_pr-code-fs); font-style: inherit; font-variant-caps: inherit; font-variant-ligatures: inherit; font-weight: inherit; white-space: inherit;"><span style="font-family: courier;">下面的筆記就先不予理會了...</span></span></div><div><span color="var(--highlight-color)" style="font-size: var(--_pr-code-fs); font-style: inherit; font-variant-caps: inherit; font-variant-ligatures: inherit; font-weight: inherit; white-space: inherit;"><span style="font-family: courier;"><br /></span></span></div><div><br /></div><div><br /></div><div><span style="font-family: courier;">有時候我們並不是要去下載某個網站的網頁,而是想利用 GeckoView 顯示 HTML+CSS 豐富的版面樣式,要讓 app 和 GeckoView 能互傳訊息,這就要利用 WebExtension(擴充套件) 的功能。</span></div><div><br /></div><div><span style="font-family: courier;">開發工具:Android Studio Chipmunk 2021.2.1 Patch 2</span></div>
<div><br /></div><div>在 <span style="background-color: black; color: #fcff01; font-family: courier;">/assets</span> 內建立要置放 <span style="background-color: black; color: #fcff01; font-family: courier;">javascript</span> 、 <span style="background-color: black; color: #fcff01; font-family: courier;">CSS</span> 和 <span style="background-color: black; color: #fcff01; font-family: courier;">WebExtension</span> 的目錄,並將需要的檔案分別置放於內;再建立一個 <span style="font-family: courier;">html</span> 首頁檔,該網頁的 <span style="background-color: black; color: #fcff01; font-family: courier;"><head></span> 區塊要引入前述的 <span style="font-family: courier;">javascript</span> 和 <span style="font-family: courier;">css</span>。</div><div><br /></div><div><span style="font-family: courier;">WebExtension</span> 在 <span style="font-family: courier;">GeckoView</span> 和 <span style="font-family: courier;">app</span> 之間扮演重要的角色,是兩者的溝通橋樑。</div><div><br /></div><div>每建立一個 <span style="font-family: courier;">WebExtension</span>,<span style="font-family: courier;">WebExtension 要有自己的目錄,在本例,我們建立 <span style="background-color: black;"><span style="color: #fcff01;">/assets/messaging/</span></span></span> ,然後每個 <span style="font-family: courier;">WebExtension 要有自己的 <span style="background-color: black;"><span style="color: #fcff01;">manifest.json</span></span></span></div></div><div><span style="font-family: courier;"><br /></span></div><div><span style="font-family: courier;">manifest.json</span></div>
<pre class="prettyprint lang-json linenums">
{
"manifest_version": 2,
"name": "messaging",
"version": "1.0",
"description": "Example messaging web extension.",
"browser_specific_settings": {
"gecko": {
"id": "messaging@example.com"
}
},
"content_scripts": [
{
// "matches": ["*://*.twitter.com/*"],
"matches": ["*://*/*.htm"],
"js": ["messaging.js"]
}
],
"permissions": [
// "activeTab",
"nativeMessaging",
"nativeMessagingFromContent",
"geckoViewAddons"
]
}
</pre>
<div><br /></div>
<div><br /></div>
再建立 <span style="font-family: courier;">app</span> 要與 <span style="font-family: courier;">GeckoView</span> 通訊的 <span style="font-family: courier;">javascript</span> 程式,在本例為 <span style="font-family: courier;">background.js</span><div>
<br /><span style="background-color: black; font-family: courier;"><span style="color: #fcff01;">background.js</span></span><br /><pre class="prettyprint lang-js linenums">
// Establish connection with app
let port = browser.runtime.connectNative("browser");
port.onMessage.addListener(response => {
// Let's just echo the message back
port.postMessage(`Received: ${JSON.stringify(response)}`);
});
port.postMessage("Hello from WebExtension!");
</pre>
<div><br /></div>
<div><br /></div>
WWW
<div><span style="font-family: courier;"><br /></span></div><div><span style="font-family: courier;"><br /></span></div><div><span style="font-family: courier;">
index.htm</span> (檔名可自定喔)
<pre class="prettyprint lang-html linenums">
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="./js/jquery-3.5.1.min.js"></script>
<script type="text/javascript" src="./js/jquery-ui-1.8.16.custom.min.js"></script>
<link rel="stylesheet" href="./css/jquery-ui-1.8.16.custom.css">
<style>
img{ max-width:50%; }
.matrix-block { font-size: 150%; text-align: center }
mtable.thin-column-padding > mtr > mtd,
fmath span.thin-column-padding > table > tbody > tr > td
{ padding: 0 0.11em !important }
</style>
</head>
<body>
hello
</body>
</html>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9sZAf_Kpu8ouZNOZ3b3NhQQfuGHCoI20eoNuFDOUML78urfLoM0ozNfAdrARpFZ8ssx7cztaNMZMmIsPzEkW-kqhEQ0LF2GYDLbHmpyUQzsNA3xnzbjUTU0SYfe7T833d8rC2vJ-J-udsz_Zkhx13ssEt4juQpVAjeYxDe9nMgyXzugMlP1q5Sx23/s435/gecko01.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="435" data-original-width="397" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9sZAf_Kpu8ouZNOZ3b3NhQQfuGHCoI20eoNuFDOUML78urfLoM0ozNfAdrARpFZ8ssx7cztaNMZMmIsPzEkW-kqhEQ0LF2GYDLbHmpyUQzsNA3xnzbjUTU0SYfe7T833d8rC2vJ-J-udsz_Zkhx13ssEt4juQpVAjeYxDe9nMgyXzugMlP1q5Sx23/w365-h400/gecko01.png" width="365" /></a></div></pre><br />
<div><br /></div><span style="background-color: black; color: #fcff01; font-family: courier;">
MainActivity.kt
</span><pre class="prettyprint lang-java linenums">
...
...
import org.mozilla.geckoview.GeckoRuntime;
import org.mozilla.geckoview.GeckoSession;
import org.mozilla.geckoview.GeckoView;
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var view:GeckoView = findViewById(R.id.geckoview)
val session = GeckoSession()
val runtime = GeckoRuntime.create(this)
session.contentDelegate = object : ContentDelegate {}
if(runtime==null) {
runtime = GeckoRuntime.create(this)
}
session.open(runtime)
view.setSession(session)
session.loadUri(""resource://android/assets/index.htm"") // 先載入首頁,讓 GeckoView 記住相關的設定
// 再載入自定的網頁內容
}
}
</pre>
</div><br />
<div><br /></div>
<div><br /></div></div><div>相關筆記<span style="font-family: courier;"> ----</span></div><div><span style="font-family: courier;"><a href="https://oldgrayduck.blogspot.com/2023/01/kotlin-mozilla-gecko-view-webview.html" target="_blank">【Kotlin】以 Mozilla GeckoView 取代 WebView</a></span></div><div><br /></div><div><br /></div></div>老灰鴨http://www.blogger.com/profile/07842107154192311741noreply@blogger.com0tag:blogger.com,1999:blog-6067556553711858174.post-1900338240533181532023-07-16T13:52:00.003+08:002023-07-19T09:26:25.261+08:00【Oracle】查詢指定月份的天數重點是這個語法不論平年、閏年都可查詢得知(而閏年的 2 月有 29 天)。<div><br />
<pre class="prettyprint lang-sql linenums">
SELECT EXTRACT(day FROM LAST_DAY(date'2016-02-01')) AS day_count FROM dual;
</pre></div>老灰鴨http://www.blogger.com/profile/07842107154192311741noreply@blogger.com0tag:blogger.com,1999:blog-6067556553711858174.post-83256831719910201752023-07-08T16:42:00.007+08:002023-07-11T10:14:33.483+08:00【Linux】LinuxMint 21.1 安裝/設定中文輸入法參考資料<span style="font-family: courier;"> ----</span><div><a href="https://oldgrayduck.blogspot.com/2022/02/linuxlinuxmint-203.html" target="_blank"><span style="font-family: courier;">LinuxMint 20.3 安裝/設定中文輸入法</span></a></div><div><br /></div><div><div>滑鼠點擊左下角的開始</div><div>→ 控制中心</div><div>→ 輸入法</div><div>→ 出現視窗</div></div><div><br /></div><div>預設已經安裝 <span style="background-color: black; color: #fcff01; font-family: courier;">fcitx5</span> 輸入法框架,並且已安裝 新酷音</div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhM8v6F-mzywcodJu0wtoqHtg4d9xorDxWnuu3tHsgB_NeIWrdjdwso6_7iUwBIt5O2dpFxME2FAzz_6SeFyzHKQNnU5VhhaTqxUbb1H9_HiAA3VQMtfTNemQzRyhynM18FdbSnzEeBPG20_OTZyXTT3ddhmd1uONLoGHoxShRTzcMbvqDmQmR902Sm6QQ/s800/fcitx5.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="522" data-original-width="800" height="418" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhM8v6F-mzywcodJu0wtoqHtg4d9xorDxWnuu3tHsgB_NeIWrdjdwso6_7iUwBIt5O2dpFxME2FAzz_6SeFyzHKQNnU5VhhaTqxUbb1H9_HiAA3VQMtfTNemQzRyhynM18FdbSnzEeBPG20_OTZyXTT3ddhmd1uONLoGHoxShRTzcMbvqDmQmR902Sm6QQ/w640-h418/fcitx5.png" width="640" /></a></div></div><div> </div><div>頗讓人不爽的是它將 <span style="background-color: black;"><span style="color: #fcff01;">倉頡輸入法</span></span> 歸到簡體中文內</div><div><div>滑鼠點擊左下角的開始</div><div>→ 附屬應用程式</div></div><div>→ 文字編輯器</div><div>→ 營幕右下角出現鍵盤圖示</div><div>→ 滑鼠在鍵盤圖示點右鍵 → 設定</div><div>→ 視窗右側的 "<span style="background-color: black;"><span style="color: #fcff01;">可用輸入法</span></span>" 往下捲,就會看到 倉頡 被歸在 <span style="background-color: black;"><span style="color: #999999; font-family: courier;">简体中文(中国)</span></span></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEidZvkSCfovhgUzpQ3V-8nrqz11oSg-ldecKpCwvnNWmWaceb4bGol0JE0GfIQ3QCFF16wO9bS86aNCpKYPuCepHX5fxfPlgMLnh_PkPzj0d877JnJl12ySjXVwYLl4ozWHFpy7MOljbFlq_yPxomhETkNH1il57HTJ1Cb3DTHeuahKRDPlvwW5BEqAx0A/s802/fcitx5-cn.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="631" data-original-width="802" height="315" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEidZvkSCfovhgUzpQ3V-8nrqz11oSg-ldecKpCwvnNWmWaceb4bGol0JE0GfIQ3QCFF16wO9bS86aNCpKYPuCepHX5fxfPlgMLnh_PkPzj0d877JnJl12ySjXVwYLl4ozWHFpy7MOljbFlq_yPxomhETkNH1il57HTJ1Cb3DTHeuahKRDPlvwW5BEqAx0A/w400-h315/fcitx5-cn.png" width="400" /></a></div><br /> </div><div><br /></div><div>先不關閉 "設定" 視窗,另開個終端機視窗,試著以命令方式安裝 倉頡,也沒看到列出的套件有 <span style="background-color: black; color: #fcff01; font-family: courier;">cangjie</span> 字樣的...</div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgX7SCoYflXG0UAkMhPw9yQpCL-gG_JqocXLNAXSMR9LAOSYnDGVgiI8WERpKfiAqCW4_973kn06bFCHPFR15MVNhd0-tirrwuuZfnOspBvypLscQuIQozsRzszF3ei_nGQYmXZnVTXttaI2Vc06OHD9bgt7jbvD-hWzwg2-MKZjHKgmTFjVRhxP7tI9PE/s708/fcitx5-table.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="708" data-original-width="582" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgX7SCoYflXG0UAkMhPw9yQpCL-gG_JqocXLNAXSMR9LAOSYnDGVgiI8WERpKfiAqCW4_973kn06bFCHPFR15MVNhd0-tirrwuuZfnOspBvypLscQuIQozsRzszF3ei_nGQYmXZnVTXttaI2Vc06OHD9bgt7jbvD-hWzwg2-MKZjHKgmTFjVRhxP7tI9PE/w526-h640/fcitx5-table.png" width="526" /></a></div></div><div><br /></div><div>更令人火大的是,回到 "設定" 視窗,雙擊 "倉頡"(倉頡就移到左邊列表) → 套用 後</div><div><br /></div><div>回到 文字編輯器,切換輸入法為 倉頡,雖然輸入的是繁體中文拆法,但顯示的卻是簡體中文!! 例如:</div><div>輸入 【<span style="background-color: black;"><span style="color: #fcff01;">女火竹水卜</span></span>】畫面出現的候選字居然是【<span style="background-color: red;"><span style="color: white;">终</span></span>】!!</div><div><br /></div><div><span style="background-color: red;"><span style="color: white;">注意</span></span>:不要在 "輸入法" 視窗 按 那個 "安裝" 鈕,會出現一堆套件衝突的錯誤訊息</div><div><br /></div><div>開一個 終端機視窗,將 <span style="font-family: courier;">fcitx5</span> 移除,再安裝舊版的 <span style="background-color: black; color: #fcff01; font-family: courier;">fcitx</span></div>
<pre class="prettyprint lang-sh linenums">
[user]$ sudo apt remove fcitx5*
# 一併安裝 新酷音 & 倉3
[user]$ sudo apt install fcitx fcitx-chewing fcitx-table-cangjie3
<span style="font-family: Times New Roman;"><span style="white-space: normal;">
</span></span></pre>
<div><div>→ 滑鼠點擊左下角的開始</div><div>→ 控制中心</div><div>→ 輸入法</div><div>→ 此時是無 輸入法框架 的狀態</div></div><div>→ 下拉並選取 <span style="background-color: black; color: #fcff01; font-family: courier;">fcitx</span></div><div>→ 重開機</div><div><br /></div><div>接下來的設定步驟就參考上面的 <a href="https://oldgrayduck.blogspot.com/2022/02/linuxlinuxmint-203.html" target="_blank"><span style="font-family: courier;">LinuxMint 20.3 安裝/設定中文輸入法</span></a> 連結吧</div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div>老灰鴨http://www.blogger.com/profile/07842107154192311741noreply@blogger.com0tag:blogger.com,1999:blog-6067556553711858174.post-34899678155628027952023-06-01T01:01:00.000+08:002023-06-01T01:01:15.819+08:00【Kotlin】計算起、迄時刻所經歷的時間<p><span style="background-color: black; color: #fcff01; font-family: courier;">app 層級 build.gradle</span></p>
<pre class="prettyprint lang-text linenums">
android {
...
...
buildFeatures {
viewBinding true
}
}
</pre>
<p><br /></p>
<p><br /></p>
<div><span style="background-color: black; color: #fcff01; font-family: courier;">activity_main.xml</span></div>
<pre class="prettyprint lang-xml linenums">
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/txtHello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:onClick="timeDuration"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</pre>
<p><br /></p>
<p><br /></p>
<div><span style="background-color: black; color: #fcff01; font-family: courier;">MainActivity.kt</span></div>
<pre class="prettyprint lang-java linenums">
package 完整package名
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.SystemClock
import android.view.View
import com.example.timeduration.databinding.ActivityMainBinding
import java.util.Date
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
}
fun timeDuration(vv: View) {
// 狀況 1: 變數型態為 Date 類別
val a1 = Date()
SystemClock.sleep(123)
val a2 = Date()
// 狀況 2: 變數型態為字串, 則轉換為 Date 類別
/*
val pattern = "yyyy.MM.dd HH:mm:ss.SSS"
val formatter = SimpleDateFormat(pattern, Locale.getDefault())
val a1 = formatter.parse("2023.05.01 00:00:00.000")
val a2 = formatter.parse("2023.05.01 01:10:11.123")
*/
val durationMillis = a2.time - a1.time
// 精確度達 0.001 秒
val hours = durationMillis / (1000 * 60 * 60)
val minutes = durationMillis % (1000 * 60 * 60) / (1000 * 60)
val seconds = durationMillis % (1000 * 60) / 1000
val millis = durationMillis % 1000
// 輸出格式為 時:分:秒.毫秒
val sDuration = String.format("%02d:%02d:%02d.%03d", hours, minutes, seconds, millis)
binding.txtHello.text = sDuration
}
}
</pre>
<p><br /></p>
<p><br /></p>老灰鴨http://www.blogger.com/profile/07842107154192311741noreply@blogger.com0tag:blogger.com,1999:blog-6067556553711858174.post-68541769908696645252023-06-01T00:02:00.008+08:002023-06-01T00:19:20.110+08:00【Kotlin】socket ping 判斷是否連上網際網路<div><span style="font-family: courier;">2023.06.01</span><br /><span style="font-family: courier;">
ping</span> 的回應速度極快,還可指定等待 <span style="font-family: courier;">timeout</span> 的時間限制,目前為止找到的最佳解</div>
<p><br /></p>
<div><span style="background-color: black; color: #fcff01; font-family: courier;">app 層級 build.gradle</span></div>
<pre class="prettyprint lang-text linenums">
android {
...
...
buildFeatures {
viewBinding true
}
}
dependencies {
...
...
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4")
}
</pre>
<p><br /></p>
<p><br /></p>
<div><span style="background-color: black; color: #fcff01; font-family: courier;">AndroidManifest.xml</span></div>
<pre class="prettyprint lang-xml linenums">
<uses-permission android:name="android.permission.INTERNET" />
<application
...
...
</pre>
<p><br /></p>
<p><br /></p>
<div><span style="background-color: black; color: #fcff01; font-family: courier;">activity_main.xml</span></div>
<pre class="prettyprint lang-xml linenums">
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/txtHello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:onClick="pingDuration"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</pre>
<p><br /></p>
<p><br /></p>
<div><span style="background-color: black; color: #fcff01; font-family: courier;">MainActivity.kt</span></div>
<pre class="prettyprint lang-java linenums">
package 完整的package名稱
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import com.example.chkinternet.databinding.ActivityMainBinding
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.net.InetSocketAddress
import java.net.Socket
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
}
fun pingDuration(vv: View) {
GlobalScope.launch(Dispatchers.IO) {
var sDuration = ""
try {
val a1 = Date()
val timeoutMs = 1000 // 設定 timeout 為 1 秒
val socket = Socket()
val socketAddress = InetSocketAddress("8.8.8.8", 53)
socket.connect(socketAddress, timeoutMs)
socket.close()
val a2 = Date()
val durationMillis = a2.time - a1.time // 平均小於 0.07 秒
val hours = durationMillis / (1000 * 60 * 60)
val minutes = durationMillis % (1000 * 60 * 60) / (1000 * 60)
val seconds = durationMillis % (1000 * 60) / 1000
val millis = durationMillis % 1000
sDuration = String.format("%02d:%02d:%02d.%03d", hours, minutes, seconds, millis)
} catch (e: Exception) {
sDuration = "斷網 -- "+e.message
}
withContext(Dispatchers.Main) {
binding.txtHello.text = sDuration
}
}
}
}
</pre>
<p><br /></p>
<p><br /></p>老灰鴨http://www.blogger.com/profile/07842107154192311741noreply@blogger.com0tag:blogger.com,1999:blog-6067556553711858174.post-43881040578116996332023-05-20T20:48:00.007+08:002023-05-21T21:24:28.594+08:00【Linux】在 CentOS7 製作 LinuxMint21 USB 開機碟參考資料<span style="font-family: courier;"> ----</span><div><span style="font-family: courier;"><a href="http://pkgs.org">pkgs.org</a></span></div><div><br /></div><h4 style="text-align: left;">以指令方式製作</h4><div>開啟終端機</div>
<pre class="prettyprint lang-sh linenums">
切換到存放要製作的 LinuxMint21 iso 檔的目錄, 在本例為 /vm
而我的隨身碟目錄為 /dev/sdf
[user]$ cd /vm/
[user]$ su
[root]# dd if=linuxmint-21.1-mate-64bit.iso of=/dev/sdf
</pre>
<div>指令模式不會顯示執行進度只好靜靜等待<span style="font-family: courier;">。</span></div><div><span style="font-family: courier;"><br /></span></div><div><span style="font-family: courier;"><br /></span></div><h4 style="text-align: left;"><span style="font-family: courier;">以圖形界面方</span><span style="font-family: courier;">式製作</span></h4><div><span style="font-family: courier;">CentOS7</span> 要製作 <span style="font-family: courier;">USB</span> 開機碟,網上推薦的圖形界面工具軟體都是 <span style="font-family: courier;"><a href="https://unetbootin.github.io/" target="_blank">unetbootin</a></span>。</div><div><br /></div><div><span style="font-family: courier;">CentOS7 並未內建安裝 unetbootin,所以要另外尋找套件庫 -- </span><span style="font-family: courier;"><a href="http://li.nux.ro/download/nux/dextop/el7/x86_64/" target="_blank">nux-dextop-release</a>。</span></div><div><span style="font-family: courier;"><br /></span></div><div><span style="font-family: courier;">到 <a href="http://li.nux.ro/download/nux/dextop/el7/x86_64/" target="_blank">http://li.nux.ro/download/nux/dextop/el7/x86_64/</a> 下載 <a href="http://nux-dextop-release-0-5.el7.nux.noarch.rpm" target="_blank">nux-dextop-release-0-5.el7.nux.noarch.rpm</a> 並安裝。</span></div>
<pre class="prettyprint lang-sh linenums">
[user]$ su
[root]# yum install nux-dextop-release-0-5.el7.nux.noarch.rpm
[root]# yum install unetbootin
</pre>
<div><br /></div>
<div><br /></div>
<div>執行 <span style="font-family: courier;">unetbootin</span>(需要<span style="font-family: courier;"> root </span>權限)<br /></div>
<pre class="prettyprint lang-sh linenums">
[root]# cd /usr/bin/
[root]# ./unetbootin
</pre><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEK_HiseEnozifGMraGE9I9K3uB5QMj_RHd5JUAmkCg7ejXXe3xUlmnd99Z-4Ot74YoSzvZL2BKRjcrFBifgEIQTmRFH6KREOQfAAGJu80igF9tMbcWQu6Fz2rKDv1WthazOIvXiUe5ifPv5Qmm9qq6oYqredS_bnDaNkEvN4D_e7dcrvWW1eG84Lo/s524/2023-05-20%2016-18-23%20%E7%9A%84%E8%9E%A2%E5%B9%95%E6%93%B7%E5%9C%96.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="397" data-original-width="524" height="484" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEK_HiseEnozifGMraGE9I9K3uB5QMj_RHd5JUAmkCg7ejXXe3xUlmnd99Z-4Ot74YoSzvZL2BKRjcrFBifgEIQTmRFH6KREOQfAAGJu80igF9tMbcWQu6Fz2rKDv1WthazOIvXiUe5ifPv5Qmm9qq6oYqredS_bnDaNkEvN4D_e7dcrvWW1eG84Lo/w640-h484/2023-05-20%2016-18-23%20%E7%9A%84%E8%9E%A2%E5%B9%95%E6%93%B7%E5%9C%96.png" width="640" /></a></div><br />按下 "確定" 鈕,等待幾分鐘後就製作完成了<span style="font-family: courier;">。</span></div><div><br /></div>
<div><br /></div><div><br /></div><div><br /></div>老灰鴨http://www.blogger.com/profile/07842107154192311741noreply@blogger.com0tag:blogger.com,1999:blog-6067556553711858174.post-51348598493031510182023-05-16T14:45:00.005+08:002023-07-29T09:59:13.883+08:00【PHP】讀取 .csv 檔轉入資料庫,且檔案內容包含 html<p>參考資料<span style="font-family: courier;"> ----</span></p><p><a href="https://www.php.net/manual/en/function.fgetcsv.php" target="_blank"><span style="font-family: courier;">fgetcsv</span></a></p><p><br /></p>
<pre class="prettyprint lang-php linenums">
$file = fopen($fileName, 'r'); // 開啟 CSV 檔案
if($file)
{
fgetcsv($file); // 如果第一列是標題列, 空跑一次不讀取, 略過標題列
while (($data = fgetcsv($file)) !== false)
{
// 逐行讀取 CSV 檔案的內容
$num_fields = count($data); // 取得欄位數
$sField0 = $data[0]; // 數字
$sField1 = $data[1]; // 數字
$sField2 = $data[2]; // 字串
$sField3 = $data[3]; // 字串
$sField4 = $data[4]; // html 字串
$sField5 = $data[5]; // 字串
switch($num_fields) // 因為我的 csv 檔有 2 種格式,欄位數 5 欄 & 6 欄 2 種
{
case 5: // 無第 6 欄
$sField6 = 'null';
break;
case 6: // 有第 6 欄
if(length($data[6])>0)
// 第 6 欄有值
$sField6 = "'".$data[6]."'";
else
$sField6 = 'null';
break;
}
// 因為要防止 SQL injection, 當 SQL 語法中含有 html 時, html 會被 "洗掉"!
// 所以要以 bindParam() 參數的方式組成 SQL 語法
$sql = "INSERT INTO qbankd
(Field1, Field2, Field3, Field4, Field5, Field6)
VALUES
($sField1, '$sField2', '$sField3', :sField4, '$sField5', $sField6) ";
$pdoStat = $pdo->prepare($sql);
$pdoStat->bindParam(':sField4', $sField4);
$pdoStat->execute();
}
fclose($file); // 關閉 CSV 檔案
unlink($file); // 刪除檔案
}
</pre>
<p><br /></p>
<p><br /></p>老灰鴨http://www.blogger.com/profile/07842107154192311741noreply@blogger.com0tag:blogger.com,1999:blog-6067556553711858174.post-70837185386869386342023-05-07T16:23:00.004+08:002023-05-07T18:19:36.315+08:00【Python2】計算 起、迄 時刻經過的時間<pre class="prettyprint lang-py linenums">
#!/usr/bin/python
#-*- coding:utf-8 -*-
import datetime
tBeginTime = datetime.datetime.now()
sBegin = tBeginTime.strftime("%Y.%m.%d %H:%M:%S")
...
...
tStopTime = datetime.datetime.now()
sStop = tStopTime.strftime("%Y.%m.%d %H:%M:%S")
print('開始時刻: '+sBegin+'\n'+
'結束時刻: '+sStop+'\n'+
'歷時: '+str(tStopTime-tBeginTime))
# 顯示類似下述
# 開始時間: 2023.05.05 10:19:18
# 結束時間: 2023.05.06 22:46:04
# 歷時: 1 day, 12:26:46.230894
</pre>
<p><br /></p>
<p><br /></p>
老灰鴨http://www.blogger.com/profile/07842107154192311741noreply@blogger.com0tag:blogger.com,1999:blog-6067556553711858174.post-54772481499849190922023-05-01T18:26:00.004+08:002023-05-01T18:26:51.389+08:00【軟體筆記】將目錄下的檔名匯入至純文字檔內<p>開啟 命令提示字元(<span style="font-family: courier;">DOS </span>視窗)</p>
<pre class="prettyprint lang-sh linenums">
# 切換至要匯出檔名的目錄
cd\dir1
# 以 dir 指令將檔名匯到指定的文字檔
dir /b > c:\myfilelist.txt
</pre>
老灰鴨http://www.blogger.com/profile/07842107154192311741noreply@blogger.com0tag:blogger.com,1999:blog-6067556553711858174.post-25688158926723432752023-04-16T20:45:00.006+08:002023-04-16T21:34:53.953+08:00【Kotlin】AdMob 插頁式廣告(InterstitialAd AdMob20[含]↑)<p>參考資料 ----</p><p><a href="https://developers.google.com/admob/android/interstitial-fullscreen" target="_blank">插页式广告</a></p><p><a href="https://developers.google.com/android/reference/com/google/android/gms/ads/interstitial/InterstitialAd" style="font-family: courier;" target="_blank">InterstitialAd</a></p><p><a href="https://support.google.com/admob/answer/6201362" target="_blank">不允許的插頁式廣告導入方式</a></p>
<p> </p>
<p> </p>
<p><span style="background-color: black; color: yellow; font-family: "courier new", courier, monospace;">app 層級的 build.gradle</span> </p>
<pre class="prettyprint lang-xml linenums">
dependencies {
...
...
// adMob
implementation 'com.google.android.gms:play-services-ads:22.0.0'
}
apply plugin: 'com.google.gms.google-services'
</pre>
<p> </p>
<p> </p>
<p><span style="background-color: black; color: yellow; font-family: "courier new", courier, monospace;">AndroidManifest.xml</span> </p>
<pre class="prettyprint lang-xml linenums">
<application
...
...>
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="@string/admob_app_id" />
...
...
</pre>
<p> </p>
<p> </p>
<p><span style="background-color: black; color: yellow; font-family: "courier new", courier, monospace;">string.xml</span> </p>
<pre class="prettyprint lang-xml linenums">
<!-- adMob app id 測試 -->
<string name="admob_app_id">ca-app-pub-3940256099942544~3347511713</string>
<!-- 橫幅 測試 -->
<string name="banner_ad_unit_id">ca-app-pub-3940256099942544/6300978111</string>;
<!-- 插頁 測試 -->
<string name="interstitial_ad_unit_id">ca-app-pub-3940256099942544/8691691433</string>
<!-- 影片 測試 -->
<string name="video_ad_id">ca-app-pub-3940256099942544/5224354917</string>
</pre>
<p> </p>
<p> </p>
<p><span style="background-color: black; color: yellow; font-family: "courier new", courier, monospace;">MainActivity.java</span> </p>
<pre class="prettyprint lang-java linenums">
class MyActivity : Activity()
{
var mInterstitialAd: InterstitialAd? = null
...
...
override fun onResume()
{
var adRequest = AdRequest.Builder().build()
InterstitialAd.load(this,getString(R.string.interstitial_ad_unit_id), adRequest, object : InterstitialAdLoadCallback() {
override fun onAdFailedToLoad(adError: LoadAdError) {
Log.d(TAG, adError.toString())
mInterstitialAd = null
}
override fun onAdLoaded(interstitialAd: InterstitialAd) {
Log.d(TAG, "Ad was loaded.")
mInterstitialAd = interstitialAd
}
})
super.onResume()
}
// 當 usser 按了按鈕時
fun onImgClicked(vv: View)
{
toWatchIntersticialAd()
}
// 觀看插頁廣告
private fun toWatchIntersticialAd()
{
val random = Random()
if ((mInterstitialAd!=null) && (random.nextInt(3)==0))
// if(mInterstitialAd!= null) // 測試用, 每次都播放
{
// 若廣告已載入, 且亂數是 3 的倍數, 則播放
// (AdMob 規定: 插頁播放的比例須 < 50%)
mInterstitialAd?.fullScreenContentCallback =
object : FullScreenContentCallback() {
override fun onAdDismissedFullScreenContent() {
Log.d(TAG, "Ad was dismissed.")
// user 關閉廣告, 回主畫面
mInterstitialAd = null
// 返回主畫面
NavUtils.navigateUpFromSameTask(this@MyActivity)
}
override fun onAdFailedToShowFullScreenContent(adError: AdError) {
Log.d(TAG, "Ad failed to show.")
mInterstitialAd = null
// 返回主畫面
NavUtils.navigateUpFromSameTask(this@MyActivity)
}
override fun onAdShowedFullScreenContent() {
Log.d(TAG, "Ad showed fullscreen content.")
// Called when ad is dismissed.
}
}
mInterstitialAd!!.show(this)
}
else
// 直接返回主畫面
NavUtils.navigateUpFromSameTask(this)
}
}
</pre>
<p><br /></p>
<p> </p>
<p> </p>
<p> </p>
<p> </p>
老灰鴨http://www.blogger.com/profile/07842107154192311741noreply@blogger.com0tag:blogger.com,1999:blog-6067556553711858174.post-35793491197334980022023-04-02T11:23:00.004+08:002023-04-02T11:24:17.408+08:00【Gimp】文字變形<p>建立文字 <br /></p><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0cJD2iQRKfds_wqym7hqUMZG4qx4x0DtLJBqJaaYq9xAdohJfH7sGB0l_iNoD4f35_CK2OkOH9LShmxAuEb9xz54gL3lbneDgrryieIQiSJyHphk78L85opZOvOKZf7e3vpMSGea56irIxxzqQQ7iZWtpJgZaCAJDDEQfs2sxiyu5hXGXhtBopGTA/s1235/02_%E5%BB%BA%E7%AB%8B%E6%96%87%E5%AD%97.png" style="margin-left: 1em; margin-right: 1em;">
<img border="0" data-original-height="505" data-original-width="1235" height="262" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0cJD2iQRKfds_wqym7hqUMZG4qx4x0DtLJBqJaaYq9xAdohJfH7sGB0l_iNoD4f35_CK2OkOH9LShmxAuEb9xz54gL3lbneDgrryieIQiSJyHphk78L85opZOvOKZf7e3vpMSGea56irIxxzqQQ7iZWtpJgZaCAJDDEQfs2sxiyu5hXGXhtBopGTA/w640-h262/02_%E5%BB%BA%E7%AB%8B%E6%96%87%E5%AD%97.png" width="640" /></a></div><p></p><p><br /></p><p>功能表</p><p>→ 工具</p><p>→ 變換工具</p><p>→ 縮放<br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoCQxNQX2Sl1hn4vix1buZTDdJI5EfHTCe0IlOe1fE0QMoi0JyP2AzMMKAzXIztn6GapMM6CtQK5vdd4bV3sG-IafKYIvyxnehv6PBeQLEysDnphpUlSz7d2Cxj-yklahe31KzL_oFQHjSnb4AfJV46eVsVku4m2N8Ld9orOSDq7Mk_V3bn2FpXZP9/s779/03_%E5%B7%A5%E5%85%B7_%E8%AE%8A%E6%8F%9B%E5%B7%A5%E5%85%B7_%E7%B8%AE%E6%94%BE.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="426" data-original-width="779" height="350" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoCQxNQX2Sl1hn4vix1buZTDdJI5EfHTCe0IlOe1fE0QMoi0JyP2AzMMKAzXIztn6GapMM6CtQK5vdd4bV3sG-IafKYIvyxnehv6PBeQLEysDnphpUlSz7d2Cxj-yklahe31KzL_oFQHjSnb4AfJV46eVsVku4m2N8Ld9orOSDq7Mk_V3bn2FpXZP9/w640-h350/03_%E5%B7%A5%E5%85%B7_%E8%AE%8A%E6%8F%9B%E5%B7%A5%E5%85%B7_%E7%B8%AE%E6%94%BE.png" width="640" /></a></div><p></p><p><br /></p><p>這時會出現 "<span style="background-color: black; color: #fcff01;">縮放</span>" 的小視窗,在 "寬度" & "高度" 設定值的右側,有個像 鐵鏈 的圖示,表示您縮放這個文字框時,是以等比例縮放的。</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiBLaHAajM8OKhdRJ_3lVTl-VfL2Gfx7KatG8fUb91bp10fgF-BfpUq0kx9KAuYoK-3Zhz3tj55PnKK7IswoXlK-3lgejJMQB-S28jSJRVofoN-bAdZHeSOgqPvfYjqayB2Sm24cESZsyW9z-sxQagKblakRC385KupVaCCSO59Xz-IvVaXw4NcFB9H/s1232/04_%E7%B8%AE%E6%94%BE_%E7%AD%89%E6%AF%94%E4%BE%8B.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="735" data-original-width="1232" height="382" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiBLaHAajM8OKhdRJ_3lVTl-VfL2Gfx7KatG8fUb91bp10fgF-BfpUq0kx9KAuYoK-3Zhz3tj55PnKK7IswoXlK-3lgejJMQB-S28jSJRVofoN-bAdZHeSOgqPvfYjqayB2Sm24cESZsyW9z-sxQagKblakRC385KupVaCCSO59Xz-IvVaXw4NcFB9H/w640-h382/04_%E7%B8%AE%E6%94%BE_%E7%AD%89%E6%AF%94%E4%BE%8B.png" width="640" /></a></div><p></p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4-rbjW2OytOIj0pDS43nBWfH3-iEc4HTypGQ_1OKAJMPFLHiW2VI4HutZP1YgNj2BgSrxOinDAQEBrT-7sfePiEdbG-uNqTslB4pk6b-Gto-ps4WHFMYc8Pmh6P3QiAbali3dbovUu83_zQa8F8aA4U8TPXnXnXZYInssZq3miyCvoOYPOW4wNqVf/s267/04_%E7%B8%AE%E6%94%BE_%E7%AD%89%E6%AF%94%E4%BE%8B_2.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="158" data-original-width="267" height="379" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh4-rbjW2OytOIj0pDS43nBWfH3-iEc4HTypGQ_1OKAJMPFLHiW2VI4HutZP1YgNj2BgSrxOinDAQEBrT-7sfePiEdbG-uNqTslB4pk6b-Gto-ps4WHFMYc8Pmh6P3QiAbali3dbovUu83_zQa8F8aA4U8TPXnXnXZYInssZq3miyCvoOYPOW4wNqVf/w640-h379/04_%E7%B8%AE%E6%94%BE_%E7%AD%89%E6%AF%94%E4%BE%8B_2.png" width="640" /></a></div><p></p><p><br /></p><p>點擊一下 "鐵鏈" 就斷開了,表示文字框可以 橫向 / 直向 各自縮放變形<br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4mF6SJT7qnWSRHWaFEEHNUo3dlGklkHCoGLOw0yUsAysPUt9tTpzXM_V7-NAQ6QuHwJ9ss_QsmVhgoPdkie3o6b88bQ0CkEg2UGagMz0C5KQo9GZI9OkqLKD4SX0E_r9YbIP5-cvWR_u15u-XQ7tmybaAbLw-Im24R-sRwOiKmAmrHvxhnO9D40Q0/s1224/04_%E7%B8%AE%E6%94%BE.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="396" data-original-width="1224" height="208" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4mF6SJT7qnWSRHWaFEEHNUo3dlGklkHCoGLOw0yUsAysPUt9tTpzXM_V7-NAQ6QuHwJ9ss_QsmVhgoPdkie3o6b88bQ0CkEg2UGagMz0C5KQo9GZI9OkqLKD4SX0E_r9YbIP5-cvWR_u15u-XQ7tmybaAbLw-Im24R-sRwOiKmAmrHvxhnO9D40Q0/w640-h208/04_%E7%B8%AE%E6%94%BE.png" width="640" /></a></div><p></p><p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgMgm7-5P3Ssc1BjjWLX8EBMHHtSNFUy6xyMwGXyJ2svdjbL3x6ydGexzzhQNWBXJP2znOcOcvKyKtmJXbrC4_Swh7jXROYEL21gioGhemp0y5E-hWqMpFiz_1wYynqJiEO3-JBU9Q7gVkFw2jHmpBqcFACQzQ1m4ZzhHSZmx4G4gRlIZk34jVhBVB/s1183/05_%E7%A2%BA%E5%AE%9A%E7%B8%AE%E6%94%BE.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="599" data-original-width="1183" height="324" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgMgm7-5P3Ssc1BjjWLX8EBMHHtSNFUy6xyMwGXyJ2svdjbL3x6ydGexzzhQNWBXJP2znOcOcvKyKtmJXbrC4_Swh7jXROYEL21gioGhemp0y5E-hWqMpFiz_1wYynqJiEO3-JBU9Q7gVkFw2jHmpBqcFACQzQ1m4ZzhHSZmx4G4gRlIZk34jVhBVB/w640-h324/05_%E7%A2%BA%E5%AE%9A%E7%B8%AE%E6%94%BE.png" width="640" /></a></div><br /><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p>老灰鴨http://www.blogger.com/profile/07842107154192311741noreply@blogger.com0tag:blogger.com,1999:blog-6067556553711858174.post-73449412322857007222023-03-19T21:27:00.018+08:002023-07-16T20:10:36.946+08:00【Kotlin】ping 判斷 android 裝置是否連線至網際網路參考資料<span style="font-family: courier;"> ----</span><div><a href="https://developer.android.com/reference/java/net/InetAddress#getByName(java.lang.String)" target="_blank"><span style="font-family: courier;">InetAddress</span></a></div><div><br /></div><div><br /></div><div><span style="font-size: medium;">請改參考這篇 -- <a href="https://oldgrayduck.blogspot.com/2023/06/kotlinsocket-ping.html" target="_blank">【Kotlin】socket ping 判斷是否連上網際網路</a></span></div><div><br /></div><div><br /></div><div><br /></div><div>在爬文的過程中, 也有看到認為這不可靠的觀點......所以,再繼續尋找更好的做法吧......</div><div><br /></div><div><span style="font-family: courier;">===== 2023.03.23 =====</span></div><div><span style="font-family: courier;">拿手邊的幾支 Android 裝置實測,在同樣的 Wi-Fi 環境中</span></div><div><span style="font-family: courier;">ASUS Zenfone3(Android8.0) -- 正常</span></div><div><span style="font-family: courier;">華為 T3 平板(Android7.0) -- 反應網路不通,但 app 內的 AdMob 廣告播放正常,表示 app 在 T3 執行時誤判。</span></div><div><span style="font-family: courier;">SugarY12S(AndroidGo8.1.0) -- </span><span style="font-family: courier;">正常</span></div><div><span style="font-family: courier;">ASUS Zenfone5(Android9) -- 正常</span></div><div><span style="font-family: courier;">小米 POCO X4 PRO(Android12) -- 正常</span></div><div><br /></div><div><br /></div>
<p><span style="background-color: black; color: #fcff01; font-family: courier;">app 層級的 build.gradle</span></p>
<pre class="prettyprint lang-txt linenums">
...
...
android {
...
...
buildFeatures {
viewBinding true
}
buildTypes {
...
...
}
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4")
}
...
...
</pre>
<p><br /></p>
<p><br /></p>
<span style="background-color: black; color: #fcff01; font-family: courier;">AndroidManifest.xml</span>
<pre class="prettyprint lang-xml linenums">
<uses-permission android:name="android.permission.INTERNET"/>
<application
...
...
</pre>
<p><br /></p><span style="background-color: black; color: #fcff01; font-family: courier;">
activity_main.xml</span><div>
<pre class="prettyprint lang-xml linenums">
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/lblHello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="isConnected"
android:text="Hello World!"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</pre>
<p><br /></p>
<p><br /></p><span style="background-color: black; color: #fcff01; font-family: courier;">
MainActivity.kt
</span><pre class="prettyprint lang-java linenums">
import android.content.ContentValues.TAG
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Toast
import com.example.pingconn.databinding.ActivityMainBinding
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.IOException
import java.net.InetAddress
class MainActivity : AppCompatActivity() {
val TAG = "MainActivity"
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
}
fun isConnected(vv: View)
{
// Toast.makeText(this, "test", Toast.LENGTH_SHORT).show()
GlobalScope.launch {
val address = "8.8.8.8"
val isReachable = myPing(address)
withContext(Dispatchers.Main) {
updateUI(isReachable)
}
}
}
private fun updateUI(isReachable: Boolean) {
if(isReachable)
binding.lblHello.text = "internet connected"
else
binding.lblHello.text = "no internet"
}
suspend fun myPing(address: String): Boolean = withContext(Dispatchers.IO) {
try {
val inetAddress = InetAddress.getByName(address)
inetAddress.isReachable(500) // 單位:毫秒,
} catch (e: Exception) {
false
}
}
}
</pre>
<div><br /></div>
<div><br /></div>
相關筆記<span style="font-family: courier;"> ----</span></div><div><a href="https://oldgrayduck.blogspot.com/2022/10/kotlin-viewbinding.html" target="_blank"><span style="font-family: courier;">【Kotlin】視圖綁定 ViewBinding</span></a><br />
<div><br /></div>
<div><br /></div></div>老灰鴨http://www.blogger.com/profile/07842107154192311741noreply@blogger.com0tag:blogger.com,1999:blog-6067556553711858174.post-66958858327779418852023-03-11T16:17:00.006+08:002023-03-11T16:19:53.157+08:00【Kotlin】以 OkHttp3 偵測是否有網際網路連線能力<p>參考資料<span style="font-family: courier;"> ----</span></p><p><span style="font-family: courier;"><a href="https://square.github.io/okhttp/" target="_blank">OkHttp</a></span></p><p><br /></p><p><span style="font-family: courier;">OkHttp</span> 是輕量型的 <span style="font-family: courier;">3rd party library,這篇筆記只是記錄透過 OkHttp 偵測 Android 裝置是否有連上網際網路。</span></p><p><span style="font-family: courier;">上網比較了幾個網站,選定目標網站是網頁內容(html 字數,包含 CSS, javascript...) 最少的 </span><a href="https://example.com" target="_blank"><span style="font-family: courier;">example.com</span></a><span style="font-family: courier;">。</span></p><p><span style="font-family: courier;"><br /></span></p><p><span style="background-color: black; color: #fcff01; font-family: courier;">app 層級的 build.gradle</span></p>
<pre class="prettyprint lang-txt linenums">
...
...
android {
...
...
buildFeatures {
viewBinding true
}
buildTypes {
...
...
}
}
...
...
dependencies {
...
...
implementation 'com.squareup.okhttp3:okhttp:4.7.2'
}
</pre>
<p><br /></p>
<p><br /></p>
<span style="background-color: black; color: #fcff01; font-family: courier;">AndroidManifest.xml</span>
<pre class="prettyprint lang-xml linenums">
<uses-permission android:name="android.permission.INTERNET"/>
<application
...
...
</pre>
<p><br /></p>
<p><br /></p><span style="background-color: black; color: #fcff01; font-family: courier;">
activity_main.xml
</span><pre class="prettyprint lang-xml linenums">
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/lblHello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btnCheck"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="check internet"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.064"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.023"
android:onClick="onBtnClicked"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</pre>
<p><br /></p>
<p><br /></p><span style="background-color: black; color: #fcff01; font-family: courier;">
MainActivity.kt
</span><div>
<pre class="prettyprint lang-java linenums">
...
...
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.example.okhp.databinding.ActivityMainBinding
import okhttp3.*
import okio.IOException
class MainActivity : AppCompatActivity() {
val TAG = "MyTag"
private lateinit var binding: ActivityMainBinding
private lateinit var client: OkHttpClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
}
fun onBtnClicked(vv: View) {
val client = OkHttpClient()
val request = Request.Builder()
.url("https://example.com")
.build()
client.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
// Log.d(TAG, "response: ${response.body?.string()}")
runOnUiThread {
binding.lblHello.text = "connected"
}
}
override fun onFailure(call: Call, e: IOException) {
// Log.d(TAG, e.toString())
runOnUiThread {
binding.lblHello.text = "no internet"
}
}
})
}
}
</pre></div>
<p><br /></p>
<p><br /></p><div>跟 <span style="background-color: black; color: #fcff01; font-family: courier;">Volley</span> 稍微比較了一下,<span style="font-family: courier;">OkHttp3</span> 速度確實快了一些。</div><div><br /></div><div><br /></div>
相關筆記<span style="font-family: courier;"> ----</span><div><span style="font-family: courier;"><a href="https://oldgrayduck.blogspot.com/2022/10/kotlin-viewbinding.html" target="_blank">【Kotlin】視圖綁定 ViewBinding</a></span></div><div><span style="font-family: courier;"><a href="https://oldgrayduck.blogspot.com/2019/11/android-studiovolley-httpclient.html" target="_blank">【Kotlin】Volley -- 輕量 httpclient</a></span></div><div><br /></div><div><br /></div>老灰鴨http://www.blogger.com/profile/07842107154192311741noreply@blogger.com0tag:blogger.com,1999:blog-6067556553711858174.post-23380957114352582682023-02-05T18:33:00.003+08:002023-02-06T13:00:46.972+08:00【Kotlin】以 BroadcastReceiver 通知裝置網路連線狀態 -- 適用 API28(含)之前參考資料 <span style="font-family: "courier new" , "courier" , monospace;">----</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><a href="http://developer.android.com/intl/zh-tw/reference/android/net/ConnectivityManager.html" target="_blank">ConnectivityManager</a></span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><a href="http://developer.android.com/intl/zh-tw/reference/android/net/NetworkInfo.html#getTypeName%28%29" target="_blank">NetworkInfo</a></span> <br /><br />
<br />授予 <span style="font-family: "courier new" , "courier" , monospace;">app</span> 權限<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;"><span style="color: yellow;"><span style="background-color: black;">AndroidManifest.xml</span></span>:
</span><br />
<pre class="prettyprint lang-xml linenums">
...
...
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
...
...
</pre>
<div><br /></div>
<div><br /></div>
<span style="background-color: black; color: #fcff01; font-family: courier;">app 層級 build.gradle</span>
<pre class="prettyprint lang-xml linenums">android {
compileSdk 33
...
...
buildFeatures {
viewBinding true
}
...
...
}
</pre>
<div><br /></div>
<div><br /></div>
<span style="font-family: "courier new" , "courier" , monospace;"><span style="color: yellow;"><span style="background-color: black;">activity_main.xml</span></span>:</span><br />
<pre class="prettyprint lang-xml linenums">
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/txtHello"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</pre>
<br /><br />
<br /><span style="background-color: black; color: #fcff01; font-family: courier;">
MainActivity.kt
</span><pre class="prettyprint lang-java linenums">
...
...
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
}
override fun onResume() {
super.onResume()
val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
registerReceiver(connectivityReceiver, filter)
}
override fun onPause() {
super.onPause()
unregisterReceiver(connectivityReceiver)
}
fun updateTextView(message: String) {
binding.txtHello.text = message
}
...
...
private val connectivityReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val connectivityManager = context?.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkInfo = connectivityManager.activeNetworkInfo
if (networkInfo != null && networkInfo.isConnected) {
(context as MainActivity).updateTextView("已連線")
} else {
(context as MainActivity).updateTextView("已斷線")
}
}
}
}
...
...
</pre>
<br /><br />
相關筆記 <span style="font-family: "courier new" , "courier" , monospace;">----</span><div><span style="font-family: courier new, courier, monospace;"><a href="https://oldgrayduck.blogspot.com/2016/03/android-ping.html" target="_blank">【Android】用 PING 偵測網路連線狀況</a><br /></span><div><span style="font-family: courier new, courier, monospace;"><a href="https://oldgrayduck.blogspot.com/2022/10/kotlin-viewbinding.html" target="_blank">【Kotlin】視圖綁定 ViewBinding</a></span><div><br /></div><div><br /></div></div></div>老灰鴨http://www.blogger.com/profile/07842107154192311741noreply@blogger.com0tag:blogger.com,1999:blog-6067556553711858174.post-56163856716739558962023-01-22T18:28:00.019+08:002023-04-30T01:02:44.763+08:00【Kotlin】以 Mozilla GeckoView 取代 webview參考資料<span style="font-family: courier;"> ----</span><div><span style="font-family: courier;"><a href="https://firefox-source-docs.mozilla.org/mobile/android/geckoview/consumer/geckoview-quick-start.html" target="_blank">Getting Started with GeckoView</a></span></div><div><a href="https://maven.mozilla.org/?prefix=maven2/org/mozilla/geckoview/" style="font-family: courier;" target="_blank">GeckoView Maven Repository</a></div><div><div><span style="font-family: courier;"><a href="https://wiki.mozilla.org/Mobile/GeckoView" target="_blank">Mobile/GeckoView</a></span></div><div><br /></div><div><br /></div><div><span style="font-family: courier;">Android</span> 內建的 <span style="font-family: courier;">webview</span> 元件在顯示的體驗上不是很好,某些網頁版面的顯示結果就是不像真正的瀏覽器,而 <span style="font-family: courier;">Mozilla </span>的 <span style="background-color: black; color: #fcff01; font-family: courier;">Gecko View</span> 是真正的瀏覽器引擎。</div><div><br /></div><div>去 <span style="background-color: black; color: #fcff01; font-family: courier;"><a href="https://maven.mozilla.org/?prefix=maven2/org/mozilla/geckoview/">GeckoView Maven Repository</a></span> 尋找您要的<span style="background-color: black;"><span style="color: #fcff01;">版本號</span></span>;在本筆記,選擇穩定版 <span style="font-family: courier;">100.0.20220425210429</span></div><div><span style="font-family: courier;"><br /></span></div><div><span style="font-family: courier;"><br /></span></div><div><span style="font-family: courier;">開發工具:Android Studio Chipmunk 2021.2.1 Patch 2</span></div>
<div><br /></div>
<div><br /></div>
<div><span style="font-family: courier;"><span style="background-color: black;"><span style="color: #fcff01;">Project 層級</span></span> build.gradle</span><br /></div>
<pre class="prettyprint lang-xml linenums">
buildscript {
ext.kotlin_version = "1.7.10"
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.2.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
</pre>
<div><br /></div>
<div><br /></div>
<div><span style="background-color: black; color: #fcff01; font-family: courier;">app 層級</span> <span style="font-family: courier;">build.gradle</span><br /></div>
<pre class="prettyprint lang-sh linenums">
plugins {
id 'com.android.application'
id 'kotlin-android'
}
ext {
geckoviewChannel="arm64-v8a"
geckoviewVersion="100.0.20220425210429"
}
...
...
android {
compileSdkVersion 33
...
...
// Note: compileOptions is only required for minSdkVersion < 24
compileOptions {
// sourceCompatibility JavaVersion.VERSION_1_8
// targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11 // 若採用這個, 則 compileSdkVersion 要指定 33(含) 以上
targetCompatibility JavaVersion.VERSION_11
}
}
...
...
repositories {
maven {
url "https://maven.mozilla.org/maven2/"
}
}
...
...
dependencies {
...
...
// mozilla geckoview
implementation "org.mozilla.geckoview:geckoview-${geckoviewChannel}:${geckoviewVersion}"
}
</pre>
<div><br /></div>
如果有出現類似</div><div><br /></div><div><span style="background-color: red; color: white; font-family: courier;">Build was configured to prefer settings repositories over project repositories but repository '???' was added by build file 'build.gradle' </span></div><div><span style="font-family: courier;"><br /></span></div><div><span style="font-family: courier;">的錯誤訊息,可能是您的專案套用了 <span style="background-color: black;"><span style="color: #fcff01;">Gradle6.8</span></span> 的新規定,那就修改 <span style="background-color: black;"><span style="color: #fcff01;">settings.gradle</span></span> ,將 <span style="background-color: black;"><span style="color: #fcff01;">dependencyResolutionManagement</span></span> 這個區塊 註解 或 刪除,我是去參照其他舊專案,只留下 2 列,如下:</span></div><div><span style="font-family: courier;"><br /></span></div>
<div><span style="background-color: black; color: #fcff01; font-family: courier;">settings.gradle</span></div>
<pre class="prettyprint lang-xml linenums">
rootProject.name = "專案名"
include ':app'
</pre>
<div><br /></div><div><br /></div><div><br />
<div><span style="background-color: black; color: #fcff01; font-family: courier;">AndroidManifest.xml</span></div>
<pre class="prettyprint lang-xmllinenums">
<manifest ... >
<uses-permission android:name="android.permission.INTERNET" />
...
</manifest>
</pre>
<br />
<br />
<div><span style="background-color: black; color: #fcff01; font-family: courier;">activity_main.xml</span><br /></div>
<pre class="prettyprint lang-xml linenums">
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<org.mozilla.geckoview.GeckoView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/geckoview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
tools:context=".MainActivity" />
</RelativeLayout>
</pre>
<br />
<br /><span style="background-color: black; color: #fcff01; font-family: courier;">
MainActivity.kt
</span><pre class="prettyprint lang-java linenums">
...
...
import org.mozilla.geckoview.GeckoRuntime;
import org.mozilla.geckoview.GeckoSession;
import org.mozilla.geckoview.GeckoView;
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var view:GeckoView = findViewById(R.id.geckoview)
val session = GeckoSession()
val runtime = GeckoRuntime.create(this)
session.contentDelegate = object : ContentDelegate {}
if(runtime==null) {
runtime = GeckoRuntime.create(this)
}
session.open(runtime)
view.setSession(session)
session.loadUri("https://tw.yahoo.com")
}
}
</pre>
</div>
<div><br /></div>
<div><br /></div>
在<span style="font-family: courier;"> ZenFon5 </span>跑的反應速度有點慢,且編譯出來的 <span style="font-family: courier;">apk</span> 頗大 -- <span style="font-family: courier;">139MB</span> !! 不過呈現的畫面的確較 <span style="font-family: courier;">webview</span> 細緻。老灰鴨http://www.blogger.com/profile/07842107154192311741noreply@blogger.com0