顯示具有 android 標籤的文章。 顯示所有文章
顯示具有 android 標籤的文章。 顯示所有文章

2018年11月19日 星期一

ndk-build的使用心得

ndk-build的使用心得

前言

  最近將繪圖引擎跨到Android,在繪圖API的部分會面臨到選擇GLES的版本的問題,還有像是GLES3只有64位元支援,如果只靠修改Android.mk與Application.mk的話會沒辦法做出像是組態(Configuration)的效果,所以對ndk-build做了研究,在此做個紀錄。

內容

  ndk-build本身不提供組態(Configuration)的功能,但可以透過參數指定Android.mk與Application.mk,這做法可以做出組態(Configuration)的效果,但只要想想組態越多,Android.mk與Application.mk的數量也會隨著組態而增多,GLES的版本目前有GLES2、GLES3、GLES3.1與GLES3.2,如果用這個方法就會有8個檔案要維護,加上共用的部分還要隔開的成本,做了一下我就放棄這個做法,因為成本有點高。

  在google一陣子後發現ndk-build可以直接從命令列新增或修改在Android.mk與Application.mk的參數,比方說"APP_ABI"這個參數,在入門教學中會把該參數寫在Application.mk,用來決定要建置的ABI,但事實上是可以用ndk-build的命令列來修改,命令如下
ndk-build APP_ABI=arm64-v8a

當用這個命令後,請移除Application.mk裡的"APP_ABI"的設定,不然ndk-build社的數值會被覆蓋,如果要設定多個參數就用空白隔開即可。只靠這個方法來實現組態會不太夠,必須再搭配makefile的if語法,語法的範例如下
ifeq ($(GLES_VERSION),GLES2)
GLES_LIBS := -lGLESv2
endif
ifeq ($(GLES_VERSION),GLES3)
GLES_LIBS := -lGLESv3
endif
ifeq ($(GLES_VERSION),GLES3_1)
GLES_LIBS := -lGLESv3
endif
ifeq ($(GLES_VERSION),GLES3_2)
GLES_LIBS := -lGLESv3
endif

範例中使用"GLES_VERSION"這個變數來決定最後的要載入的LIB,"GLES_VERSION"是自訂的,搭配之前的方法可以在ndk-build的命令列隨喜好來修改,這樣不論是選GLES版本或者是只能建64位元版本都可以解決了!

  ndk-build在指定ABI的版本來建置後,會把非建置版本的ABI移除,例如APP_ABI=arm64-v8a時,x86_64、armeabi-v7a與x86都會被移除,這個問題目前解決的方案不是很好,就是每個ABI都分不同資料夾來建置,可以在ndk-build的命令列修改"NDK_OUT"與"NDK_LIBS_OUT"來完成,這個方法可行,但就是覺得不太優雅,有機會再找找有沒有優雅的解決方案。

  ndk-build的clean有點討厭,它只為清除LIB,但不會清除OBJ,如果需要完整重建的話,就手動清空或是寫個命令來清除。

  最後來說一下命令的部分,我個人是使用Python來執行ndk-build並選擇組態(Configuration),不選擇Shell與bash只是單純地不熟,但Python雖然跨平台,但有新舊版本的問題(Python2.7與Python3),這部分的實現目前就這樣解決,如果有更好的方法以後再來說明。

參考資料

Application.mk
ndk-build

2018年11月12日 星期一

在Android裡開啟Dialog

在Android裡開啟Dialog

前言

  在之前的這篇"在Android實現多個按鍵同時按壓"有提及要做一個輸入Console命令的視窗,該篇說到了開啟子視窗的方法,這篇來實作UI的部分,在此做個紀錄。

內容

  Android並沒有視窗UI,而"Dialog"的功能可以做出像在Windows開子視窗,所以本篇會使用Dialog來實現,在找資料的過程中,還找到了也可以做出類似效果的"AlertDialog",所以方法並不是只有一種,有機會再來說明"AlertDialog"。

  開啟一個"Empty activity"的專案,接著要新增一個"Layout",具體的操作如下圖
