2024年3月11日 星期一

【Flutter】Looking up a deactivated widget's ancestor is unsafe

 簡單來說,就是要使用Router切換Widget時無法切換。

真正的原因是Router當真正要執行時呼叫程式設計給它的BuildContext參數,卻發現這個參數無法使用。


更具體來說,在很多地方(函數)會收到BuildContext傳入。

設計上會習慣在這些地方(函數)區段內將這些BuildContext傳給需要使用的功能,但偶爾會發生這些BuildContext不能使用的情況。

例如在AlertDialog裡取得的BuildContext就無法用在Router上。(因為Router會用這個BuildContext做定位、來知道Widget與Widget之間的關係,與它們在WidgetTree上的位置。而一個已經關閉的Dialog並不在WidgetTree上。)


解決這個錯誤的方法基本上是使用「GlobalKey」

簡單來說就是在Widget的建構子/Build階段傳入一個GlobalKey物件的實體到「key」這個欄位中。確認傳入後,就可以在這個Widget下使用GlobalKey的「currentContext」這個參數去取得可用的BuildContext(A),然後在宣告Router並需要傳入BuildContext的地方改傳入BuildContext(A)。

2021年8月9日 星期一

不定時開發日記:為什麼我找不到問題的解答?

網路上程式設計教學或解疑文章成千上萬,但為什麼很少有可以解決自己問題的?

假設這裡有篇解釋「如何使用某某元件」的文章,簡單看一下會發現作者對元件的認知很正確、很充分,行文也很流暢又詳盡、不會「貼個程式碼就了事」,——這樣的文章難道也沒用嗎?

「對!這種文章往往更危險!」

為什麼?怎麼可能?

一般性的原因就不重新贅述,這裡主要是要提出一個可能沒人提過的主張:因為這些文章作者使用的模組/框架有問題。


怎麼會提到模組/框架呢?


以Android來說,Activity基本的生命週期就是種模組/框架,要示範或說明如何使用元件(或任何技術)都必須要以Activity為範例的基礎。

所以模組/框架是存在的。

假定「只要順著基本的生命週期去使用元件必然不會出錯」這是個很嚴重的錯誤假設,因為想要學習怎麼使用元件(或技術)的人很多其實都有個很複雜的商業需求擺在那邊等著他們學習後去滿足,而他們的商業需求讓他們經常要忽略生命週期這類的框架。

聽起來好像沒什麼,就好像「在主線程外更新畫面就使用Handler就好了」,偏偏Android領域中有個「HandlerLeaking」的議題,而且發送Handler還有「Looper權限」.......

以上所說還在於「如果沒正確操作,Error訊息會留下足夠的指引讓人去尋找失敗的原因與解答,」但有些東西(例如MediaPlayer的IllegalState)是根本沒有解答的!(如果有IllegalState,那LegalState的標準何在......找不到啊!找不到啊!真的找不到啊!)


這時候就會發生美妙的悲劇了!

「我按照別人的教學與範本操作,最後專案碰到了一個怎樣都解不開的問題。要整個打掉重做?來不及了!但尋找解答又永遠找不到。」

2021年4月5日 星期一

【Android】onConfigurationChanged的觸發(和Configuration)

有些情況下-主要是指「配置」發生改變-,Activity會被重啟...

這些重啟發生時,經常伴隨「onConfigurationChanged的觸發」。(但官方文件說法是「不會觸發」。)

但如果只希望「onConfigurationChanged的觸發」發生、但不要有Activity重啟時,可以在AndroidManifest中,找到會被重啟的Activity下增加一個屬性「android:configChanges」,並在後面增加對應的參數來決定哪些情況要觸發、哪些情況就任由Activity重啟吧!


Configuration在官方的定義是指「決定系統要取用哪些Resource檔的方式」,像「螢幕是橫式或直式時會取用不同的Layout檔(如果有設定)」。

所以偵測螢幕橫式直式最正確的方式(除了等待onConfigurationChanged觸發)是從Activity下呼叫「getResource().getConfiguration()」去取得Configuration,然後判斷orientation的參數。


