2020年3月4日 星期三

用 grub2 boot loader 啟動 LMDE4

LMDE4 是 Linux Mint Debian edition 4的縮寫, 可以上官網去下載 iso 檔(https://linuxmint.com/rel_debbie.php), 開機時用 grub2 啟動作業系統, 只要修改 grub.cfg, 針對 Debian linux 只要修改啟動選項加入 boot=live 及 findiso(相較於  Ubuntu Linux使用的啟動選項是 boot=casper 及 iso-scan/filename), 就能直接啟動 iso 封裝的 linux , 例如以下範例, 加入一個啟動項目(menuentry):
menuentry "Boot LMDE4 iso" {
    set root=(hd0,2)
    set iso=/boot/lmde4.iso
    loopback loop $iso
    linux (loop)/live/vmlinuz    boot=live     findiso=$iso
    initrd (loop)/live/initrd.lz
}

2020年2月19日 星期三

dart 使用 ffi 和 c 語言溝通

參考官網文章: https://dart.dev/guides/libraries/c-interop
1. 使用 linux 作業系統, 安裝好dart sdk ,  c 語言編譯程式加上  cmake
2. 新增一個 dart 專案,  專案內新增一個 hello 目錄 , 進入 hello 目錄, 準備要編譯成  hello 模組(libhello.so):
2.1 先在 hello 目錄裡面編輯一個檔案 CMakeLists.txt:
cmake_minimum_required(VERSION 3.7 FATAL_ERROR)
project(hello_library VERSION 1.0.0 LANGUAGES C)
add_library(hello_library SHARED hello.c hello.def)
add_executable(hello_test hello.c)
set_target_properties(hello_library PROPERTIES
    PUBLIC_HEADER hello.h
    VERSION ${PROJECT_VERSION}
    SOVERSION 1
    OUTPUT_NAME "hello"
    XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Hex_Identity_ID_Goes_Here"
)

2.2. 為了指示要將函數(testWork, add, reverse 等等) 導出來(export)使用, 要編輯一個文字檔案 hello.def:
LIBRARY   hello
EXPORTS
   testWork
   add
   reverse

2.3. 編輯一個 c 程式,用來宣告函式原型的標頭檔 hello.h:
void testWork( );
int  add(int a, double b);
char *reverse(char *str);

2.4. 編輯 c 模組程式的原始碼  hello.c:
#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include "hello.h"
int main( ) {
    testWork( );
    printf("3 + 5 = %d\n", add(3, 5));
    printf("%s\n",reverse("Hello-String") );
    return 0;
}
void testWork( ) {
    printf("It works!\n");
}
int add(int a, double b) {
    return a + b;
}
char *_bufferPointer = NULL;
int _buffrLength = 0;
char *reverse(char *src) {
    int size = strlen(src);
    if(size == 0) return NULL;
    else if(size > _buffrLength) { // allocate if space not enough
        if(_bufferPointer != NULL) free(_bufferPointer);
        _bufferPointer = (char *)malloc(size + 1);//from heap
        _buffrLength = size;// maximum characters to hold
    }
    _bufferPointer[size] = '\0';// eos
    for (int i = 0; i < size; i++) _bufferPointer[size - 1 - i] = src[i];// reverse
    return _bufferPointer;
}

2.5 上述檔案都編輯完成後就能執行 make 作業, 製作成動態程式庫 (hello/libhello.so)
   cmake  .
   make

3. 在專案內新增 pubspec.yaml 檔案, 完成後, 開啟終端機執行  pub get 準備使用 ffi:
name: cinterop
version: 0.0.1
description: >-
  An example of calling C code from Dart through FFI
author: MyName<email@google.com>
environment:
  sdk: '>=2.6.0 <3.0.0'
dependencies:
  ffi: ^0.1.3
dev_dependencies:
  test: ^1.5.3

4.編輯一個透過 ffi  界面呼叫 c 語言的 dart 範例程式 main.dart:
import 'dart:ffi';
import 'package:ffi/ffi.dart';
main( ) {
  final libso  = DynamicLibrary.open('hello/libhello.so');
  final testFun= libso.lookup<NativeFunction<Void Function( )>>('testWork').
                 asFunction<void Function()>();
  final addFun = libso.lookup<NativeFunction<Int32 Function(Int32, Double)>>('add').
                 asFunction<int Function(int,double)>( );
  final revFun = libso.lookup<NativeFunction<Pointer<Utf8> Function(Pointer<Utf8>)>>('reverse').
                 asFunction<Pointer<Utf8> Function(Pointer<Utf8>)>( );
  final c2dartString   = (Pointer<Utf8> _pointer) => Utf8.fromUtf8(_pointer);
  final dartstring2c   = (String _string) => Utf8.toUtf8(_string);
  final reverse = (String _string) => c2dartString(revFun(dartstring2c(_string)));
  testFun( );
  print('adder: 334 + 5 = ${addFun(334, 5.0)}');
  for(int i=0; i<20 ; i++)   print('${reverse("$i.Hello-string")}');
}

5. 用  dart VM 執行上述 main.dart 看看:  /pathToDartSDK/bin/dart   main.dart
It works!
adder: 334 + 5 = 339
gnirts-olleH.0
gnirts-olleH.1
gnirts-olleH.2
gnirts-olleH.3
gnirts-olleH.4
gnirts-olleH.5
gnirts-olleH.6
gnirts-olleH.7
gnirts-olleH.8
gnirts-olleH.9
gnirts-olleH.01
gnirts-olleH.11
gnirts-olleH.21
gnirts-olleH.31
gnirts-olleH.41
gnirts-olleH.51
gnirts-olleH.61
gnirts-olleH.71
gnirts-olleH.81
gnirts-olleH.91

後記: dart SDK 的 dart2native 可以將原始碼轉成可執行檔, 不用 dart VM, 直接就能執行:
/pathToDartSDK/bin/dart2native  main.dart  &&   ./main.exe

2020年2月7日 星期五

Javascript 讀取 blob

Firefox 69 或是 Chrome 76 以後的版本, blob 透過擴展函數 arrayBuffer( ) 可以取得其中的內容, arrayBuffer( ) 可以用在像是透過 webSocket 取得的 blob 資料, 或是放入陣列中(_blobArray)再用 Blob 建構式產生的新 blob, 若是舊版本只能透過 FileReader 去讀取, 以下程式碼搭配 Promise 物件模擬 arrayBuffer 的讀取行為:
        // arrayBuffer reader
        async  function  blobReader(_blobArray) {
            const blobTemp = new Blob(_blobArray);
            if( blobTemp.arrayBuffer )  return await blobTemp.arrayBuffer( );
            else { // use FileReader & Promise to read
                    var _ready = null;
                    const  _arrayBuffer = new Promise((_resolv, _) => _ready=_resolv);
                    const  _reader = new FileReader( );
                    _reader.onloadend = ( ) => _ready && _ready(_reader.result);
                    _reader.readAsArrayBuffer(blobTemp);
                   return await  _arrayBuffer;
           }
        }

2020年2月5日 星期三

Javascript 使用 html5 的 MediaSource 播放器

Chrome 與 Firefox 都支援 HTML5 的 MediaSource, 但餵進去的資料必須是 fmp4 格式的資料結構, 伺服器可以透過 mp4fragment 將 mp4 檔案一一轉換成 fmp4 格式檔再傳給客戶端. 客戶端的播放程式(javascript)利用  html 的 video 標籤播放該影音檔, 將以下程式碼存成 html 文字檔, 放在伺服器讓客戶端(Chrome 或是  Firefox)來連線:

<html><head><meta charset='utf-8'></head><body><script>
const codec = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';
class HttpPlayer {
    constructor (width, height) {
        const video  = document.createElement('video');
        const mediaSource = new MediaSource( );
        mediaSource.onsourceopen = _evt => {
            var  completeFile = null;
            var  number  = 1; // total number to get
            const sourceBuffer = mediaSource.addSourceBuffer(codec);
            const httpGet = _filename => {
                const url = `https://${location.origin.split("//").pop( )}${_filename}`;
                const https = new XMLHttpRequest( );
                https.responseType = 'arraybuffer';
                https.onload = ( ) => sourceBuffer.appendBuffer(https.response);
                https.onloadend = ( ) => completeFile = _duration => {
                    completeFile = null;
                    number -- <= 0 ?  mediaSource.endOfStream( ) : (( ) => {
                        sourceBuffer.timestampOffset = _duration - 2;
                        httpGet("/streamN.fmp4"); // next fmp4
                    })( );
                }
                https.open('get', url);
                https.send( );
            }
            sourceBuffer.onupdateend = _evt =>
                completeFile && completeFile(mediaSource.duration);
            httpGet("/stream0.fmp4"); // initial fmp4
        }
        video.width    = width;
        video.height   = height;
        video.muted    = true;
        video.controls = "controls";
        video.autoplay = "autoplay";
        video.src = URL.createObjectURL(mediaSource);
        document.body.append(video);
    }
};
new HttpPlayer(1024, 768);
</script></body></html>

2019年12月25日 星期三

用 android ndk 編譯 bento4

bento4 官方網站原始碼: https://www.bento4.com/downloads/
android ndk官方網站: https://developer.android.com/ndk/downloads
到上述官網下載 bento4 原始碼, 及程式編譯工具 android ndk, 分別將它解壓縮. 例如我將它解壓縮, 另外再改名, 分別放在目錄:
    export  path_to_bento4=/home/mint/Downloads/bento4
    export  path_to_ndk=/home/mint/Downloads/ndk20

1. 稍微修改 bento4 原始碼的 Ap4Config.h, (位在 $path_to_bento4/Sourece/C++/core/Ap4Config.h), 找出並修改 AP4_fseek 及 AP4_ftell 的定義:
     #define   AP4_fseek   fseek
     #define   AP4_ftell   ftell

1. 確認自己下載的 ndk 版本(最少要 24), 現在用 29, 用來設定 ANDROID_PLATFORM 環境變數, 目的是讓 AP4_fseek 及 AP4_ftell 支援 64 位元的 ftello , fseeko, 若只使用 ftell 及 fseek 則開啟檔案只能支援 32 位元長度 2^32 = 4G

2. 在 linux shell 命令列模式要先設定好 ndk 的環境變數:
    export ANDROID_NDK=$path_to_ndk
3. 切換到 betnto4 主目錄, 首先用 cmake 產生 CMakeLists.txt, 接著用 make 下指令(-j3 用 3 個 cpu 加速編譯速度), 讓 ndk 開始編譯 c 程式:
     cd  $path_to_bento4  &&  mkdir  build  &&  cd  build
     cmake  -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake   -DANDROID_PLATFORM=29   ..
     make -j3

4. 許多有用的工具程式都已編譯成 android 下可直接執行的 binary 檔, 放在目錄:
    $path_to_bento4/build
像是 mp4Fragment 可以將  mp4 轉換為 fmp4. fmp4 格式檔是一個可以透過  javascript 餵給 html5 MediaSource 播放的片段串流影音檔:
  mp4Fragment path_to_source.mp4 path_to_taget.fmp4
5.在 Android app 透過一些技巧, 例如將上述編譯完的執行檔包在 assets, 執行 app 時再將它解開,放進 app cache 目錄內. 透過 ProcessBuilder 修改成可執行權限, 就像在終端機執行一樣, http 伺服器再將轉成的 fmp4 透過網路傳給客戶端, 就成了名符其實的媒體串流伺服器 app.

2019年11月7日 星期四

實現簡單的 Motion JPEG 改用 WebSocket

1. 伺服器透過 http 連線將連線資料(html 檔案), 送給客戶端
2. 客戶端收到內含 Javascript 可以開啟 WebSocket 連線的 html 檔案
3. 伺服端收到 WebSocket 升級請求, 連線後,依照時間順序,不停將 JPEG (binary)傳給客戶端.
4. 客戶端的 Javascript, 從 WebSocket 收到的 JPEG 資料, 製成 url, 將圖貼至 canvas 就可以了
放在伺服器上的 mjpeg.html 客戶端程式範例:
<html><head><meta charset='UTF-8'></head>
<body><script>
class ViewImageWS {
    constructor(width, height) {
        this._canvas = document.createElement('canvas');
        this._canvas.width  = width;
        this._canvas.height = height;
        this._canvas.style.setProperty('border', '1px solid #000000');
        this._context = this._canvas.getContext('2d');
        this._height = this._canvas.height;
        this._width  = this._canvas.width;
        document.body.append(this._canvas);
    }
    set drawImage (_img) {
        const scaley = this._height/_img.height;
        const scalex = this._width/_img.width;
        this._context.save( );
        this._context.scale(scalex, scaley);
        this._context.drawImage(_img, 0, 0, this._width, this._height);
        this._context.restore( );
    }
    set host(_ip) {
        const ws = new WebSocket(`ws://${_ip}`);
        ws.onmessage = _jpg => {// jpg binary
            const url = URL.createObjectURL(new Blob(
                [_jpg.data],
                {type: 'image/jpeg'}
            ));
            const img = new Image( );
            const release = ( )=> {
                URL.revokeObjectURL(url);
                img.remove( );
            }
            img.style= 'transform:rotate(90deg);';// rotate 90 deg if need
            img.onerror = release;
            img.onload = ( ) => {
                this.drawImage = img;
                release( );
            };
            img.src = url;
        }
    }
};
var viewer  = new ViewImageWS(1024, 768);
viewer.host = location.origin.split('//').pop( );
</script></body>
</html>

2019年10月30日 星期三

Dart 與 Java 資料互傳


1. 陣列(array)資料結構 Dart 端稱為 Uint8List, Java 端稱為 ByteArray, 簡寫為 byte[ ]
2. 緩衝(buffer)區資料結構 Dart 端稱為 ByteData, Java 端稱為 ByteBuffer, Dart 似乎是將ByteBuffer  脫去外殼,留下裡面的 ByteArray,  用屬性 buffer 連結過去, 因此當 dart 端使用到byteData.buffer 時, 所指的內容其實就是 ByteArray. 當然還是得透過方法 Uint8List.view(byteData.buffer) 將裡面的 byteData.buffer (也就是ByteArray)轉成Uint8List 相容的資料結構.用 ByteBuffer 好處是除了可以用索引修改陣列的內容外, 也不需將陣列空間全數複製過去, 而是將連結傳送過去, 免除 copy 效率不佳的問題.
3. Dart 端透過 defaultBinaryMessenger 與 Java 端的 BinaryMessenger 用 binary 資料相互傳遞訊息, 所用就是緩衝區的資料結構來溝通, 實驗發現在 Java 端似乎只能用 ByteBuffer.allocateDirect(length) 或是 ByteBuffer.allocate(length) 分配記憶空間, 資料才能送的過去. 若是用 ByteBuffer.wrap(byte[ ]) 的方式包裝的陣列空間, 傳送似乎有問題, 詳細原因不明.
4. 若是要直接傳送原始的 binary 資料,  Java 端另外還能透過 EventChannel 將 ByteArray 直接傳到  Dart 端, 其收到的就是 Uint8List 也就是 ByteArray (byte[ ],無需再另外轉換.
5. 但若用 EventChannel 方式將 ByteBuffer.array( )當成 ByteArray 傳給 Dart, 會發現長度比原先用 allocate 分配 ByteBuffer 時的記憶空間, 整整少了 7 個位元組(bytes), 推測原因可能是 Big Endian 與 Little Endian 關係, 或是他們的資料結構儲存方式,本就不同, 尚待查證.

2019年10月24日 星期四

用 Javascript 實現簡單的 motion jpeg

伺服器上先準備好 jpeg 格式檔: mjpeg0, mjpeg1, mjpeg2  ... 等, 再寫一個 mjpeg.html 及一個 mpeg.js 檔案, mjpeg.html 範例:
<html><head><meta charset='utf-8'></head>
<body> <script type='application/javascript' src='mjpeg.js'></script></body>
</html>
底下是一個 Javascript 檔案 mjpeg.js 範例程式, 全部放到 http 伺服器同 jpeg 檔案的附錄內,  讓它執行, 用瀏覽下抓上述 mjpeg.htm, 就會定時從伺服器下載  jpeg 檔, 再繪製貼圖到 canvas, 這就是一個超簡單又名符其實的 motion jpeg 伺服器:
class MjpegViewer {
    constructor(fps, width, height) {
      this._fps     = fps;
      this._running = false;
      this._canvas  = document.createElement('canvas');
      this._canvas.width  = width;
      this._canvas.height = height;
      this._canvas.style.setProperty('border', '1px solid #000000');
      document.body.append(this._canvas);
    }
    get _duration( ){ return  0 < this._fps && this._fps < 32 ? 1000/this._fps : 100 }
    get _context( ) { return this._canvas.getContext('2d')  }    
    get stop( ) { this._running = false  }
    set host(_ip)  {
        var _counter  = 0;
        this._running = true;
        const _period = this._duration;
        const _draw = (img)=> {  this._context.drawImage(img, 0, 0) }
        const _periodRun = ( )=> {
            if(! this._running) return;
            const _img = document.createElement('img');
            _img.onerror = ( )=> { _img.remove( )  }
            _img.onload = (_)=> {
                _counter ++;
                _draw(_.target);      
                _img.remove();
            };
            _img.src = _ip + '/mjpeg' + (_counter);
            setTimeout(_periodRun, _period);
        }; 
        _periodRun( ); // fire the process
    }
};
const  viewer  = new MjpegViewer(1, 1024, 768) // fps = 1 frame/sec, canvas: 1024x768
viewer.host = 'http://192.168.0.1:8080';
setTimeout( ( )=>{ viewer.stop } , 10000);

2019年10月13日 星期日

Flutter 使用 PlatformView


vscode 開啟終端先執行以下 1 ~ 3  命令, 確認範例程式是可上傳至手機並執行的:
1. flutter create -i swift -a kotlin textview
2. cd textview
3. flutter run
接著 vscode 開啟 textview 專案夾, 修改以下檔案:
1. 開啟檔案 lib/main.dart, 將以下程式碼複製貼上:
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
  @override build(BuildContext context) => MaterialApp(
     theme: ThemeData.dark(),
     home : NdoWidgetExample('native android widget!')
  );
}
class ViewController {
  final MethodChannel _channel;
  ViewController(int id): _channel = MethodChannel('_channel$id');  
  Future<void> setText(String text) async => await _channel.invokeMethod('setText', text);
}
typedef void NdoWidgetInit(ViewController _);// function type
class NdoWidgetExample extends StatelessWidget {
  final NdoWidgetInit _init;
  NdoWidgetExample._(this._init); 
  factory NdoWidgetExample(String text) => NdoWidgetExample._(
      (ViewController _) => _.setText(text)

  );
  @override build(BuildContext context)  => Scaffold(
    appBar: AppBar(title: Text('Android TextView')),
    body  : Column(children: [Text("Flutter!"), Container(
                width: 320.0,
                height:240.0,
                color: Colors.grey,
                child: AndroidView(
                    viewType: 'ndoView',
                    onPlatformViewCreated: (int id) => _init(ViewController(id))
                )
            )])
  );
}

2.開啟檔案 android/app/src/main/kotlin/com/example/textview/MainActivity.kt, 將以下程式碼複製貼上:
package com.example.textview;
import android.os.Bundle
import io.flutter.app.FlutterActivity
import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.plugin.common.PluginRegistry.Registrar
class MainActivity: FlutterActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        GeneratedPluginRegistrant.registerWith(this)
        registrarFor("com.example.textview").apply {  

          val ndoView = Factory_NdoWidget(messenger())
          platformViewRegistry().registerViewFactory("ndoView",ndoView)        
       }
    }       
}