在layout資料夾新增xml
在layout資料夾點擊右鍵後,依據上圖的操作新增xml,接著會看到下圖
新增layout的設定視窗
看到新增layout的設定視窗後,更改layout的名稱,本篇使用"sub_dialog",可以自己命名,但要注意不能有大寫的文字。新增完後開啟該xml檔,依據下圖操作
修改layout的內容
依據上圖開啟layout的文字模式,輸入以下xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <EditText
        android:id="@+id/editText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:ems="10"
        android:inputType="textPersonName"
        android:text="Name" />

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="Close" />

</LinearLayout>


這個xml會規劃一個輸入的text與一個關閉的按鈕。

  接著在activity的layout新增一個按鈕,用來開啟Dialog,操作如下圖
新增開啟Dialog的按鈕
按鈕增加完後,接著在Activity裡開啟,範例碼如下
package com.hosee.hellodialog;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.app.Dialog;
import android.util.Log;
import android.widget.EditText;

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

        Button btnOpenDialog = (Button)findViewById( R.id.button2);
        btnOpenDialog.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                subDialog=new Dialog(MainActivity.this);
                subDialog.setTitle("Title");
                subDialog.setCancelable(false);
                subDialog.setContentView(R.layout.sub_dialog);

                Button btnClose=(Button)subDialog.findViewById(R.id.button);
                btnClose.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        EditText editText=(EditText)subDialog.findViewById(R.id.editText);
                        Log.i("HelloDialog","Input:"+editText.getText() );
                        subDialog.cancel();
                    }
                });
                subDialog.show();
            }
        });
    }
}


範例碼有關於View的ID可能會與範例碼的ID不同請依據layout的命名來修改。在"btnOpenDialog"就是Activity用來開啟Dialog的按鈕,為它設定"Click"事件,在"Click"事件中,會看到開啟Dialog的過程,setTitle()可以設定Title的顯示名稱,似乎不能省略,setCancelable()設為false表示當使用者點擊Dialog是窗外的範圍時"不會"關閉Dialog,接著會看使用"subDialog"來執行findViewById(),而不是直接用activity的findViewById(),這裡要注意!然後設定關閉按鈕的Click"事件,這裡會再關閉前列印輸入text的內容後執行"subDialog.cancel()",這道命令會真正的關閉Dialog,在將事件都設定完後可以看到"subDialog.show()",這道命令會開啟Dialog,完成的結果如下圖
執行結果


參考資料


相關文章

在Android實現多個按鍵同時按壓

2018年11月5日 星期一

在Android實現多個按鍵同時按壓

在Android實現多個按鍵同時按壓

前言

  由於繪圖引擎跨到Android時是全螢幕狀態,加上考慮接合Android的事件如Click、Touch...等,這時多了個麻煩,要如何輸入Debug的命令?在PC不論是Window或Linux都有Console視窗,但Android卻沒有,所以目前想到的是在螢幕的左上與右下新增隱形的按鈕,當同時壓下時會出現UI可以輸入命令。這次只記錄同時按壓的部分,在此做個紀錄。

內容

  先在Android studio裡開一個"Empty activity"的專案,開完後到Layout裡新增2個TextView,如下圖
在Layout新增按鍵
按鍵是用TextView,而不是Button,因為TextView當裡面的text是空值時就可以變成隱形的按鈕,範例為了演示而未清空。要注意圖中右邊的ID與對其都要調整,ID的部分為textView1與textView2,而對齊分別為左上與右下。

  調整為Layout後,接著就來偵測同按壓時,範例如下
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.util.Log;
import java.util.Date;
public class MainActivity extends AppCompatActivity {
    private long time1=0;
    private long time2=0;
    private void CheckMultiClick(){
        long delTime=time1-time2;
        if(delTime < 0)
            delTime*=-1;
        //
        if(delTime<1000){
            Log.i("MultiClick","Double click!");
        }
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView tv1=(TextView)findViewById(R.id.textView1);
        TextView tv2=(TextView)findViewById(R.id.textView2);
        //
        tv1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                time1=(new Date() ).getTime();
                CheckMultiClick();
            }
        });
        //
        tv2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                time2=(new Date() ).getTime();
                CheckMultiClick();
            }
        });
    }
}