這個屬性後面可以接收的參數有:(官方網頁將它列在「activity」的Manifest說明中。)

android:configChanges=["mcc", "mnc", "locale",
                                 
"touchscreen", "keyboard", "keyboardHidden",
                                 
"navigation", "screenLayout", "fontScale",
                                 
"uiMode", "orientation", "density",
                                 
"screenSize", "smallestScreenSize"]

(官網這裡的說明有點奇妙,XML檔無法用這種方式輸入tag的參數,必須要用類似「"mcc|mnc"」這樣的格式才可以。其他欄位的說明有遵照這個模式,唯獨「configChanges」卻是錯誤的。)

比較奇妙的是:如果要針對螢幕轉向做偵測,需要使用多個參數會比較保險。

一般來說,是使用「orientation」和「screenSize」,(甚至再加上「uiMode」。)



2020年5月3日 星期日

隱私權政策

非常感謝各位使用本APP。
本APP不會收集各位的使用行為,例如閱覽習慣或GPS定位。
除了使用Google帳號進行身分認證以外,不會使用到任何使用者的個人資料。

謝謝。

2020年3月8日 星期日

【不要仰賴Activity的Android開發】什麼事情都用CustomView解決





一般來說,製作一個這樣的介面會使用一組Activity和XML。



<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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"
android:orientation="vertical"
tools:context=".MainActivity"
tools:showIn="@layout/activity_main"> 

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" /> 

 <EditText
android:id="@+id/identify"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minEms="10"
android:maxLines="1"
android:hint="ID" />
<EditText
android:id="@+id/password"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minEms="10"
android:maxLines="1"
android:hint="Password" />
<Button
android:id="@+id/login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="OK"/>
</LinearLayout>



public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { 
 super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); 

 final EditText id = findViewById(R.id.identify);
final EditText pw = findViewById(R.id.password); findViewById(R.id.login).setOnClickListener(new View.OnClickListener() { @Override
public void onClick(View view) { 

 Log.d("Hsu", "ID:" + id.getEditableText().toString());
 Log.d("Hsu", "PW:" + pw.getEditableText().toString()); 


 }
); 
 } 
}
(因為不需要,所以OnClickListener的內容只有意思性地Log。)


接下來的做法或許會比較麻煩,但概念上來說會提供程式比較多的擴充升級彈性。

因為它完全不受限於Activity,功能和介面可以隨時彈性重組,方便調整或擴充。

首先,整個Activity會變成只剩下這樣.......


public class MainActivity extends AppCompatActivity { 
 @Override 
 protected void onCreate(Bundle savedInstanceState) {             super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main); 

 } 
}

但XML會做出比較大幅度的改變...

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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"
android:orientation="vertical"> 

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" /> 

<com.myapplication.InputText
android:id="@+id/identify"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minEms="10"
android:maxLines="1"
android:hint="ID" /> 

<com.myapplication.InputText
android:id="@+id/password"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minEms="10"
android:maxLines="1"
android:hint="Password" /> 

<com.myapplication.LoginButton
android:id="@+id/login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="OK"/> 

</LinearLayout>


可以看到EditText和Button從Android標準元件變成APP專案客制的元件。

「com.myapplication」是專案的設定,有興趣的人可以自己彈性的修改命名,只要是「InputText」和「LoginButton」的路徑位置就好。(這兩個View物件的命名也可以自己彈性調整。)

InputText和LoginButton的內容則在下面。

public class InputText extends EditText { 
 public static InputText ID; 
 public static InputText PW; 

 public InputText(Context context, AttributeSet attrs) { 
 super(context, attrs); 
 String hint = getHint().toString();
 switch (hint){ 

 case "ID": ID = this;
 break;
 case "Password": 

 PW = this;
 break;
 } 

 } 
}



public class LoginButton extends Button implements View.OnClickListener { 
 public LoginButton(Context context, AttributeSet attrs) { 
 super(context, attrs);
 setOnClickListener(this);
 } 


 @Override
 public void onClick(View view) { 

 Log.d("Hsu", "ID:" + InputText.ID.getEditableText().toString());
 Log.d("Hsu", "PW:" + InputText.PW.getEditableText().toString()); 

 } 
}


