over 6 years ago

如何在 Android Studio 中使用 NDK 編譯 JNI

定義 Native 方法


  • 新增 NativeLib.java 在下圖中的路徑

  • NativeLib.java

    package com.demo.jnidemo;
    
    public class NativeLib {
        static{
            System.loadLibrary("native");
        }
    
        public native String getHelloMessage();
    }
    

撰寫 Native Code (C/C++)


  • [app/src/main] 路徑下建立 jni 資料夾,並在資料夾中新增檔案: native.cpp。要注意的是,路徑及資料夾名稱都是 Android Studio 的預設規則,需正確對應才能順利編譯。

  • native.cpp

    #include <jni.h>
    
    extern "C" {
    
        JNIEXPORT jstring JNICALL Java_com_demo_jnidemo_NativeLib_getHelloMessage( JNIEnv* env, jobject object){
            return env->NewStringUTF("Hello, JNI.");
        }
    
    }
    

設定 NDK 路徑


如果之前沒有在環境變數中加入 NDK 的路徑,編譯時會遇到 > NDK not configured. 的錯誤。需要在專案根目錄下的 local.properties 檔案中設定 ndk.dir 變數。

sdk.dir=/your-sdk-path
ndk.dir=/your-ndk-path

設定 Android.mk 參數


AndroidStudio 的預設行為,在編譯時會自動生成 Android.mk 檔,路徑位於:app/build/intermediates/ndk/[debug/release]/Android.mk。可以透過設定 app 目錄下的 build.gradle 來修改。

  • build.gradle

    apply plugin: 'com.android.application'
    
    android {
        compileSdkVersion 21
        buildToolsVersion "21.1.1"
    
        defaultConfig {
            applicationId "com.demo.jnidemo"
            minSdkVersion 17
            targetSdkVersion 21
            versionCode 1
            versionName "1.0"
    
            ndk{
                moduleName "native"
                //cFlags "-DANDROID_NDK -D_RELEASE"
                //ldLibs "m", "log", "jnigraphics"
                //abiFilters "armeabi", "armeabi-v7a"
                //stl "stlport_static"
            }
        }
    
        ....
    
  • 在 ndk 區塊中可以設定的參數如下

    • moduleName : 編譯出的 .so 名稱
    • cFlags:對應生成模式
    • ldlibs:對應 LOCAL_LDLIBS,需去掉前綴的 "l"
    • abiFilters:對應 APP_API
    • stl:對應 APP_STL

Build and Run


加上測試程式碼

String str = NativeLib.getHelloMessage();
Log.d("", str);

編譯後在 logcat 可看到訊息

Hello, JNI.
 
almost 7 years ago
  • Update Repository

    git fetch repos --all
    
  • Create a local branch tracking remote branch

    git checkout --track -b feature/test repos/feature/test
    
 
almost 7 years ago

Install MySQL


  • Mac OS 上可直接用 Homebrew 套件管理工具安裝 MySQL

    brew install mysql
    

Start and Stop MySQL Server


  1. 開啟 ~/.bash_profile,加入以下內容

    alias mysqlstart="sudo /usr/local/mysql/support-files/mysql.server start"
    alias mysqlstop="sudo /usr/local/mysql/support-files/mysql.server stop"
    
  2. 之後啟動或關閉 MySQL 可直接使用 mysqlstartmysqlstop 指令。

Install phpMyAdmin


  1. 下載 phpMyAdmin(http://www.phpmyadmin.net/home_page/index.php

  2. 解壓縮後將資料夾命名為 phpMyAdmin,放到 server 根目錄,在瀏覽器輸入

    http://localhost/phpMyAdmin
    

    正常的話,會看到登入畫面。不修改設定直接登入的話,可能會遇到 #2002 Cannot log in to the MySQL server 的錯誤訊息,所以接下來我們要修改一下設定檔中的參數。

  3. 將 phpMyAdmin 目錄下的 config.sample.inc.php 重新命名為 config.inc.php。並打開檔案修改下列內容

    //$cfg['Servers'][$i]['host'] = 'localhost';
    $cfg['Servers'][$i]['host'] = '127.0.0.1';
    
    //$cfg['Servers'][$i]['AllowNoPassword'] = false;
    $cfg['Servers'][$i]['AllowNoPassword'] = true;
    
  4. 以預設帳號 root,密碼為空,即可成功登入。登入後記得修改root密碼。

 
almost 7 years ago

[更新] 升級到 Mac OS 10.10 (Yosemite)後,Apache http://localhost/~username 出現 404 錯誤。


解決方式

  1. 開啟檔案 /etc/apache2/httpd.conf,移除下列幾行的註解。(移除 # 字號)

    #LoadModule php5_module libexec/apache2/libphp5.so
    #LoadModule userdir_module libexec/apache2/mod_userdir.so
    #Include /private/etc/apache2/extra/httpd-userdir.conf
    
  2. 開啟檔案 /etc/apache2/extra/httpd-userdir.conf,移除下列註解。(移除 # 字號)

    #Include /private/etc/apache2/users/*.conf
    
  3. 確認 /etc/apache2/users/ 路徑下,是否有 username.conf 這個檔案,沒有的話則新增檔案,並填入下列內容。

    <Directory "/Users/username/Sites">
        Options Indexes MultiViews
        AllowOverride None
        Require all granted
    </Directory>
    
  4. 修改 /etc/apache2/httpd.conf 檔案中,DocumentRoot 的路徑,需要與第三點 username.conf 中的路徑相同。

    DocumentRoot "/Users/username/Sites"
    <Directory "/Users/username/Sites">
    
  5. Restart Apache Server

    sudo apachectl restart
    

Mac OS 10.8 預設關閉網站分享,需要透過手動的方式開啟。

Start and Stop Apache Server


  • Start

    sudo apachectl start
    
  • Stop

    sudo apachectl stop
    
  • Restart

    sudo apachectl restart
    
  • Check Apcahe Version

    httpd -v
    

System Level Root


預設的網站根目錄

http://localhost/

根目錄中的檔案則存放在以下路徑

/Library/WebServer/Documents/

User Level Root


  1. 檢查是否有 '~/Sites' 這個資料夾,如果沒有請自行建立。

  2. 修改 'username.conf',確認此檔案是否位於

    /etc/apache2/users/
    

    如果沒有則自行建立,命名規則是 "[使用者名稱] + .conf"。(Ex. john.conf)

    sudo vi /etc/apache2/users/john.conf
    

    填入以下內容,記得將 username 換成自己的使用者名稱

    <Directory "/Users/username/Sites/">
    Options FollowSymLinks Indexes MultiViews
    AllowOverride All
    Order allow,deny
    Allow from all
    </Directory>
    
  3. 檢查檔案權限,正常的話應該為

    -rw-r--r--   1 root  wheel  username.conf
    

    如果不是,需更改權限

    sudo chmod 644 username.conf
    
  4. 重新開啟 apache

    sudo apachectl restart
    
  5. 輸入網址確認是否成功

    http://localhost/~username/
    

Turn On PHP


  1. 開啟 httpd.conf

    sudo vi /etc/apache2/httpd.conf
    
  2. 找到 php 的相關設定,並移除註解

    • Before

      #LoadModule php5_module libexec/apache2/libphp5.so
      
    • After

      LoadModule php5_module libexec/apache2/libphp5.so
      
  3. 重新開啟 apache

    sudo apachectl restart
    

References


 
almost 7 years ago

使用 java 開發應用程式,當最後需要將程式交付給使用者時,可將編譯出的 class 封裝成 .jar(Java Archive File)檔。JDK 中有個 jar 工具程式,可以幫助我們完成這件事情。


以一個簡單的範例做說明,假設根目錄下有一個 jar_test 資料夾:

| [jar_test]
|    | [classes]
|    |    | main.class
|    |    | 1.class
|    |    | 2.class
|    | manifest.txt
  • classes 資料夾下是編譯出的 class 檔
  • manifest.txt 的內容如下,指定讀取 jar 檔時要執行的 Main-Class。

    Main-Class: main.class
    
  • 在 windows 環境下,Main-Class: main.class後需再按 Enter 新增一行,否則可能會發生無法讀取的錯誤。


假設 jar 工具程式的路徑已經存在於環境變數中,可在任何路徑下執行。

  • 變換當前目錄至 [jar_Test]

    cd /jar_test
    
  • 執行指令產生 jar 檔

    jar cvfm myapp.jar manifest.txt -C classes /
    

  • 輸入指令執行 jar 檔,或是滑鼠左鍵按兩下直接開啟程式

    java -jar myapp.jar
    
 
about 7 years ago

Dalvik VM


Android 平台的核心之一,支援 .dex (Dalvik Executable)格式的應用程式運行。.dex 是一種壓縮格式,一個 dex 檔案可能包含多個 class,各類別重複的字串和常數只會放一份在 dex 中,節省空間。

每個 Android 應用程式都會運行在獨立的 Dalvik VM 實體中,有自己的運算資源和記憶體,每個 DVM 的最大記憶體空間是固定的,定義在 /system/build.prop 檔案中,相關於 DVM heap 大小的參數有三個:

dalvik.vm.heapstartsize=8m
dalvik.vm.heapgrowthlimit=128m
dalvik.vm.heapsize=512m
  • dalvik.vm.heapstartsize:應用程式啟動後分配到的初始大小,不夠的時候才會再向系統要求配置。

  • dalvik.vm.heapgrowthlimit:單個應用程式可用的最大記憶體空間,超過這個值程式會當掉。

  • dalvik.vm.heapsize:如果在 AndroidManifest.xml 中設定了 android:largeHeap,則最大記憶體空間為 heapsize 定義的值。(要看各家廠商如何實作,不一定有效)

    <application
        ...
                android:largeHeap="true"
        ...
    </application>
    

也可透過 adb 工具查看 heap size 的相關設定值

adb shell getprop dalvik.vm.heapstartsize

Java Object Heap, Bitmap Memory and Native Heap


Android 應用程式上的記憶體可分為三種,分別是Java Object Heap, Bitmap MemoryNative Heap

  • Java Object Heap

    • 在 Java 層 new 出來的記憶體空間,dalvik.vm.heapgrowthlimit限制了 Java Object Heap 的最大值。
  • Bitmap Memory

    • 也稱作 External Memory,用來處理 Bitmap 圖像。在 Android 3.0(HoneyComb)前,Bitmap memory 是在 Native Heap 中分配,但記憶體空間會計入 Java Object Heap 中,所以 Bitmap memory 和 Java Object 占用的空間加起來,還是不能超過 Java Object Heap 的最大值。由於是在 Native Heap 中配置的,因此 bitmap 也無法使用 DVM 的 GC 機制,是由 Skia 圖形處理引擎自己的 GC 機制負責記憶體的釋放。在 HoneyComb 後,Bitmap Memory 就直接在 Java Object Heap 中配置了,可以受到 GC 的控管。
  • Native Heap

    • 在 Native 層 malloc 宣告的記憶體空間,不受到 Java Object Heap 的最大值限制,但還是要避免濫用。

References


 
about 7 years ago

整理 .NET Framework 中的記憶體釋放機制。

Managed vs Unmanaged Resources


首先要先了解託管資源(managed resources)與非託管資源(unmanaged resources)間的差異。託管資源指的是 GC 管理的記憶體空間,當沒有任何參考指向託管資源時,GC 會在適當的時機自動釋放它。而非託管資源則是 GC 不知道的記憶體空間,例如:windows handles(HWND)、database connections 等,使用者必須自行釋放。

Destructor 與 Finalize 間的關係


Finalize(); 不應該直接被調用,而是由類別的 destructor 隱含呼叫。

~MyClass()
{
    DoReleaseMyResource();
}

解構函式會被轉換為以下代碼:

protected override void Finalize()
{
    try
    {
        DoReleaseMyResource();
    }
    finally
    {
        base.Finalize();
    }
}

IDisposable


將記憶體空間交由 GC 控管雖然方便,但缺點是釋放時機無法自行掌握,因此 .Net 提供了 IDisposable 介面,提供及時釋放資源的機制。

IDisposable 中只公開了一個方法 Dispose(),官方提供了標準的實作方式如下:

// Design pattern for a base class.
public class Base: IDisposable
{
    private bool disposed = false;

    //Implement IDisposable.
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Free other state (managed objects).
            }

            // Free your own state (unmanaged objects).
            // Set large fields to null.
            disposed = true;
        }
    }

    // Use C# destructor syntax for finalization code.
    ~Base()
    {
        // Simply call Dispose(false).
        Dispose (false);
    }
}

// Design pattern for a derived class.
public class Derived: Base
{
    private bool disposed = false;

    protected override void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Release managed resources.
            }

            // Release unmanaged resources.
            // Set large fields to null.
            // Call Dispose on your base class.
            disposed = true;
        }

        base.Dispose(disposing);
    }

    // The derived class does not have a Finalize method
    // or a Dispose method without parameters because it inherits
    // them from the base class.
}