範例中,先分別為TextView上OnClick事件,可以看到當Clcik發生時會記錄"time",而這個"time"的意義可以參考Java日期時間(Date/Time),紀錄完後喚起"CheckMultiClick()",裡面很簡單,將兩個"time"的差做檢查是否小於1000,這裡的時間差代表兩個"time"差幾毫秒(milliSecs),所以1000代表差1秒就發生同時按壓。

參考資料

Java日期時間(Date/Time)

2018年10月29日 星期一

從OpenGL跨到OpenGLES的心得

從OpenGL跨到OpenGLES的心得

前言

  最近將C++的繪圖引擎跨到Android,PC端使用的是OpenGL4.6,Android端使用的是OpenGLES3.2,目前只完成初步的測試,就是只能用基本的Shader畫三角形。這次我做了個挑戰,由於手機有支援OpenGLES3.2,所以直接跨到OpenGLES3.2,果不其然碰到了不少問題,這裡做個紀錄。

內容

  從OpenGL跨到OpenGLES的差異這裡分成"Context的差異"與"API的差異"兩部分。Context的差異是必然的,因為PC端的OpenGL為Windows與Linux分別準備了WGL與GLX,自然也別期望android平台可以用一樣的方法,而API的差異部分比想像來的少,透過docs.gl可以查詢OpenGL與OpenGLES的差異。

  Context的差異的部分主要有兩大問題,"執行序(Thread)的問題"與"視窗可能被銷毀的問題",執行序(Thread)的問題主要是因為OpenGL與OpenGLES的Context都會認執行序,也就是用哪個執行序創建Context就只能用該執行序來執行繪圖,這個規則在PC時問題不大,但在Android時會有麻煩,由於Android的Activity不提供類似main()的Function,所以要自己做一個執行序(Thread)來當作main(),這時候就可能發生創建Context與繪圖的執行序(Thread)不是同一個,而且不幸的是這件事情發生時不會有任何的報錯!只會得到一個黑畫面而已,一定要確保創建Context、執行繪圖與銷毀Context都是用C++創建的執行序(Thread),接合Android的Activity的事件只能用來通知有事件發生,不能執行任何EGL與OpenGLES的API。視窗可能被銷毀的問題是Android跟Windows與Linux很大的不同,在Windows與Linux把視窗縮到最小是將視窗Hide的意思,但Android很不一樣,當進入休眠或跳出程式時,都會去銷毀視窗!還記得EGL創建Context時用的eglMakeCurrent()嗎?裡面的Surface參數會在進入休眠或跳出程式時失效,所以必須要對該事件做處理,處理的流程可以參考Android 前后台切换与OpenGL(EGL)创建销毁的周期,裡面有個ResetSurface()可以在事件發生時喚起。

  API的差異部分如下:
1.不支援glDepth(),請用glDepthf()來替代
2.不支援GL_TEXTURE_1D,如果需要1D的Texture請用2D替代
3.不支援glMapBuffer(),寫UBO的話請用glBufferSubData()替代
4.GLSL編譯的時候請將"#version 300 es"放在最開頭
5.Extension不互通,如在OpenGL判斷是否支援UBO可以透過Extension檢查,OpenGLES則是版本來決定(OpenGLES3以後就支援)

前4點都還好很容易克服,第五點就比較頭痛了,必須知道各個版本的規格,只能用經驗來克服了。

  最後再說個關於include的問題,如果查詢Android NDK Native APIs時會發現以下畫面
OpenGLES3.2的include
這個include是錯的!如果去NDK把"GLES3/gl3ext.h"打開的話,就會發現這個Header是個只有註解的空檔案,正確的include如下
#include "GLES3/gl32.h"
#include "GLES2/gl2.ext.h"

雖然看起來有些奇怪,但如果不這麼做的話,會沒辦法創建壓縮格式的Texture,如ASTC、PVR...等,因為Marco都定義在"GLES2/gl2ext.h",由於OpenGLES目前只有跨到Andoird,不知這是不是OpenGLES的標準用法,只能日後再確認了。