因為這個介面還是算簡單的,所以這樣做的好處可能無法立刻顯現,要引入一些情境做說明。

這樣做的目的在於快速方便的大量產生擁有登入功能的介面。
登入這件事情越來越複雜,會產生各種登入模式,例如多帳號登入、例如彈出視窗登入、例如重新檢查使用者帳號密碼.....
所以快速的設計跟新增登入功能或許有其必要。

InputText可以確保針對登入的輸入功能可以快速重複利用,譬如「將輸入的內容加密」「將使用者帳號自動帶入ID欄」。
而LoginButton可以延生出各種版本,例如CheckPasswordButton可以用來讓使用者再次輸入密碼做保險性登入,等物件設計好後只要修改XML,將LoginButton換成CheckPasswordButton,一樣不需要修改Activity的任何內容,就可以完成新介面新功能。

用這種方式寫程式,為的是可以同時滿足快速組合出功能並達到模組化的目的。


有些人可以從附上的程式碼中快速找到一些功能上的限制,但其實這些限制都可以藉由簡單幾行程式碼擴充來破解,但以後再寫了。

2019年8月1日 星期四

【Google】如何將SheetSpread表格內的資料轉換成JSON

其實Google自己有自己的工具,但轉換出來的資料複雜的不合理。




所以找了一下,發現有神人製作了一個工具「http://gsx2json.com/」...

可以將第一列視為「Tag」,然後將每一欄作為一筆資料,用該欄第一列的資料作為Tag進行資料分類索引。




利用工具後...「http://gsx2json.com/api?id=1bRM_dIcl67MgLinJfc_YBqifZ65vusun5XJLWHQZ_oM&sheet=1&columns=false

轉換出的資料變成「{"rows":[{"標題":"測試一","內容":"測試內容1","參數":100},{"標題":"測試二","內容":"測試內容2","參數":1002},{"標題":"測試三","內容":"測試內容3","參數":1002}]}」

這樣的規格明顯合理許多。

也有別的參數可以調整...「http://gsx2json.com/api?id=1bRM_dIcl67MgLinJfc_YBqifZ65vusun5XJLWHQZ_oM&sheet=1

{"columns":{"標題":["測試一","測試二","測試三"],"內容":["測試內容1","測試內容2","測試內容3"],"參數":[100,1002,1002]},"rows":[{"標題":"測試一","內容":"測試內容1","參數":100},{"標題":"測試二","內容":"測試內容2","參數":1002},{"標題":"測試三","內容":"測試內容3","參數":1002}]}」

2019年7月12日 星期五

【Android Html5】播放Youtube影片

目標是建立一個會佔滿整個WebView的YouTubePlayer。

必須注意到:基本上,開發時所有會遇到的問題都源於「網頁版Youtube撥放器在移動網頁上不能正常運作」。──所以Google才會另外開發一套移動式套件,但因為這套件非常不好用,所以終究還是要回來解決不能正常運作的問題。


基本建立方式在第二個回應。(測試過,可以使用。)

這個技巧播放檔案的秘訣在於監聽Player的State,所以當它Ready就開始播放,播放完可以開始再次重播、或撥下一檔。

如果要改撥放PlayList、而不是單一影片,可以使用這個方法修改範例。

範例內是使用在YT.Player內直接設定VideoId的方式,但其實有用播放指令指定ID的方法,同時也可以用清單播放。方法可以看標準官方範例。

官方範例內有player.nextVideo(),可以用來自動播放列表下一檔。


影片如果要不暫停、就退出或前進到下一層功能,這個地方有講解需要使用什麼樣的修改。簡而言之就是WebView也有自己的onPause和onResume,必須要在Fragment和Activity中相對應的地方呼叫。(同時需要使用Timer的啟動與暫停。)

另外,影片如果還沒開始撥放就被退到背景,YouTube Player這時可能會在背景被啟動。所以在執行「是否要開始撥放」前,同時要檢查WebView是否還在前景上。



2019年4月10日 星期三

【Android AES】SHA1PRING + Crypto