IDisposable 實作分析


範例程式碼中有幾個重點:

  1. disposed 旗標判斷是否做過資源釋放的動作。

    public class Base: IDisposable
    {
        private bool disposed = false;
    
        ...
    
        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                // Free your own state (unmanaged objects).
                // Set large fields to null.
                disposed = true;
            }
        }
    }
    

    透過 Dispose(); 釋放資源後,此資源仍可能短暫存在記憶體中,其他物件如果仍有此資源的參考,還是可能發生錯誤引用,所以需要 disposed 旗標來把關。

  2. 覆寫 void Dispose(bool disposing)。Dispose 方法有兩個可能的進入點:

    • 由呼叫端自行呼叫 Dispose(); 方法
    • 由 GC 觸發 destructor,再由 destructor 呼叫 Dispose();

    如果是由使用者呼叫 Dispose 方法,就要自行釋放託管資源。相反地,若是由 GC 觸發,物件中的託管資源已被 GC 釋放,需避免重複釋放資源。

  3. Dispose 方法中加上 GC.SuppressFinalize(this);,表示資源已由物件自行釋放,不會進入解構函式(Finalize())。

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    

References


 
about 7 years ago

[Mac OS] 如何加入 git auto completion


  1. Install bash git completion

    brew install git bash-completion
    
  2. 修改 ~/.bash_profile,加入以下內容

    if [ -f $(brew --prefix)/etc/bash_completion ]; then
        . $(brew --prefix)/etc/bash_completion
    fi
    

__git_ps1 command not found 錯誤的解決方法


 
about 7 years ago

Show git branch

  • Install bash-completion

    brew install bash-completion
    
  • 編輯 ~/.bash_profile,加入以下內容

    # Bash completion
    if [ -f /etc/bash_completion ]; then
        . /etc/bash_completion
    fi
    

    and

    export PS1='\[\033[01;32m\]\u@\h\[\033[01;34m\] \w\[\033[01;33m\]$(__git_ps1)\[\033[01;34m\] \$\[\033[00m\] '
    

Show pending changes

  • 編輯 ~/.bash_profile

    export GIT_PS1_SHOWDIRTYSTATE=1
    

References

 
about 7 years ago

Eclipse 匯入 Git 專案可透過以下步驟完成

  1. File -> New -> Java Project
  2. 取消勾選 Use Default location 選項,按下 Browse 按鈕選擇包含 git 資訊的專案資料夾。
  3. 按下 Finish 按鈕完成匯入。