3. 新增 android/app/src/main/kotlin/com/example/textview/TextViewPlugin.kt, 將以下程式碼複製貼上:
package com.example.textview
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory
import android.widget.TextView
import android.content.Context
class Factory_NdoWidget(private val messenger: BinaryMessenger)
: PlatformViewFactory(StandardMessageCodec.INSTANCE) {
    override fun create(context: Context, id: Int, o: Any?)
    = _NdoWidget(context, id, messenger)
}
class _NdoWidget(context: Context, id: Int, messenger: BinaryMessenger)
: PlatformView, MethodCallHandler { // interface to implement
    private val _channel = MethodChannel(messenger, "_channel$id")
    private val _view    = TextView(context)
    init {  _channel.setMethodCallHandler(this) }
    override fun getView() = _view

    override fun dispose() {   }
    override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
        when (call.method) {
            "setText" -> {
                _view.setText(call.arguments as String);
                result.success(null)
            }
            else -> result.notImplemented()
        }
    }
}
4.重新編譯並上傳到手機執行

2019年10月8日 星期二

Flutter 與原生插件溝通

Flutter 與原生插件主要是透過通道來溝通,除了 Flutter 官網上所展示的 MethodChannel 外, 還有 EventChannel 及 MessageChannel.  他們之間的最大不同點是: EventChanel 僅能單向從原生插件端往 Flutter 平台推送資料流, 也就是說原生插件是主動發送端, Fultter 處於被動的接收資料流, 而 MethodChannel 主動權是在 Flutter 端, 原生插件處在被動回應的角色, 嚴格來說是半雙工通道(需求發送完要等待回應), 至於 MessageChannel, 視情況兩端都能各自指定為主動端或是被動端, 是一個全雙工的通道(發送與接收分開同時運作)在互相傳輸資料, 理論上來說 MessageChannel 是可以取代上述兩種通道的, 只是運作介面之不同而已.通常要馬上回應的溝通管道直接使用 MethodChannel, 需長時間的回應就就交由 EventChannel, 比方說錄影時要馬上回應機器狀態, 就直接通過 MethodChannele 溝通與控制, 當錄影開始到結束需要花費較長時間,不可能讓程序長時間等待,就可以另闢 EventChannel 管道,後續用廣播方式作通知. 用 EventChannl 與 MethodChannel 就能滿足大部份文字訊息溝通的需求, 關於 EventChannel 與 MessageChannel 的使用方式,參考範例程式:
 // 原生 flutter plugin 端 kotlin 原始碼
    import android.os.Bundle
    import io.flutter.app.FlutterActivity
    import io.flutter.plugin.common.StringCodec
    import io.flutter.plugin.common.MethodChannel
    import io.flutter.plugin.common.EventChannel
    import io.flutter.plugin.common.BasicMessageChannel
    import io.flutter.plugins.GeneratedPluginRegistrant    // ...   
    var stream : EventChannel.EventSink? = null
    EventChannel(flutterView , "com.example.event").setStreamHandler(//事件通道註冊
        object:  EventChannel.StreamHandler { // 繼承 StreamHandler 並實現
                      override fun onCancel(arguments: Any?) { stream = null  }
                      override fun onListen(arguments: Any?, sink: EventChannel.EventSink?) {
                            stream = sink
                            stream?.success("init complete")   // 資料流非同步發送一個事件
                     }
    })
    //... async send event
    stream?.success("event !") // 資料流非同步再發送一個事件
    val msg  = BasicMessageChannel<String>(flutterView, "channel", StringCodec.INSTANCE)//簡單訊息通道註冊
    msg.send("Hello!") { reply: String? -> println(reply); } // 簡單訊息通道發送一則訊息  
    msg.setMessageHandler { req, reply ->  reply.reply("$req: plugin")  }// 同時能回應訊息
    // ...