Google提供的範例檔案


好像是Android6以後,AES加解密如果是使用SecureRandom這個物件搭配演算法「SHA1PRING」和Provider「Crypto」來取得RawKey(用來產生SecretKeySpec時需要的導入參數),會發生失敗的情況。

雖然Google提供了一組叫做CryptoProvider的物件在網路上讓人下載(或複製貼上),但在Cipher進行解密(執行dofinal這個函數)時一樣會出錯。

Google這個超級天才就繼續提供了一組物件叫做「InsecureSHA1PRNGKeyDerivator」在網路上,(載點在上面,)來解決這個問題。


這個物件的用法很單純,就是呼叫一組static物件取得Instance然後再導出 RawKey即可。

方法大概是這樣...

byte[] raw = InsecureSHA1PRNGKeyDerivator.deriveInsecureKey(seed, 16);

比較詭異、比較巧妙的是這個「16」,因為大多數使用這個物件的範例都使用「32」,可是解密部分一樣會在Cipher的地方出錯。這似乎是看你使用的Key長短而定,或是你設定的加密位元。

(32是256位元?16是128位元?)

其他部分都可以循著一般標準範例。

2019年2月19日 星期二

【Android】Mac Adress的可用求法(純Java、不含Android API)

根據官方文件的說法,所有可行的Android API都已經不可以取得Mac Address。

所以要取得Mac Address,必須要回歸原始Java方法。


