發表文章

目前顯示的是 2月, 2020的文章

設定ImageView圖片的幾種方式

直接設定Resource ID imageView.setImageResource(R.drawable.your_image); Uri直接設定 import android.net.Uri; Uri uri = 圖片/文件選取器取的Uri imageView.setImageURI(uri); 從Uri開啟檔案再設定 import android.net.Uri; import android.graphics.BitmapFactory; try {     InputStream inputStream = getContentResolver().openInputStream(uri);     imageView.setImageBitmap(BitmapFactory.decodeStream(inputStream)); } catch (FileNotFoundException fnf) { } uri如果是Content://這種的,基本上都需要取得READ_EXTERNAL_STORAGE權限。 從實際路徑載入檔案 import android.graphics.Bitmap; String path = 看你從哪裡拿來的路徑 Bitmap bmp = BitmapFactory.decodeFile(path); if (bmp != null) {     imageView.setImageBitmap(bmp); } 如果不是APP自己底下的檔案路徑,都需要取得READ_EXTERNAL_STORAGE權限。

長按ImageView開啟選取器取得圖片Uri

加入長按事件     private ImageView imageView; ... onCreate ...         imageView = findViewById(R.id.imageView);         imageView.setOnLongClickListener(new View.OnLongClickListener() {             @Override             public boolean onLongClick(View view) {                 pickFromGallery();                 return true;             }         }); 啟動圖片選取器     /**      * @ref https://androidclarified.com/pick-image-gallery-camera-android/      */     final static int GALLERY_REQUEST_CODE = 0x1000;     private void pickFromGallery() {         Intent intent = new Intent(Intent.ACTION_PICK); // launch gallery app         //Intent intent = new Intent(Intent.ACTION_GET_CONTENT); // launch document app, more complicate UI         intent.setType("image/*");         //String[] mimeTypes = {"image/jpeg", "image/png"};         //intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);         //intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);

筆記:github page

如果你要創建一個xyz.github.io的github page github註冊使用者xyz  創建一個repository名為xyz.github.io (選項)將branch取名為gh-pages (可以從Setting分頁選擇,已不是必要步驟) 將網頁圖文資料加入到該branch,並將分支上傳到github。 進入該repository的Settings分頁,選擇GitHub Pages要使用的branch (預設上傳完應該自動就選好了)。 瀏覽你的github page https://xyz.github.io 如果你有其他repository也是可以設定github page,只是預設會是xyz.github.io/respository_name 如果你有自己的CNAME,也可以在GitHub Pages上設定指到github page。 缺點: 只能放靜態網頁。 所有資料都是公開的。 有網站容量限制。 有流量及瀏覽量限制。 Github官方Steps https://help.github.com/en/github/working-with-github-pages/getting-started-with-github-pages 或參考龍哥的介紹 https://gitbook.tw/chapters/github/using-github-pages.html

筆記:分享圖片/文字/HTML到其他App

分享文字/圖片到其他App,例如FB、Line 以前分享文字 以前分享文字,自己寫intent,然後開啟Activity static Intent getSendTextIntent(String text) {     Intent intent = new Intent();     intent.setAction(Intent.ACTION_SEND);     intent.setType("text/plain");     intent.putExtra(Intent.EXTRA_TEXT, text);     return intent; } startActivity(getSendTextIntent("Hello, just do a test!")); 一般還會建議應該再判斷是否可以啟動Activity if (intent.resolveActivity(getPackageManager()) != null) {     startActivity(intent); } 現在分享文字 現在用ShareCompat就好了 import android.support.v4.app. ShareCompat ; 最新的JetPack是引用 import androidx.core.app. ShareCompat ; ShareCompat.IntentBuilder.from( MainActivity .this)         .setType("text/plain")         .setChooserTitle("Share Text")         .setText("Hello, just do a test!")         .startChooser(); 如果要Intent也是可以getIntent()取出來 Intent intent =     ShareCompat.IntentBuilder.from(MainActivity.this)     .s

取得最上層繪製權限

在SDK 23之前,最上層繪製是不需要取得權限的。 在SDK 23之後,最上層繪製需要於啟動系統設定頁處理。 canDrawOverlays用以確認是否可以最上層繪製。 這裡我曾在SONY手機Android 8.0上遇到一個坑。 APP取得權限後馬上使用canDrawOverlays,實際上是已經擁有權限,但是不論如何都無法獲得正確數值,必須關閉APP再重啟才可以確認已擁有權限,這點在網路上也有人分析是系統問題,必須升級系統才會正常。 static public Intent getDrawOverlayPermissionIntent (Context context) { Intent intent = null; if (Build.VERSION. SDK_INT >= 23 ) { intent = new Intent( Settings. ACTION_MANAGE_OVERLAY_PERMISSION , Uri. parse ( "package:" + context.getPackageName())) ; // Workaround: put extras for the key event not handled in Overlay Setting page. intent.putExtra( "extra_prefs_show_button_bar" , true ) ; // show the status bar intent.putExtra( "extra_prefs_show_skip" , false ) ; // skip button is gone intent.putExtra( "extra_prefs_set_next_text" , "" ) ; // next button is gone intent.putExtra( "extra_prefs_set_back_text" , "

彙整一些取得Android Intent的方便函數

取得Android Home,Launcher2, Launcher3(Androi 7.1/TV), 設定頁, Wifi設定頁 static public Intent getAndroidHomeActivityIntent () { Intent intent = new Intent(Intent. ACTION_MAIN ) ; intent.addCategory(Intent. CATEGORY_HOME ) ; intent.addFlags(Intent. FLAG_ACTIVITY_NEW_TASK ) ; return intent ; } static public Intent getLauncher2ActivityIntent () { Intent intent = new Intent(Intent. ACTION_MAIN ) ; intent.setComponent(ComponentName. unflattenFromString ( "com.android.launcher/com.android.launcher2.Launcher" )) ; intent.addFlags(Intent. FLAG_ACTIVITY_NEW_TASK ) ; return intent ; } static public Intent getLauncher3ActivityIntent () { Intent intent = new Intent(Intent. ACTION_MAIN ) ; intent.setComponent(ComponentName. unflattenFromString ( "com.android.launcher3/com.android.launcher3.Launcher" )) ; intent.addFlags(Intent. FLAG_ACTIVITY_NEW_TASK ) ; return intent ; } static public Intent getSettingActivi

筆記:在heroku建立免費line bot

圖片
主要參考資料 使用Node.js在Heroku上架設Line機器人 https://medium.com/@wanwanyang/60ec3f6cac7f Line官方文件 https://developers.line.biz/en/docs/messaging-api/building-sample-bot-with-heroku/ 前置知識 git node.js ubuntu 1. 建立Line開發者帳號 從這裡 進入 ,可以用個人用的一般Line帳號登入,登入Developers console時會請我們再填入 名字 跟 E-mail 就照著填。 Line的帳號 分類 ,簡略來說就是 Reply和Push API對開發者不收費但有限制好友數目 免費版則只能用Reply API,也只能文字訊息 付費用Push API至少要到進階版(API) 收費3,888/月 【加值服務教學】升級方案、取消付款與確認購買狀態 http://at-blog.line.me/tw/archives/63198901.html#Q6 Line API差異 Reply API僅能回覆訊息,使用者一問機器人一答的情境,無法回多個訊息。 Push API就是Line bot可以主動發送訊息給加好友的使用者,或是分多次訊息發話。 2. 建立bot用的Channel並找出secret和token 依照Line 官方步驟 建立channel,從developers console中選擇"Create new provider"。 隨後channel類型選擇"Messaging API",並輸出各種channel的資訊即可完成建立。底下兩個方塊記得打勾。 可參考: https://developers.line.biz/en/docs/messaging-api/building-bot/ https://developers.line.biz/en/docs/messaging-api/building-sample-bot-with-heroku/ 2.1 找出Channel Secret 在Chan

line bot推送多訊息

1. 事前準備: (1) 取得對方的User ID 需請對方加bot好友。對方封鎖bot好友時,對方會收不到發送的訊息。 (2) 填入將User ID填入變數 取代下面程式碼的targetUID/TARGETUID的值。 (3) 設定環境變數 export CHANNEL_ACCESS_TOKEN=你的line bot的access token 2. 使用node.js發送 const targetUID = 'U1fcb...'; const line = require('@line/bot-sdk'); const client = new line.Client({   channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN, }); const msg1 = { type: 'text', text: 'Hello World1' }; const msg2 = { type: 'text', text: 'Hello World2' }; client.pushMessage(targetUID, msg1)   .then(() => {     console.log('send1 ok!');     client.pushMessage(targetUID, msg2)     .then(() => {       console.log('send2 ok!');     })     .catch((err) => {       console.error(err);     });   })   .catch((err) => {     console.error(err);   }); 2.1 執行範例 若上面程式碼命名為send.js則 node send.js 3. 使用CURL發送@Linux export TARGETUID=U1fcb... curl -v -X POST https://api.line.me/v2/bot/me

用CloudWatch定時產生Event丟檔案到S3

圖片
1. 建立S3 Bucket 建立後記得改後面的程式碼的var bucketName變數的值。 2. IAM新增Role  先在IAM新增一個role,連接AWSLambdaExecute政策,這個政策會包含可以在S3放檔案的權限。 3. 新增Lambda函數 選擇"從頭開始撰寫" 函數名稱,看你喜歡,我是設"myLambda" 執行時間,使用"Node.js 12.x" 選擇或建立執行角色,使用已存在角色,選擇步驟1所建立的Role。 放置Lambda程式碼 var AWS = require('aws-sdk'); var bucketName = '{your-bucket-name}'; function putFileToS3(key, body, callback) {     var s3 = new AWS.S3();     s3.putObject({Bucket: bucketName, Body: body, Key: key}, callback); } exports.handler = (event, context, callback) => {     console.log('my lambda');     console.log('Received event:', JSON.stringify(event, null, 2));     putFileToS3(event.id, JSON.stringify(event, null, 2),         (err, response) => {             if (err) {                 console.error(err);                 callback(err);             } else {                 console.log('put to s3 succeed, obj:' + event.id);                 callback(nul

筆記:放置AWS安全登入資訊及存取金鑰

圖片
在閱讀 AWS 文件 後,得知需建立 存取金鑰(Access key) ,要注意事後若忘記 金鑰, AWS 也不會再提供 ,只能從主控台中產生新的 金鑰 ,並廢止前一組 金鑰。 我們可以在安全登入資料( security credentials page) 中建立根密鑰( root secret key) ,這個設定可從 Identity and Access Management (IAM) 進入。 由於直接從安全登入資料所建立的存取金鑰 是沒有限制存取資源的,所以 官方 建議要先建立一個 IAM user ,再創建這個使用者使用的存取金鑰,以便 限制存取的資源。 我可以在 AWS IAM user 中新增一個 使用者,並給予 必要 權限以方便後續測試。創建使用者後,點進去使用者細節,就可以在分頁"安全登入資料" 中 創建存取金鑰 。 建立時會像下面這樣,記得要下載,不然之後AWS也不會再提供了。你只能建立新的 AWS 安全登入資訊應放置得位置 Linux使用者為 ~/.aws/credentials  Windows 使用者則為  C:\Users\{YOUR_USER_NAME}\.aws\credentials [default] aws_access_key_id = YOUR_ACCESS_KEY_ID aws_secret_access_key = YOUR_SECRET_ACCESS_KEY

Android.mk引用aar包

aar是Android Archive檔,在Anroid.mk中使用aar包有三步驟: 1. 先把這些aar模組設定成prebuilt 把aar包下載回來,假設aar包都放在lib目錄內,寫個獨立的Android.mk設為prebuilt include $(CLEAR_VARS) LOCAL_PREBUILT_STATIC_JAVA_LIBARRIES += {aar-package-name-1}:lib/{aar-file-name-1}.aar LOCAL_PREBUILT_STATIC_JAVA_LIBARRIES += {aar-package-name-2}:lib/{aar-file-name-2}.aar ... include $( BUILD_MULTI_PREBUILT ) 2. 表明加入需要使用aar包 LOCAL_STATIC_JAVA_AAR_LIBRARIES += {aar-package-name-1} LOCAL_STATIC_JAVA_AAR_LIBRARIES += {aar-package-name-2} ... 3. 並加入aar的class名稱 編譯程式才會找的到 LOCAL_AAPT_FLAGS += --auto-overlay LOCAL_AAPT_FLAGS += --extra-packages {aar-class-name-1} LOCAL_AAPT_FLAGS += --extra-packages {aar-class-name-2} ...

範例:Android.mk編譯/執行Java程式(非APP)

1. Java程式碼 一個非常簡單的Java程式,放置在src/com/test/demo/HelloWorld.java package com.test.demo; public class HelloWorld {     public static void main(String[] args){         System.out.println("Hello World");     } } 2. 執行Java程式的sh檔 #!/system/bin/sh base=/system export CLASSPATH=$base/framework/JProg.jar exec app_process $base/bin com.test.demo.HelloWorld "$@" 這個執行程式的sh檔案,其實是參照/system/bin/am所做的,依照你的需求可能會需要包含framework.jar 最後一行的com.test.demo.HelloWorld是隨著你的程式碼而改變的 3. 編譯用的Android.mk檔案 LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_MODULE := JProg ALL_DEFAULT_INSTALLED_MODULES += $(LOCAL_MODULE) include $(BUILD_JAVA_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := runJProg ALL_DEFAULT_INSTALLED_MODULES += $(LOCAL_MODULE) LOCAL_SRC_FILES := runJProg LOCAL_MODULE_CLASS := EXECUTABLES LOCAL_MODULE_TAGS := optional include $(BUILD_PREBUILT) 會編譯出一個jar檔JProg.jar和runJProg 4. 在目標裝置

Android.mk加成必須編譯模組

在你的必須編譯模組的LOCAL_MODULE定義後面,加入這行 ALL_DEFAULT_INSTALLED_MODULES += $(LOCAL_MODULE) Android.mk是會找相依性,跟系統相關的才會納入編譯,你可以透過在base.mk中加入必須的相依性,但是我們總是會覺得應該跟模組本身寫在一起

用ffmpeg比較壓縮前後PSNR/SSIM

0. ffmpeg壓縮範例 ffmpeg -i movie.mp4 -c:v libx264 -crf 30 -an -sn compressed.mp4 如果用yuv檔當輸入,需要先給定影片長,寬,格式,幀率,建議先用ffplay測試過, 如果正常播放yuv的指令是: ffplay -s 1280x720 -pix_fmt yuv420p -framerate 30 -i uncompress.yuv 4.4.2之後-s以被設為deprecated, 要改用-video_size 那用ffmpeg壓縮就是改成 ffmpeg  -s 1280x720 -pix_fmt yuv420p  -framerate 30  -i uncompress.yuv \ -c:v libx264 -crf 30 -an -sn compressed.mp4 其他格式影片轉出yuv檔案可以參考[1][2] 1. 比較影片PSNR ffmpeg -i  movie.ts  -i compressed.ts  -lavfi psnr="stats_file=psnr.log" -f null - 參考資料[3]有提出與其他工具結果不一致的問題,建議改為[4]提出的指令,改寫如下: ffmpeg -i movie.mp4 -i compressed.mp4 -lavfi '[0:v]setpts=PTS-STARTPTS[v0];[1:v]setpts=PTS-STARTPTS[v1];[v0][v1]psnr=psnr.log' -f null - 螢幕會輸出整體的比較結果 frame= 1921 fps=254 q=-0.0 Lsize=N/A time=00:01:05.33 bitrate=N/A speed=8.64x  video:1006kB audio:12108kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown [Parsed_psnr_0 @ 0x2e1b480] PSNR y:17.903124 u:31.221207 v:29.559965 average:19.541083 min

筆記:gdb debug指令概要

1. b/break設定中斷點 1.1 針對function名稱設定中斷點b func_name gdb內可以用TAB的auto completion,預設最多顯示200筆資料 (gdb) b avpriv_mpegts avpriv_mpegts_parse_close   avpriv_mpegts_parse_open    avpriv_mpegts_parse_packet (gdb) b avpriv_mpegts_parse_packet Breakpoint 1 at 0x749540: file libavformat/mpegts.c, line 3369. 1.2 針對特定source code行數設定中斷點b file_name:line_num (gdb) b libavformat/mpegts.c:3163 Breakpoint 2 at 0x7451a7: file libavformat/mpegts.c, line 3163. 1.3 針對目前source code設定中斷點b line_num (gdb) b 300 Breakpoint 2 at 0x4971a7: file fftools/ffprobe.c, line 300. 如果你還沒有執行程式,b 300的意思是設定main程式所在檔案的第300行 例如ffprobe的main程式在fftools/ffprobe.c, 就會設定在ffprobe.c的300行 1.4 針對當前位置設定中斷點 b (gdb) b 1.5 顯示中斷點i b/info break (gdb) i b Num     Type           Disp Enb Address            What 1       breakpoint     keep y   0x0000000000749540 in avpriv_mpegts_parse_packet at libavformat/mpegts.c:3369 2       breakpoint     keep y   0x00000000007451a7 in mpegts_read_header at libavformat/mpegts.c:3163

筆記:app當機時用gdb遠端debug

參考來源: Debugging apps or processes that crash https://source.android.com/devices/tech/debug/gdb 1. 在目標裝置上設置debug.debuggerd.wait_for_gdb Android 7之後可以使用內建功能, 首先設置debuggerd的property $ adb shell setprop debug.debuggerd.wait_for_gdb true 2. 模擬APP當機 使用ps確定測試用APP是com.myapp.test $ adb shell "ps | grep com.myapp.test" 產生SIGABRT信號模擬APP當機 $ adb shell kill -6 2823 從adb logcat中可以看到下面一段debuggerd印出的dump資訊, 有看到下面 藍色部份 就是表示系統已正在等待gdb連線 --------- beginning of crash 02-13 15:45:52.820  2823  2823 F libc    : Fatal signal 6 (SIGABRT), code 0 in tid 2823 (ht.mod.watchdog) 02-13 15:45:52.821  1650  1650 W         : debuggerd: handling request: pid=2823 uid=1000 gid=1000 tid=2823 02-13 15:45:52.923  3190  3190 F DEBUG   : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 02-13 15:45:52.924  3190  3190 F DEBUG   : Build fingerprint: '...:user/release-keys' 02-13 15:45:52.924  3190  3190 F DEBUG   : Revision: '0' 02-13 15:45:52.924  3190  3190 F DEB

筆記:gdb透過網路debug目標裝置

1. 於目標裝置置入gdbserver 以AOSP為例, 在prebuilts目錄下有已經編譯好的gdbserver版本(64-bit要找gdbserver64), $ find -name gdbserver prebuilts/misc/android-arm/gdbserver prebuilts/misc/android-arm/gdbserver/gdbserver ... 我手邊裝置是arm,從本機用adb push把gdbserver置入目標裝置中即可 adb push prebuilts/misc/android-arm/gdbserver/gdbserver /system/bin/ 如果push不上去,應該是read-only狀態,需要先adb remount後再adb push上去 2. 啟動目標裝置上的gdbserver 啟動gdbserver有兩種不太一樣的debug模式如下: Debug情境1. debug已在跑的Process 例如:假設已用ps確認要debug已在跑的程式recorder之PID為2947。 # ps | grep recorder root      2947  2939  40352  11380 hrtimer_na b2656f50 S recorder 在目標裝置上執行gdbserver在port 9999等待gdb連線,並附掛上指定PID為2947之程式 # gdbserver :9999 --attach 2947 Attached; pid = 2947 Listening on port 9999 Debug情境2. 從啟動程式開始debug 例如:在目標裝置上用gdbserver直接啟動待debug的程式recorder,並帶入啟動參數http://www.google.com, 在port 9999等待gdb連線近來 # gdbserver :9999 recorder http://www.google.com Process recorder created; pid = 3039 Listening on port 9999 注意:程式啟動參數無法從本機電腦端(gdb)給予,請記得在目標裝置端( gdbserver )給定 3.