雜物聚集地

JNI中是否需要自行呼叫DeleteLocalRef

Wed, 22 May 2013 15:43:46 GMT

這是一個被問到我一時之間答不出來的問題,後來去找了一下

JNI電子書籍
https://192.9.162.55/docs/books/jni/
https://www.soi.city.ac.uk/~kloukin/IN2P3/material/jni.pdf

下面是相關說明
引自 https://192.9.162.55/docs/books/jni/html/refs.html

A local reference is valid only within the dynamic context of the native method that creates it, and only within that one invocation of the native method. All local references created during the execution of a native method will be freed once the native method returns.
There are two ways to invalidate a local reference. As explained before, the virtual machine automatically frees all local references created during the execution of a native method after the native method returns. In addition, programmers may explicitly manage the lifetime of local references using JNI functions such as DeleteLocalRef.

Why do you want to delete local references explicitly if the virtual machine automatically frees them after native methods return? A local reference keeps the referenced object from being garbage collected until the local reference is invalidated. The DeleteLocalRef call in MyNewString, for example, allows the intermediate array object, elemArr, to be garbage collected immediately. Otherwise the virtual machine will only be able to free the elemArr object after the native method that calls MyNewString (such as C.f above) returns.

基本上就是說

  • Local Reference只在其創建方法的dynamic context內有效。
  • 兩個狀況下Local Reference會失效:使用DeleteLocalRef,其創建方法返回時。
  • 既然返回時會失效為啥要呼叫DeleteLocalRef,在於呼叫後就立刻允許VM回收而非等到方法返回之後。

個人感覺,這跟java中將用完物件的ref設為null的感覺很像。


Android設定寄送夾帶附件的E-mail時附件無法使用

Fri, 19 Apr 2013 15:01:26 GMT

問題起因

基本上使用常見的方法如下

Intent i = new Intent(Intent.ACTION_SEND);
i.setType("image/jpeg");
i.putExtra(Intent.EXTRA_EMAIL  , new String[]{sendTarget});
i.putExtra(Intent.EXTRA_SUBJECT, sendSubject);
i.putExtra(Intent.EXTRA_TEXT   , sendText);
i.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://"+ filePath));
try {
        curActivity.startActivity(Intent.createChooser(i, "Please select Email client"));
} catch (android.content.ActivityNotFoundException ex) {
    Toast.makeText(curActivity, "There are no email clients installed.", Toast.LENGTH_SHORT).show();
}

問題出於我給的是Internal Storage的路徑,所以基本上當其它的應用程式要取用檔案時有有存取權限問題

解決方式

copy一份到External Storage再用copy檔的路徑即可

File externalPicPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
File dstFile = new File(externalPicPath, "picture.png");
try{
    FileInputStream in = new FileInputStream(filePath);
    FileOutputStream out = new FileOutputStream(dstFile);
    byte[] buf = new byte[1024];
    int len;
    while ((len = in.read(buf)) > 0) {
        out.write(buf, 0, len);
    }
    in.close();
    out.close();
}
catch(IOException e)
{
    Toast.makeText(curActivity, "I/O Error.", Toast.LENGTH_SHORT).show();
        return;
}```

Unity練習:使用Texture Packer自製簡易2D Sprite物件

Sun, 07 Apr 2013 15:48:25 GMT

這是一個基本的練習,記載練習中學到的一些東西,基本上公司專案是使用NGUI這個plugin。
檔案放在 https://github.com/hsienwei/Unity_2D_Sprite_Test
呈現畫面大概如下

{% asset_img b27c3e4548a2ec5a51d69c880381c09c-746777.png %} {% asset_img 2f8385900ffdb08f8ec0603404810d1b-747761.png %}

Texture Packer與JSON Parser

Texture Packer是一個功能很完整的工具軟體,可以讓你把一些分散的圖包成一個大圖,可以節省記憶體與加快讀取速度,在以前使用cocos2d-x時就常常在用,現在他也提供Unity 3D的格式,但以這個格式匯出的檔案基本上是一個JSON格式的檔案,只是副檔名存成txt,我想是因為Unity的TextAsset只能是.txt的檔案,另外在格式當中另外有JSON(Array)與JSON(Hash)都是JSON格式,主要差在Frame的資料是用array存或者是object存,Unity 3D的格式跟JSON(Hash)的內容是一樣的。

{% asset_img 44821a95d11d41671941ff6a4f99273e-750059.png [JSON(Hash) & Unity 3D] %} {% asset_img 58f235ec98ad7704ff274a8bb16b46cf-751904.png [JSON(Array)] %}

確定為JSON格式後就是選擇parser,試了兩個現有的library(Asset store中也有但我沒試)

https://wiki.unity3d.com/index.php/JSONObject
這一個在讀Texture Packer的檔案時會有點問題,看了一下居然是無法處理空格囧,基本上要處理的話在JSONObject.cs中Line61加一行 str = str.Replace(" ", ""); 就可以解決,但是後來想想還是算了。

https://wiki.unity3d.com/index.php?title=UnityLitJSON
這個是基於一個C#的JSON Library : LitJSON去做的,看了一下這個相關文件比較多而且比較便於使用。
他提供了兩個用法:

  • Mapping JSON to objects
  • Readers and Writers

我用的是Mapping JSON to objects,比較接近我以前的經驗。
下面是LitJSON一些相關資料
https://www.cnblogs.com/peiandsky/archive/2012/04/20/2459219.html 簡單介紹使用方法
https://litjson.sourceforge.net/doc/manual.html 官方說明

基本上上面的一些步驟有參考下面這兩篇
https://blog.csdn.net/midashao/article/details/8220868 Unity3D之结合TexturePacker使用显示贴图
但我用的不是JSON(Array) 且我用的LitJSON不是原版的,似乎有被加工處理
https://tpathuis.tumblr.com/post/42501893370/texturepacker這一篇是Texture Packer中Unity 3D分類的Turtorial,這裡有一些Texture Packer的設定,我用的設定比較貼近這個,沒有開Allow rotation,但我有開Trim Mode。

設定好後publish,就可以產出兩個檔案 一個txt檔一個png檔,將這兩個檔案放到Unity project 中asset/resources資料夾中之後使用。 (註記一下如果要用Resources.Load()去讀檔案一定要放在名稱為"resources"的資料夾中)

建立Mesh物件

2D Sprite由一個平面組成基本上只要兩個三角面,但Unity的內建的Plane有200個三角面,實在是多太多了,所以有必要自己弄一個面數較少的來使用。
要建立Mesh資料,可以使用Mesh Filter這個component,然後再設定Mesh的vertices, uv與triangles,我的設定內容如下連結:
https://github.com/hsienwei/Unity_2D_Sprite_Test/blob/master/Assets/script/Plane.cs
我的做法是依照frame的資訊去設定vertices的寬高,跟NGUI的做法上不太一樣,NGUI的設定基本上寬高都是1*1,藉由調整Scale去調整為該frame的寬高,我的做法是設定vertices的位置為該frame的寬高,調整scale的話就是調整縮放比例。
至於uv的部分就是將座標值轉成0-1之間的數值,另外由於座標系統不一樣,必須將y軸做調整。

另外要注意因為每個atlas都是一張圖,所以在atlas物件中有一個material欄位是用來對各個sprite物件設定renderer的material,這裡主要是因為想要減少drawcall,下面是相關資料的連結:
https://docs.unity3d.com/Documentation/Manual/DrawCallBatching.html
Dynamic Batching的條件中有一項是使用相同的Material,所以我從之前各個sprite都自己new material物件改為在atlas讀檔時new material,在sprite設定時再參照到atlas的material,由於sprite的頂點夠少,所以Unity會自動幫我們處理batching。
(如果要自己使用batching script,在Import Package->script 匯入Standard Assets/Scripts/Utility Scripts內的兩個script)。

editor

這個練習我寫了兩個主要的script,一個是atlas,另一個是Plane,這兩個我在做法上有點不同。 首先是atlas,他的畫面與部分程式碼如下 {% asset_img e393420d2c4f67038433399b9f3741e3-753607.png %} {% asset_img a5783729a8ecd4c2dd0964bc2d311803-754879.png %}

主要設定 TextAsset這個欄位,下面的一些資訊會自動生成(我這裡有一點bug,設定好TextAsset 後需要play一次下面的資訊才會出現),基本上這邊我只有使用最簡單的方式,也就是在程式的部分設定為public,這些值就會出現在Inspector介面中供調整,TextAset以下的其實可以不要出現在Inspector,但為了方便看相關資訊所以讓他出現。

下面的是Plane這個sprite {% asset_img f2d055e6a8a991cfb37d28fb3bd17184-755749.png %}

這一個主要是使用custom editor,自己寫一個editor的類別去處理這個script在Inspector中顯示的GUI介面,程式碼如下
https://github.com/hsienwei/Unity_2D_Sprite_Test/blob/master/Assets/editor/PlaneEditor.cs

可以參考一下editor類別的說明 https://docs.unity3d.com/Documentation/ScriptReference/Editor.html
還有官方文件https://docs.unity3d.com/Documentation/Components/gui-ExtendingEditor.html
相關教程 https://www.youtube.com/watch?v=WlGwBmM-dfA

自己記錄的幾個點

  • 要用custom editor 要在前面加上 [CustomEditor(typeof(CLASS))]
  • 繼承 Editor
  • 要放在editor資料夾
  • 可以使用OnEnable, OnInspectorGUI兩個方法,OnEnable會在該GUI出現時執行一次,OnInspectorGUI會執行數次(我還不確定次數的根據)
  • 如果改了Scene裡的物件屬性有時候不會及時更新,可呼叫SceneView.RepaintAll();
  • 可用GUI.changed去判定是否有變更過屬性
  • 基本上你在GUI修改數值是不會被存起來的(play時或play完就會回到原樣),需要用EditorUtility.SetDirty告訴Unity存起來
  • 使用target存取目標物件
  • 可以使用SerializedObject輔助,修改target時SerializedObject取property會是改變前的數值(相關討論 https://answers.unity3d.com/questions/43611/oninspectorgui-using-the-default-object-selection.html)

大概就是這樣,這個練習應該還有一些bug,但基本上我學了不少


cocos2d-x 2.0.3 跨平台專案經驗

Sat, 05 Jan 2013 16:08:15 GMT

注意線程安全

不要經pthread開線程(Native code)還使用JNI呼叫Java Code 基本上是線程安全問題 如果這樣做很容易出問題 有遇過下面的狀況

  1. 字串被卡掉
  2. JNI Call找不到jclass
  3. JNI Call找到jclass,但仍然出錯,加上程式是多執行緒,除錯十分困難

基本上後來都採取pthread傳訊息,schedule再處理的模式

map erase 的使用

map的erase方法不會回傳刪除後下一個元素的iterator(vector會)
曾經遇到一個狀況如下:

for (itMap = _mapData.begin(); itMap != _mapData.end(); ++itMap)
{
    if (...)
    {
        _mapData.erase(itMap);
    }
}

這樣的用法是有問題的
在iOS運行正常,但在Android有可能造成無限迴圈

正確的方式應該如下

for (itMap = _mapData.begin(); itMap != _mapData.end(); )
{
    if (...)
    {  //移除
        _mapData.erase(itMap++);
    }
    else
    {  //不移除
        ++itMap;
    }
}

cocos2d-x文字寬度不同平台差異

CCLabalTTF iOS版跟Android版的中文字(字型黑體)在設定相同字型大小時,實際顯示大小有不同,如果設定寬度交由cocos2d-x去斷行基本上沒有什麼問題,但是如果因為文句美觀而自行設定斷行時,就要注意兩平台寬度不同造成顯示效果的不同。

Android的CCRenderTexture

這個類別在Android常常出狀況,基本上下面這兩個我都遇到過(2.0.3剛出的時候)。
https://www.cocos2d-x.org/news/75 2.0.4修正
https://www.cocos2d-x.org/issues/1544 2.1.1修正

現在看來是都修好了,只是那時候剛換2.0.3的時候真的很慘,不確定何時會修好,就算確定也不能等,所以android用到的部分幾乎都要再另寫一個版本,使用的人在Android版最好多加測試,因為cocos2d-x的開發者也不是能夠測到所有裝置,須仰賴整體社群的幫忙。

cocos2d-x 預設libcurl 不支援SSL

cocos2d-x 中有提供libcurl 的這個URL連線程式庫,不過內建的沒有支援ssl,基於專案的需求所以要更換為有SSL功能的。 原本想要自己編,不過看了一下官方的建議做法,覺得實在太麻煩,後來發現下面這個網址:
https://github.com/dumganhar/libcurl-build
這應該算cocos2d-x官方提供的,可以直接使用。

版本號的設定

一開始iOS的版本號設定是以x.x.x這樣的形式去處理的。 遊戲移植到android後,由於需要在某些版本快速修正bug後出一個新版本,所以在android有些版本versionName是像x.x.x.y這樣。
後來iOS版由於某些原因需要以某版為基礎修正後再出一版,版本號原本是想要跟android一樣,但後來發現iOS版本的專案設定中,版本的設定分為version與build,version的設定只能x.x.x,build則無限制。
因為在這個專案中的版本號會影響到一些跟server取資料時的判定,所以版本號需要相同。
iOS跳號後android也需要跟進,所以在這一點需要注意。


call to OpenGL ES API with no current context

Fri, 02 Nov 2012 15:20:00 GMT

Logcat中的錯誤訊息如下
call to OpenGL ES API with no current context (logged once per thread)

問題起因

簡單的說
不能在OpenGL Thread以外的Thread呼叫OpenGL指令

我遇到的實際情況是使用cocos2dx實作一個按鈕
點擊後會使用JNI呼叫Java以連接其他人提供的程式庫
該程式庫會處理一些事情後經過Listener callback 呼叫 Native Method
該Native Method會使用OpenGL 指令繪圖
此時會出現這個錯誤
可能是其他人的程式庫callback時在UI Thread或者是其他的Thread造成這個問題

解決方式

可以參照以下這篇
https://stackoverflow.com/questions/5234867/using-opengl-from-the-main-thread-on-android
使用GLSurfaceView.queueEvent就可以解決
在cocos2dx中可以直接使用Cocos2dxActivity.runOnGLThread
或者是修改程式碼在callback呼叫Native Method時改變作法不要直接呼叫繪圖指令
等到回到OpenGL Thread時再處理