// Flutter 端 dart 原始碼
   final _event  =  EventChannel('com.example.event'); //事件通道註冊
   _event.receiveBroadcastStream().listen((onData) {  //監聽資料流所發生的事件
         print('$onData');
   });
   final _msg = BasicMessageChannel<String>('channel', StringCodec());//簡單訊息通道   
  _msg.setMessageHandler( (String onData) async { // 簡單訊息通道等待接收訊息  
        print('receive: $onData');
        return 'hi';  // 回應資料
    });
  _msg.send('who').then(( x) =>  print('answer:$x')); // 簡單訊息通道同時能傳送訊息
總結來說, EventChannel 專用於插件的廣播頻道, MethodChannel 專用於插件的回應需求通道, 而 MessageChannel 則是一個通用的全雙工溝通管道.
使用 BinaryMessenger 通道傳輸, 參考文章:
        https://medium.com/flutter/flutter-platform-channels-ce7f540a104e
底下使用 android 端的 Kotlin 原始碼:  android platform -> flutter 溝通:
    import android.os.Bundle
    import io.flutter.app.FlutterActivity
    io.flutter.plugin.common.BinaryMessenger
    import io.flutter.plugin.common.StringCodec
    import java.nio.ByteBuffer
    import java.nio.ByteOrder
    // import io.flutter.plugin.common.BinaryCodec
    // import io.flutter.plugin.common.StandardMessageCodec
    // 若要接收監聽處理範例:
    flutterView.setMessageHandler("binary")  { buffer: ByteBuffer?,  reply ->    
      buffer?.order(ByteOrder.nativeOrder())      
      buffer?.putDouble(3.1415)
      buffer?.putInt(123456789)
      reply.reply(buffer)
    }
   // 若要傳送並等待處理範例
    val buffer = ByteBuffer.allocateDirect(12)
    buffer.putDouble(3.1415)
    buffer.putInt(123456789)  
    flutterView.send("binary", buffer) { _ -> } // 忽略回傳值
    // {  x: ByteBuffer? -> val y =  BinaryCodec.INSTANCE.decodeMessage(x) }//處理回傳值