public static String getMacAddr() {
    try {
        List<NetworkInterface> all = Collections.list(NetworkInterface.getNetworkInterfaces());
        for (NetworkInterface nif : all) {
            if (!nif.getName().equalsIgnoreCase("wlan0")) continue;

            byte[] macBytes = nif.getHardwareAddress();
            if (macBytes == null) {
                return "";
            }

            StringBuilder res1 = new StringBuilder();
            for (byte b : macBytes) {
                String hex = Integer.toHexString(b & 0xFF);
                if (hex.length() == 1)
                    hex = "0".concat(hex);
                res1.append(hex.concat(":"));
            }

            if (res1.length() > 0) {
                res1.deleteCharAt(res1.length() - 1);
            }
            return res1.toString();
        }
    } catch (Exception ex) {
    }
    return "";

簡單來說就是去取得無法閱讀的編碼然後轉換成Mac Address格式。(但也不是真的無法閱讀,只是送出的資料為數值,要轉16進位、還要轉成統一文字格式。

方法很簡單,但是困死許多人。



2019年2月18日 星期一

【Android】APP內部的「返回APP首頁」

(思考這個題目的目的在於........官方所提供關於Activity的某些控制機制其實邏輯不明確,很難知道照表操課的結果,所以會想要思考如何自己「實作一套機制」。)

雖然已經有Fragment,可以讓開發者用「切換」Fragment來製造「回到首頁」的效果,但這種做法其實極限很多。

問題在於「產品設計」通常不會希望APP設計上的「使用流程」受到嚴格拘束,他們總是會希望「APP的功能可以自由地切換。」


嚴格拘束、嚴格規範過的APP操作流程基本上都是樹狀圖。

假設首頁是Activity,下面的幾個主功能則是Activity1、Activity2、Activity3...

如果Activity1下面還有子功能,則是Activity1_1、Activity1_2、Activity1_3...

在Activity1內新增一個static的布林值,假設命名為「Close」,則Activity1_1、Activity1_2、Activity1_3內(還有未來可能延伸出來「子功能的子功能」)的「首頁」鍵按下後都會把這個Close的內容改為true。

另外在Activity1的onResume內檢查它的值,如果為true就把這個Activity關閉,如果false就繼續執行正常功能。

但記得永遠在onCreate內將Close設為false。因為如果經過onCreate就表示這個Activity是被正常且完整的流程給啟動的,可以視為一個「新的呼叫或使用」。

以此類推,Activiy1_1內也可以有自己的Close,讓 Acitivity1_1_1、Activity1_1_2....可以關閉。


這樣的缺點是「APP的操作路徑必須永遠是樹狀的。不同的樹枝之間不可以隨意橫跨呼叫。」


有辦法克服嗎?.......當然有,但是那必須要用靈魂交換.......開玩笑的。

其實這個方法反而相較簡單,但要使用它,必須要對Activity的生命週期應用非常嚴謹。




但最理想的方式是使用「ActivityLifecycleCallbacks」。

(注意!這個方法跟上述的方法是互相矛盾不可並行的。)

一般來說返回首頁會是個按鈕,則可以將這個按鈕統一用一個View類別來擴稱,不管是哪個類別,假設擴充之後的名稱為「HomeCall」。

HomeCall基本上是個要用在XML Layout檔中的類別,並不需要在個別Activity中使用它,(但要使用也可以,必須斟酌發揮一些巧思,)在建構子中使用傳入的Context去將「ActivityLifecycleCallbacks」實作並呼叫。

(應該已經有人知道我要做什麼了。)

然後一樣設置一個static的布林參數,假設命名為「HomeCall」.......(這地方可以依照實務上的需要做調整,不用布林改用其他數值參數也可以。)

在這個View自身上設置OnClickListener,然後內容設計為「一律將HomeCall設為false」,然後在之前實作的「ActivityLifecycleCallbacks」中的「onActivityResume」設計為「一律將傳入的Activity關閉」.......



【未完】

2019年2月11日 星期一

【Android】修改螢幕亮度

應該是Android 6.0以後,這類的硬體功能調整光是在Manifest檔中加上「User-Permission」並不夠,還需要有「權限啟動」的通知。

Intent intent1 = new Intent(android.provider.Settings.ACTION_MANAGE_WRITE_SETTINGS);intent1.setData(Uri.parse("package:" + getPackageName()));intent1.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);startActivity(intent1);

方法大概如上。這個功能會啟動特定APP的權限管理頁,讓使用者決定是否要自己打開權限。

但在執行這行之前,要詢問系統是否有「執行他們」的能力,--是否有修改Setting的權限。

Settings.System.canWrite

這是個函數,後面要傳入「Activity」作為參數。

如果回傳false,就表示權限未開啟,如果執行程式碼會導致當機。


之後就是很一般的程式碼,直接取得Setting類別後,要求修改特定參數。

android.provider.Settings.System.putInt(
        getContentResolver(),        android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE,        android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);android.provider.Settings.System.putInt(
        getContentResolver(),        android.provider.Settings.System.SCREEN_BRIGHTNESS,        brightNeess);

brightNeess這個參數要自己自訂。(寫死?或是可以讓使用怎選取、輸入想要的數直。)

2018年12月6日 星期四

【Android】如何讓WebView大小自動擴展為內容的大小

WebViewClient的「onPageFinished」被呼叫執行時,就表示網頁已經有一個初步的大小可供計算。但從Java端並無法去取得這個到小,只有網頁本身可以知道自己的大小,所以我們必須要使用JavaScriptInterface去取得這個大小。

在「onPgaeFinished」內執行一段JavaScript:「"javascript:MyApp.resize(document.body.getBoundingClientRect().height)"」,功能大概是說「執行resize這個函數,並將網頁body的高度傳入。」

所以接著要設計一個名稱為「resize」的函數並掛上「@JavascriptInterface」。

函數的內容基本上是重新設定整個WebView的LayoutParams,尤其是寬高。

高度的算法大概是「height * getResources().getDisplayMetrics().density」,「height」大概就是在JavaScript中傳入「resize」函數的數值。因為網頁有自己的高度單位,所以要換算成density才能取得正確大小。


注意!要記得執行addJavascriptInterface這個功能。這是讓網頁內容可以取得加註了「@JavascriptInterface」函數的必要辦法。(記得「javascript」中的「MyApp」和這裡的「MyApp」是連動的,如果要修改或自訂,記得一起完成。)


以下是更完整的程式碼。
private void setupWebView() {
    webView.getSettings().setJavaScriptEnabled(true);
    webView.setWebViewClient(new WebViewClient() {
        @Override
        public void onPageFinished(WebView view, String url) {
            webView.loadUrl("javascript:MyApp.resize(document.body.getBoundingClientRect().height)");
            super.onPageFinished(view, url);
        }
    });
    webView.addJavascriptInterface(this, "MyApp");
}

@JavascriptInterface
public void resize(final float height) {
    MyActivity.this.runOnUiThread(new Runnable() {
        @Override
        public void run() {
            webView.setLayoutParams(new LinearLayout.LayoutParams(getResources().getDisplayMetrics().widthPixels, (int) (height * getResources().getDisplayMetrics().density)));
        }
    });
}

2018年11月27日 星期二

[Android]使用DownloadManager

DownloadManager是個可以直接把檔案存進「下載區」、而不是在專案路徑下的工具。

它算是SystemService的一種,取得實體的方式跟其他Manager大同小異。(使用Activity下的getSystemService,並傳入Context.DOWNLOAD_SERVICE這個參數。)

但實際執行下載動作的不是DownloadManager,而是DownloadManager.Request。只要使用一般物件宣告實體的方式並指定一組網址參數給它,然後將它轉交給DownloadManager的「enqueue」就會開始執行下載動作。

下載完畢的時候,它會發出一個帶有「DownloadManager.ACTION_DOWNLOAD_COMPLETE」的Intent,所以這個工具要使用BroadcastReciever去接收這個Intent已完成「下載完成」的偵測。(這跟常見的Http管理工具風格有點相違背,因為這年頭習慣製作一個「事件」的Listener後,再把這個事件傳給Http的行為Thread。所以不習慣什麼事情都用BroadcastReciever的人在使用這個物件時可能會猶豫。

開始下載前(執行「enqueue」指令時), DownloadManager會回傳一組長整數(Long):fileID。在BroadcastReciever中,要使用這個fileID去取得「下載完的檔案實體路徑」。(顯然Google注意到了並不是每家廠牌手機都使用相同的路徑設定,下載區有可能會有很獨特的路徑,這會導致APP崩潰。)

以下功能寫在BroadcastReciever中。(範例是個圖檔,所以使用ImageView顯示。)

DownloadManager.Query query = new DownloadManager.Query(); query.setFilterById(downloadId); Cursor c = manager.query(query); if (c.moveToFirst()) { int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS); if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) {
String uriString = c.getString(
c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)); imageView.setImageURI(Uri.parse(uriString)); Toast.makeText(MainActivity.this,
"download success",
Toast.LENGTH_SHORT).show(); } }

它提供了一個快速打開「檔案區」的方法。
Intent i = new Intent(); i.setAction(DownloadManager.ACTION_VIEW_DOWNLOADS); startActivity(i);

但一般來說都會希望自動導引使用專門開啟檔案的APP來瀏覽檔案。

要讓「其他APP」瀏覽「檔案」從API:24以後都強制使用FileProvide。

FileProvider不用複寫任何功能,只需擴充就可以發揮功用。(這樣做的意義何在?研究中。)






2018年10月30日 星期二

演算法練習「Candidate Report: Anonymous」

解答...


這題是「Stacks and Queues」的第一題練習。

簡單來說就是「後進先出」:用一個矩陣逐一紀錄並疊加收到的資料。

例如:資料1收在矩陣0,資料2收在矩陣1...資料N收在矩陣N-1,但等到要處理資料時,先從資料N、資料N-1...逐一開始往回處理,如果臨時又有資料加入,則繼續疊加程序。


這跟一般來說的「排隊」不同,這是文本或編譯器的基礎。

例如JSON翻譯器,或程式碼編譯時用來辨識「區段」的開始與結束,例如for迴圈或類別檔的內容。

本身反而沒難度,搞懂邏輯程序、確保題目結果正確後,效能並沒有什麼難度。(因為題目本身就是個一維迴圈。)

2018年10月29日 星期一

演算法練習 「GenomicRangeQuery」 (快速計算矩陣內單位總和的方式)

題目和解法...

講義...

一連串長度為X的數值矩陣,如何知道單位M和N之間有不為零的數值?(M>N,且絕對不小於零或大於等於X,所以M和N單位絕對都在矩陣內。)


