2022年5月5日 星期四

MySQL(MariaDB) INSERT INTO ... ON DUPLICATE KEY UPDATE,只更新符合條件的資料

INSERT INTO ... ON DUPLICATE KEY UPDATE 沒辦法用 WHERE 條件過濾出要更新的資料,
但要更新的值,可以用 IF 條件來控制。
讓不欲更新的資料,用原值更新,效果便猶如沒更新過,而只更新其他資料。

[範例]
一資料表test,三個欄位 id(主鍵)、aa(int)、bb(int)。
新增(INSERT INTO)資料時,若資料(id)已存在,則更新 aa、bb 兩欄位資料,
但若原本的 bb 資料小於 100,則不更新 bb:
INSERT INTO test (id, aa, bb)
  VALUES (8, 123, 456)
ON DUPLICATE KEY UPDATE
  aa = VALUES(aa),
  bb = IF(bb<100, bb, VALUES(bb));


[注意]
如果有一筆資料(id, aa, bb) => (10, 1, 2)
INSERT INTO test (id, aa, bb)
  VALUES (10, 100, 200)
ON DUPLICATE KEY UPDATE
  aa = VALUES(aa),
  bb = aa;
得到的結果是 (id, aa, bb) => (10, 100, 100)

將最後兩行順序調換:
INSERT INTO test (id, aa, bb)
  VALUES (10, 100, 200)
ON DUPLICATE KEY UPDATE
  bb = aa,
  aa = VALUES(aa);
得到的結果是 (id, aa, bb) => (10, 100, 1)

可發現,順序不同,結果不同
=> ON DUPLICATE KEY UPDATE 若要取其他欄位的原值,須排在其他欄位修改之前。



所以最初的例子「若原本的 bb 資料小於 100,則不更新 bb」,
如果改成「若原本的 bb 資料小於 原本aa,則不更新 bb」,
則須注意 aa、bb 修改的順序,避免比較到 aa 修改後的值:
INSERT INTO test (id, aa, bb)
  VALUES (8, 123, 456)
ON DUPLICATE KEY UPDATE
  bb = IF(bb<aa, bb, VALUES(bb)),
  aa = VALUES(aa);



參考:



2022年5月3日 星期二

Andriod 使用 WebView 內嵌網頁簡易範例

  1. 新增一個 Empty Activity 專案


  2. AndroidManifest.xml 加網路權限
    <uses-permission android:name="android.permission.INTERNET" />
    最終 AndroidManifest.xml  內容:
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.testwebview">
        <uses-permission android:name="android.permission.INTERNET" />
        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/Theme.TestWebView">
            <activity
                android:name=".MainActivity"
                android:exported="true">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
    
    </manifest>

  3. 開啟 layout 檔案 activity_main.xml,刪掉預設的 TextView(Hello World!),放一個 WebView
    設定 id 為「myWebView」,layout_width、layout_height 屬性設為「0dp」
    (使用專案預設的 ConstraintLayout)


    WebView 屬性設定往下拉,找到 All Attributes 裡的 layout_constraints 設定
    layout_constraintBottom_toBottomOf、layout_constraintEnd_toEndOf、layout_constraintStart_toStartOf、layout_constraintTop_toTopOf 都設為「parent」

    最終 activity_main.xml 內容:
    <?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">
    
        <WebView
            android:id="@+id/myWebView"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>

  4. MainActivity.java 內容
    package com.example.testwebview;
    
    import androidx.appcompat.app.AppCompatActivity;
    
    import android.os.Bundle;
    import android.webkit.WebSettings;
    import android.webkit.WebView;
    import android.webkit.WebViewClient;
    
    public class MainActivity extends AppCompatActivity {
    
        WebView mWebView;
        WebSettings mWebSettings;
        //Android 9 (API level 28) 後,預設只能開啟 https 網站
        String url = "https://google.com/";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            //mWebView = (WebView) findViewById(R.id.myWebView);
            mWebView = findViewById(R.id.myWebView);
            mWebSettings = mWebView.getSettings();
            //mWebSettings.setUserAgentString("app");
            mWebSettings.setJavaScriptEnabled(true);//開啟JS
            mWebSettings.setDomStorageEnabled(true);//開啟localStorage
            mWebSettings.setJavaScriptCanOpenWindowsAutomatically(true);
    
            //若沒設定setWebViewClient(),則點WebView頁面的連結會呼叫系統瀏覽器開啟,不會保留在APP中
            mWebView.setWebViewClient(new WebViewClient());
            mWebView.loadUrl(url);
        }
    }

  5. 去除 APP 上方的 ActionBar 標題列,修改 res\values\themes.xml、res\values-night\themes.xml,改成 NoActionBar
    themes.xml
    <resources xmlns:tools="http://schemas.android.com/tools">
        <!-- Base application theme. -->
        <style name="Theme.TestWebView" parent="Theme.MaterialComponents.DayNight.NoActionBar">
            <!-- Primary brand color. -->
            <item name="colorPrimary">@color/purple_500</item>
            <item name="colorPrimaryVariant">@color/purple_700</item>
            <item name="colorOnPrimary">@color/white</item>
            <!-- Secondary brand color. -->
            <item name="colorSecondary">@color/teal_200</item>
            <item name="colorSecondaryVariant">@color/teal_700</item>
            <item name="colorOnSecondary">@color/black</item>
            <!-- Status bar color. -->
            <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
            <!-- Customize your theme here. -->
        </style>
    </resources>
    themes.xml(night)
    <resources xmlns:tools="http://schemas.android.com/tools">
        <!-- Base application theme. -->
        <style name="Theme.TestWebView" parent="Theme.MaterialComponents.DayNight.NoActionBar">
            <!-- Primary brand color. -->
            <item name="colorPrimary">@color/purple_200</item>
            <item name="colorPrimaryVariant">@color/purple_700</item>
            <item name="colorOnPrimary">@color/black</item>
            <!-- Secondary brand color. -->
            <item name="colorSecondary">@color/teal_200</item>
            <item name="colorSecondaryVariant">@color/teal_200</item>
            <item name="colorOnSecondary">@color/black</item>
            <!-- Status bar color. -->
            <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
            <!-- Customize your theme here. -->
        </style>
    </resources>