底下使用 Flutter 端的 dart 原始碼:  flutter  -> android platform 溝通:
    // 若要接收監聽處理範例:
    BinaryMessages.setMessageHandler('binary', (ByteData buffer) async {
      final readBuffer = ReadBuffer(buffer);
      final x = readBuffer.getFloat64( );
      final n = readBuffer.getInt32( );
      print('Received $x and $n');
      return null;
    });
    // 若要傳送並等待處理範例
    final writeBuffer = WriteBuffer( ) ..
                      putFloat64(3.1415) ..
                      putInt32(12345678);
    final buffer = writeBuffer.done();
    await BinaryMessages.send('binary', buffer);
上述 BinaryMessages 通道若是透過 StringCodec( ), 就能用字串加以溝通:
    // 若要接收監聽處理範例:
    final codec = StringCodec( );
    BinaryMessages.setMessageHandler('binary', (ByteData buffer) async {
      print('${codec.decodeMessage(buffer)}');
      return codec.encodeMessage('Hi');
    });
    // 若要傳送並等待處理範例:
    final reply = await BinaryMessages.send('binary', codec.encodeMessage('Hello'))
    print(codec.decodeMessage(reply));

2019年10月2日 星期三

Flutter 透過高階函式(high orfer function)溝通 Widget

當參數帶有迴調函式時稱為高階函式,  StatefulWidget 與下層 Widget 溝通就可透過建構式傳遞高階函式方式當參數, 架起溝通管道, 底下 MyButton 雖然是一個 StatelessWidget 創造的 widget, 透過技巧可讓它看起來多彩多姿, 範例程式:
import 'package:flutter/material.dart';
void main() => runApp(MyApp( ));
class MyApp extends StatelessWidget {
  @override build(BuildContext context) => MaterialApp(
      theme : ThemeData.dark(),
      home  : MyHomePage()
  );
}
class MyHomePage extends StatefulWidget {
   @override  createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
  final colors = <Color>[Colors.red, Colors.orange, Colors.yellow, Colors.green, Colors.blue, Colors.cyan,Colors.purple];
  String  title = '按下按鍵更改標題';
  int     index = 0;  // 顏色索引
  Color color( ) { // callback function to send color
    index = (index +1) % colors.length;
    return colors[index];
  }
  void changeTitle(String _) {  // callback function to change title
    setState( ( ) => title =_ ); // setState to rebuild Scaffold widget
  }
  @override build(BuildContext context) => Scaffold(
      appBar: AppBar(title: Center(child:Text(title))),
      body  : Center(child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[MyButton(color, changeTitle)],
      ))
  ); 
}
class MyButton extends StatelessWidget {
  final Color Function( ) colorIn;
  final void Function(String) titleOut;
  MyButton(this.colorIn, this.titleOut);
  @override build(BuildContext context) => FlatButton.icon(
      onPressed : ( ) => titleOut('這是新標題'),
      color     : colorIn(), // get color
      label     : Text('按下按鍵更改顏色'), 
      icon      : Icon(Icons.color_lens)
  ); 
}

用 python 解簡單的常微分方程式

# sudo apt install python3-pip # python3 -m venv venv # cd venv # . bin/activate # pip3 install numpy matplotlib import numpy as np import m...