這其實是個變相計算總和的技巧。
如果把矩陣從頭(最小單位)跑一次迴圈,並且每次都把前一個單位的值和自己做加總,例如「X[A] = X[A] + X[A-1]」,則等到迴圈跑完,這個迴圈的每個單位(i)會變成「從0到i單位的總和」,如果要知道每個單位原本的值,只需要把單位(i)減去單位(i-1)就好。
如果要問「i到j之間單位的數值總和(假設j > i)」,也只需要把單位(j)減去單位(i)就好。



所以如果題目要知道ATCG在基因序列中出現的頻率,那就宣告四個和基因序列等長的矩陣,個別對應到ATCG,每次只要出現相對應的序列,就在那個矩陣單位中加一(X[A]=1)。

如果C[i] = N,而C[i + a] = N+2,這就表示在「i」到「i+a」這中間的單位裡,C出現了兩次。這個C[i] - C[i+a]如果稱為F(C, i, 2),則按照題目的要求,F(A, i, j)不為0時答案為1,F(C, j, j)不為零時答案為1......

2018年8月2日 星期四

【Android】DialogFragment使用的小秘訣

DialogFragment並不是Dialog,所以習慣上設定Dialog的很多細節都無法直接使用。

設計上(我發現)矛盾的地方在於它確實會產生Dialog類別,但這個類別的產生又限定在MainUIThread中,這會導致「不同時間點使用DialogFragment時會產生Error導致Crash」,但Fragment的使用自由度高、調控會由底層安排,這點似乎在DialogFragment上有漏洞。

要徹底避免,實作DialogFragment時就要使用onCreateView、而不是onCreateDialog。

但這會導致無法使用Dialog的功能,而只能製作setView的內容。

解決方法是使用DialogFragment的getDialog......對!DialogFragment下有這個功能。

但同樣的矛盾問題再次發生:要被MainUIThread執行過後才會有Dialog,在這之前執行getDialog只會得到空值。

所以要注意將整段功能(需要使用到Dialog)的地方丟入MainUIThread中執行、或乾脆等個幾毫秒鐘再繼續執行...(無限判斷是否已經有Dialog。)

另外,FragmentManager要在MainUIThread執行過 executePendingTransactions() ,Dialog才會是「可以被修改的」。(不然,要設個ClickListener?不行。) 

2018年7月23日 星期一

【Android】尋找特定View在ScrollView中的位置並滾動到定位

ScrollView中必須要再包一個「ViewGroup」才有辦法再放入其他View。(它只能容納一個ChildView。)

所以「(特定View)在ScrollView中的位置」其實是「(特定View)在此ViewGroup中的位置」。

但一般尋找「View在螢幕中絕對座標」的辦法無法用在此處。

以縱軸座標的ScrollView來說,「位置」其實是指「這個View距離ViewGroup上方邊界的距離」。

所以要使用View中的功能:getTop(),來取得.......

但這個功能取得的是View跟最接近的ViewGroup的距離。也就是說如果ScrollView和View之間不只一層ViewGroup,而是有多層ViewGroup,(為了做出複雜的版型,)那就必須要用一個迴圈將它們的「getTop()」通通取出來作加總...

舉例.....

假設ScrollView和View之間有ViewGroup(0)、ViewGroup(1)、ViewGroup(2)、ViewGroup(3),最後才是View,....如果已經可以清楚知道ViewGroup的數目,那就好辦,但如果不知道...

要從View取ParentView,(這東西可以直接轉型成「View類別」,)然後再從ParentView取ParentView...直到取出來的ParentView是ScrollView為止。

每次的getTop記得都要累加起來,得到的數值才是畫面上、視覺上,這個View在ScrollView中的位置。

2018年7月20日 星期五

【Java】Instanceof 的效能

Interface可以讓「無直接繼承關係」的物件之間共享相同屬性,──簡單來說,介面本身就是種屬性。

判讀這種屬性用的就是「instanceof」這個指令。


舉例:「設置一個介面稱為『Wing』,然後在怪物A的子怪物類別群中找到一隻,假設類別為怪物A2B,然後讓它擴充『Wing』介面。」如此一來,在一系列怪物中就可以跳過在「怪物A」、甚至「怪物A的父類別」中新增參數「Wing」。──雖然不一定會發生,這樣做的好處是「設計上彈性非常高」。

但是相較於判讀介面,判讀物件類別名稱只要十分之一的效能。(而且判讀物件類別名稱所消耗的效能又是直接判讀參數的一到三倍。)但最終物件類別名稱並無法達到我剛才所說的效果。

把所有都擴充了Wing介面的類別都集合在一起判斷,施作上不合理!(但......實際上可以這樣做。)


將所有屬性都集中在一個父類別,然後用子類別來「變化/初始化」這些參數,這在講求效能的情況下還是比較實在。但怎麼設計參數........每多增加一個參數,類別的物件實體在記憶體中占用的量就會增加。

所以一般來說都是使用位元運算子,一個短整數等於8bit,也就等於八個boolean值,直接宣告一個短整數省記憶體?還是宣告八個boolean省記憶體?...以後再測試。

2018年7月17日 星期二

【Android Fragment】如何保留畫面上的操作選項

(因為是「Fragment」,所以這篇文章中的某些技巧與思考才會有存在的價值。)

簡單來說,一個Fragment如果被暫時從畫面上退到「Stack」中,當它從「Stack」中回到畫面上時,如何保留上次Fragment介面中的操作紀錄,例如輸入框輸入的資訊內容。

方法其實很簡單,Fragment內要有個「全域變數A」來承接onCreateView中第一次產生的View,當下次Fragment從Stack中回到畫面時,優先判斷順序就變成先判斷「全域變數A」是否為空值(不是直接進行LayoutInflater),如果不為空值就改讀取「全域變數A」做接下來的操作。

但是當再背景停留太久、整個Fragment的資料都被回收.....這種招數就不管用了。

話說回來,除非每次操作資料都即時進SQL,不然這種事情是無解的。(頂多確保Fragment能正常顯示而已。)

(真正的解答是乖乖使用Argument。)

2018年7月5日 星期四

【Android】Render截圖

為什麼要註明Render截圖?

因為目前Android官方推出的標準截圖方法必須建立在「有辦法取得影片檔案的Description」上。

但影片格式越來越多樣,即使檔名不變,但Description可能無法支援的情況會發生。

所以必須要直接從Render上取得「要撥出的影像畫質內容」。

關鍵在於取得Render內的GL10。(其實不是「Render內」,而是onRenderDraw/onDrawFrame這些函數中,會取得GL傳入的GL10。)

取得後,資料轉換的方法就很固定了。
void screenShot(GL10 gl) {
     int screenshotSize = mCurrentViewportWidth * mCurrentViewportHeight;
        ByteBuffer bb = ByteBuffer.allocateDirect(screenshotSize * 4);
        bb.order(ByteOrder.nativeOrder());
        gl.glReadPixels(0, 0, mCurrentViewportWidth, mCurrentViewportHeight, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, bb);
        int pixelsBuffer[] = new int[screenshotSize];
        bb.asIntBuffer().get(pixelsBuffer);
        bb = null;
        Bitmap bitmap = Bitmap.createBitmap(mCurrentViewportWidth, mCurrentViewportHeight, Bitmap.Config.RGB_565);
        bitmap.setPixels(pixelsBuffer, screenshotSize-mCurrentViewportWidth, -mCurrentViewportWidth, 0, 0, mCurrentViewportWidth, mCurrentViewportHeight);
        pixelsBuffer = null;

        short sBuffer[] = new short[screenshotSize];
        ShortBuffer sb = ShortBuffer.wrap(sBuffer);
        bitmap.copyPixelsToBuffer(sb);

        //Making created bitmap (from OpenGL points) compatible with Android bitmap
        for (int i = 0; i < screenshotSize; ++i) {                  
            short v = sBuffer[i];
            sBuffer[i] = (short) (((v&0x1f) << 11) | (v&0x7e0) | ((v&0xf800) >> 11));
        }
        sb.rewind();
        bitmap.copyPixelsFromBuffer(sb);
        
        
    }