[其他修改]






參考:

Android Studio 新增 assets 資料夾

 [新增 assets 資料夾]
「app」(按右鍵)-> 「New」-> 「Folder」-> 「Assets Folder」


如果不修改資料夾位置,可直接按「Finish」建立資料夾(預設位置在 src/main/assets/ )


 [存取 assets 資料夾,使用的路徑]
使用 file:///android_asset/ 路徑存取,例如 assets 資料夾內有一個 test.html 檔案。

mWebView.loadUrl("file:///android_asset/test.html");




參考:


2022年5月2日 星期一

Windows Defender 防火牆開啟 log

環境:Windows 10
Windows Defender 防火牆,預設沒有開啟 log 紀錄,
有時遇到無法連線時,看不到防火牆 log,無法立即判斷是否被防火牆擋住了。


在 Windows Defender 監視畫面中,可知道 log 檔位置,不過「記錄丟棄的封包」、「記錄成功的連線」預設沒開啟。





以下將「丟棄的封包」log 紀錄開啟:
點選「Windows Defender 防火牆內容」


選擇目前使用的網路設定檔(網域、私人、公用),再於紀錄項目,選擇「自訂」


將「紀錄丟棄的封包」,改為「是」




之後便可以在紀錄檔看到被防火牆阻擋的紀錄:  
#Version: 1.5
#Software: Microsoft Windows Firewall
#Time Format: Local
#Fields: date time action protocol src-ip dst-ip src-port dst-port size tcpflags tcpsyn tcpack tcpwin icmptype icmpcode info path
2022-04-28 22:53:25 DROP TCP 192.168.0.10 192.168.0.11 40770 9000 60 S 3427311517 0 29200 - - - RECEIVE


參考:


2022年4月27日 星期三

讓 Android Studio 編譯提示訊息,明確指出使用到已棄用API的程式位置

[情況]
Android Studio 編譯時,提示程式使用到已棄用的API,但沒指出哪邊使用到,另外提示加上 -Xlint:deprecation 後重新編譯,可取得更詳盡訊息。
Note: ... MainActivity.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.



[解決方式]
Android Studio 編譯加 -Xlint:deprecation 的方式,
開啟 build.gradle,加上
allprojects {
    gradle.projectsEvaluated {
        tasks.withType(JavaCompile) {
            options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
        }
    }
}
修改 build.gradle 後,會詢問將專案同步「Sync Now」,按「Sync Now」同步。


重新編譯,這次的提示訊息,明確指出哪邊使用到已棄用的API了
... MainActivity.java:16: warning: [deprecation] Handler() in Handler has been deprecated
        new Handler().postDelayed(new Runnable() {
        ^
1 warning

原來是 Handler() 這邊有問題
new Handler().postDelayed(new Runnable() {
    @Override
    public void run() {
        .....
    }
}, 2000); 

查了一下 Handler() 用法,新版寫法須顯性指定 Looper
加上 Looper.getMainLooper() 即可:
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
    @Override
    public void run() {
        .....
    }
}, 2000); 



參考: