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);
        
        
    }




2018年6月27日 星期三

Eclipse上輸入Picasso (連Maven都不用)

為了使用相較之下比較精簡的MD360Android,所以開始使用Picasso,(不然之前都使用Glide。)

okhttp和okio這兩個專案都有提供jar檔,可以直接下載下來輸入libs中使用。

GitHub內的Picasso專案,扣掉TestUnit Package外,其實就可以簡單拆解成四個項目:Picasso、Picasso-pollexor、Picasso-provider、Squareup-pollexor。





但再仔細觀察,這四個專案其實都沒有res內容,也就是說可以直接整併在同一個專案下。Squareup-pollexor甚至只是個純Java-client的專案。

Picasso專案內本身並不包含「Squareup-pollexor」的程式碼,是在gradle的builder中匯入,所以要使用搜尋的方式找到這個專案才可以。(直接使用Package的名稱,如果使用Pollexor或Thumbor,都有其他相同名稱的專案存在。)



因為都沒有使用到Resource檔的內容,本來應該編譯完就可以直接輸出一份jar做使用了!

(懶人說明)額外注意的是:最新的Picasso的Package名稱是「com.squareup.picasso3」,GitHub上很多使用Picasso的第三方專案都還在使用「com.squareup.picasso」,所以只要修改import名稱即可使用。


最新版本「Picasso3」的特色是:不能直接使用「Picasso類別」的static函數,必須要先從「PicassoProvider類別」的static函數「get」取得;但直接做一樣會出錯,因為PicassoProvider中沒有Context參數,而它是使用Provider去獲取Context參數,所以必須要在專案的AndroidManifest檔中加入PicassoContextProvider的設定值。

因為這篇文章是使用Eclipse,不搭配Gradle或Maven,所以參數檔的寫法要注意...

在PicassoProvider套件專案內可以找到一個AndroidManifest檔,裡面有這個Provider的設定寫法。

<application>
<provider
android:name=".PicassoContentProvider"
android:authorities="${applicationId}.com.squareup.picasso3"
android:exported="false"/>
</application>


但要注意,直接複製貼上會引來編譯器錯誤,必須要把含有「$」的「applicationId」直接刪除,然後「name」標籤後面要加上檔案路徑(修改為「com.squareup.picasso3.PicassoContentProvider」)。

2018年6月26日 星期二

Eclipse上的WorkingSet

我是在這篇文章中看到這個東西的使用方式......(不知道他老兄從哪裡看來的原文。)

每新建一個專案時,會有個「Working Sets」要大家選擇並輸入。

這東西的功能基本上就是把「PackageExplore」中的Project分類。

例如新增一個「SupportV4」類別,然後把AndroidSupportV4相關的Project都設定為這個WorkingSet,那新增完後會直接在「AndroidSupportV4」下找到這個新增的Project。

當然,說白了就只是把PackageExplore區的Project分類後做成可以「打開/關上」的形式,對開發(克服技術困難)的速度一點影響也沒有。

但開發時的心情跟管理程式碼的效率相信會好很多。


但如何管理已經存在的Project?Project上點右鍵,然後選擇「AssignWorkingSet」,就可以了。


基礎使用方式,建議翻翻上面的連結。

2018年5月24日 星期四

[路徑]\android.jar is neither a directory nor file (type=1).

這是Android SDK更新檔案失敗時經常會做的處置。

仔細看...SDK Manager更新個別Plateform檔案的方式是「把舊的砍掉」「新建一個」「把新的改成跟舊的一樣的名字」。

但如果更新的過程失敗,例如「新建一個」失敗?它並不會把砍掉的Plateform檔案還原,而是就任由它那樣空置在那裏。

這時候IDE就會發現專案找不到相對應的Plateform檔案,但關於「Builder/Plateform」的名單卻沒有更新,使用者就會看到IDE一直在那邊試圖讀取在SDK中已經不存在的Plateform檔案。


退出IDE、重新更新一次SDK,其實就可以輕易的修好這個Bug。

(同樣的理由,有時候運行的很正常的專案有一天會忽然「沒有指定任何Plateform」,就是因為更新的過程中,它刪除了Plateform檔但又一直無法更新成功,所以就先讓IDE更新了Plateform名單,但之後很巧妙神奇地完成了Plateform檔更新,也再次更新了Plateform名單。)

2018年5月10日 星期四

【Android Line Login API】Eclipse上使用Line Login API時碰到Activit not found的解決辦法

呼叫Line的功能會使用到「com.linecorp.linesdk.auth.internal.LineAuthenticationActivity」和「com.linecorp.linesdk.auth.internal.LineAuthenticationCallbackActivity」。

可以在aar檔解壓縮後的Library內找到AndroidManifest.xml檔,並用標準Eclipse的方式匯入。

(Line SDK有提供Maven版。但我一樣沒有用。)


在Library內的AndroidManifest檔下有這兩個設定值,將它們完整複製下來、貼到自己的工作專案下,(光是直接import並不夠,)然後每個「android:exported」都改為true。

接著就可以正常使用了。



        <activity
            android:name="com.linecorp.linesdk.auth.internal.LineAuthenticationActivity"
            android:configChanges="orientation|screenSize|keyboardHidden"
            android:exported="false"
            android:launchMode="singleTop"
            android:theme="@style/LineSdk_AuthenticationActivity" />
        <activity
            android:name="com.linecorp.linesdk.auth.internal.LineAuthenticationCallbackActivity"
            android:configChanges="orientation|screenSize|keyboardHidden"
            android:exported="true" >
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />

                <data android:scheme="lineauth" />
            </intent-filter>
        </activity>

不要搬移這段設定值,程式依然可以正常執行,但是當進行登入時,系統層的logcat訊息會跳出「Activiy」找不到的警訊然後中斷執行,(在APP層的logcat中看不到。)

還在使用Eclipse的人有興趣可以試試看。


會注意到這點,是因為使用FCM時也碰到類似的情況:必須要把Library中的Manifest檔設定內容搬到APP中的Manifest檔中。

感謝Dandar3寫的說明文件讓我有解決這個問題的基礎。(歡迎大家去Github上拜訪他一下。)

2018年5月9日 星期三

【Android Fragment】Fragment的優缺點

先說缺點。

缺點當然是麻煩!

不單單是設計上,如果要在Fragment中插入Fragment,一定不能走XML,必須要在主Fragment中呼叫getChildFragmentSupport,然後用Java程式碼將子Fragment一個一個插入主Fragment中。

(這很自打嘴巴。因為Google自己當初用非常強勢的方式規劃了一個用XML為主的設計流程思維,現在又告訴大家「這個時候要丟掉這個東西。」)



優點主要有兩個....

一是介面可以大範圍的元件化。如果介面內的資料需要複雜的初始化、但又要可以動態的顯示、隱藏、銷毀(不再使用),過去需要設計複雜的Handler,而且還要處理HandlerLieaking(一種送出的Handler被主執行序完全忽略不執行的狀況),但如果使用Fragment就可以確保「隨時都能插入或移除介面元件」甚至還有「複雜的初始化」。

(FragmentManager可以從任何地方任何時刻向主執行序發出一個修改介面的請求。不管是add/replace/remove一個Fragment,或將一個現存Fragment用簡單的「先detach、再attach」來刷新內容。)


二是延續一,讓設計者可以用介面元件更新控制「流程」或「功能的週期」。一個執行序需要有更明確且簡單的控制判斷時,可以使用Fragment。例如執行序中途需要更新畫面(例如一個TextView),但TextView會有不在畫面上的時刻,如果用Activity層來控制執行序,Handler的設計需求會更複雜,如果改用Fragment,執行序可以直接判斷Fragment的生命週期即可。

(一般的View沒有生命週期,如果要用是否為「null」,就需要獨立製作資料的存取功能,如果用Fragment,則只要用Frgament當存放資料的容器即可,當Fragment需要使用資料時,也只需要再「自己」身上找資料即可。)




2018年4月18日 星期三

【Android】camera2 -- YUV格式圖檔

Android5以後的android.hardware.camera被列為「@Deprecated,請改用android.hardware.camera2。」

但就算不提「android.hardware.camera2」的複雜,傳入的照片資料結構本身跟「android.hardware.camera」的差異也非常大,如果要升級,光是改寫相機硬體與權限套件還不夠,還要連照片的轉檔也一併完成。


Android相機預設的檔案格式稱為YUV,它是種「用三種向量的方式來記錄圖片資料」的格式(完全無法理解怎麼實作),一共有420、422和444三種格式。(但也可以指定相機傳入舊格式檔案,但會造成「無法預覽」,自然也無法進行拍照。這格式的檔案在實用上功能有點不明。)

這檔案的格式技術細節未來需要慢慢補完。



Android使用這種格式的檔案奇妙之處在於:View元件本身幾乎都不支援這個格式的檔案,依然支援傳統RGB(Bitmap)或Drawalbe。

「android.hardware.camera」中取得的是YUV圖片的raw資料格式,也就byte矩陣檔。

但「android.hardware.caemra2」中經由「ImageReader」取得的是YUV圖片的資料格式封裝物件「Image」,這東西並不是YUV檔的實體,也無法直接轉成raw資料格式。

一個Image中有三層「Plane」,每一層都可以視為是對應「YUV」中的一種向量資料,因為是向量,所以每一張照片中的每一層Plane資料格式大小其實都不一定,跟RGB格式不一樣,只要知道的圖片長寬和格式,幾乎就可以順利估算出檔案大小或資料長度。

將Image物件格式轉成raw格式的演算法如下。三層ByteBuffer剛好是依序三層Plane的ByteBuffer資料。

// nv12: true = NV12, false = NV21
    public static byte[] YUV_420_888toNV(ByteBuffer yBuffer, ByteBuffer uBuffer, ByteBuffer vBuffer, boolean nv12) {
        byte[] nv;

        int ySize = yBuffer.remaining();
        int uSize = uBuffer.remaining();
        int vSize = vBuffer.remaining();

        nv = new byte[ySize + uSize + vSize];

        yBuffer.get(nv, 0, ySize);
        if (nv12) {//U and V are swapped
            vBuffer.get(nv, ySize, vSize);
            uBuffer.get(nv, ySize + vSize, uSize);
        } else {
            uBuffer.get(nv, ySize , uSize);
            vBuffer.get(nv, ySize + uSize, vSize);
        }
        return nv;
    }

但用QRCode Decode的套件:ZXing作測試,它可以接受「android.hardware.camera」的raw資料,但仍無法接收這個格式的資料。所以這個演算法還需要再檢討。

但奇妙的是:這個演算法獲得的raw格式資料確實可以轉成RGB格式檔案/Bitmap物件。

演算法如下。


public static byte[] NV21toJPEG(byte[] nv21, int width, int height, int quality) {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        YuvImage yuv = new YuvImage(nv21, ImageFormat.NV21, width, height, null);
        yuv.compressToJpeg(new Rect(0, 0, width, height), quality, out);
        return out.toByteArray();
    }

參考連結

https://stackoverflow.com/questions/9325861/converting-yuv-rgbimage-processing-yuv-during-onpreviewframe-in-android