參考資料

Android NDK Native APIs
docs.gl
Android 前后台切换与OpenGL(EGL)创建销毁的周期

2018年10月15日 星期一

在Android studio裡設定NDK

在Android studio裡設定NDK

前言

  最近打算讓C++的引擎支援Android,所以就安裝許久沒用的Android studio,裝完後發現NDK的用法跟我以前的用法差很多,所以想說可以換回以前的"ndk-build.cmd"嗎?答案是可以的,設定的過程不太好記,所以在此做個紀錄。

內容

  在設定NDK的第一步驟是安裝NDK,Android studio本來就有整合,只是預設不會裝。首先打開SDK Manager,如下圖
打開SDK Manager

接著選取安裝NDK,如下圖
安裝NDK

安裝完後請重新開啟Android studio。

  下一個步驟是設定External Tools,這裡會新增兩個工具,一個是javah,另一個是ndk-build。
打開設定,如下圖
打開設定

接著到External Tools新增工具,如下圖
External Tools新增工具

新增"javah",如下圖
新增"javah"
Program的參數
$JDKPath$\bin\javah.exe

Arguments的參數
-classpath "$Classpath$;" -d $ModuleFileDir$\src\main\jni $FileClass$

Working Directory的參數
$ModuleFileDir$\src\main\java\
接著新增ndk-build",如下圖
新增"ndk-build"

Program的參數
C:\Users\hosee\AppData\Local\Android\Sdk\ndk-bundle\ndk-build.cmd

Working Directory的參數
$ModuleFileDir$\src\main\
Program的參數的參數因為沒marcro,所以是個絕對位置,其實就是NDK的安裝位置,如果不要用Android studio裡的NDK,
可以把這裡的位置改掉。
接著設定打開專案結構,如下
打開專案結構

接著設定JDK,使用Android studio裡面的JDK,如下圖
設定JDK
設定的部分到此結束,接著是在專案中使用NDK。

  開一個新專案後,在gradle.properties新增以下
android.useDeprecatedNdk=true

如圖
修改gradle.properties

然後在build.gradle新增以下
ndk {
  moduleName "HelloNDK"
  ldLibs "android"
}
sourceSets.main {
  jni.srcDirs = []
  jniLibs.srcDir "src/main/libs"
}

如圖
修改build.gradle

修改完後,再來是新增jni的資料夾,如下圖
新增jni資料夾

新增後會跳出"New Android Component",直接Finish即可,如下圖
"New Android Component"視窗

  在專案的Activity裡新增以下
    static {
        System.loadLibrary("HelloNDK");
    }

    public static native String nativeHello();

如圖
新增NDK介面

介面加完後,使用"javah"來新增header檔。

  接著在jni資料夾新增source檔,檔案內容如下
#include "string.h"
#include "jni.h"
#include "com_example_hosee_hellondk_MainActivity.h"
JNIEXPORT jstring JNICALL Java_com_example_hosee_hellondk_MainActivity_nativeHello(JNI* pEnv,jclass jObj)
{
  return (pEnv)->NewStringUTF("Hello NDK!");
}

操作如圖
新增source檔

  接著在jni資料夾新增"Android.mk",內容如下
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := HelloNDK
LOCAL_SRC_FILES := com_example_hosee_hellondk_MainActivity.cpp
LOCAL_LDLIBS += -landroid
include $(BUILD_SHARED_LIBRARY)

操作如圖
新增"Android.mk"

  接著在jni資料夾新增"Application.mk",內容如下
APP_PLATFORM := android-15
APP_MODULES := HelloNDK
APP_ABI := all

操作如圖
新增"Application.mk"

  接著使用ndk-build產生so檔,如下圖
使用ndk-build產生so檔

最後在專案的Activity新增以下
TextView tv = (TextView) findViewById(R.id.TestTextView);
tv.setText(nativeHello() );

操作如圖
使用NDK的Function

這裡要注意TextView的ID要先去Layout裡面加完,加完後就可以使用了。


參考資料

超级简单的Android Studio jni 实现(无需命令行)