[Eclipse]新建立的Android專案出現"R cannot be resolved to a variable"

這問題其實是很詭異的,因為同樣是今年,只是在SDK是使用r20以前的時候,都可以正常建立新專案(這時的Target SDK為2.3.3)。但今天把它更新到r22.0後,就出現了標題所列的問題。

找了很多網路資料,不管是Clean後又Build,還是專案重建,都一樣的結果。

後來找到這篇「Android: r cannot be resolved to a variable」 ,他有提到是因為SDK4.x的專案建立的時候,會在res底下多出了value-v11以及value-v14這兩個資料夾。然後這個時候設定的Minimun Required SDK是設定為2.3.3。所以根據那篇的說法,這樣2.3.3的SDK是不認識那兩個資料夾,所以就變得無法產生R.java。

後來專案刪掉後重建的時候,把Minimun Required SDK選擇成4.2,就正常可以產生R.java了。

後來又為了要確認問題,所以把Minimun Required SDK、Target SDK、以及Compile With都設定為2.3.3。嘿嘿,好玩了,建立出來的專案一樣不能產生R.java。

所以這意思是,SDK Tools用了r22以後的,就不能編譯給2.3.3用的APP嚕?

後記:
結果打完這篇之後,建出來的專案又不正常了。
之後又找了其他參考,有人指出是需要Android SDK Build-tools。所以在更新這個tools之後,r就一切都正常了。


然後如果Unity還在用3.5.7以下的話,Android SDK Platform-tools請不要更新,不然會發生Unity無法編譯apk的慘劇。唯一的解決辦法就是把,Android SDK Platform-tools降版本至16.0.2以下,以及不要安裝Android SDK Build-tools。

(2013/06/27 - 目前確認,造成Unity無法編譯的是Android SDK Build-tools,僅需把Android SDK Build-tools移除掉,Unity就可以正常編譯了。)

[Unity]使用Plugin - JAR將二進位檔寫入至SDCard

Version:
  1.Unity                     : 3.5.7f6
  2.Anddroid                : 2.3.3
  3.Android SDK Tools : r22
  4.Eclipse                  : Juno SR2

    目前想在Unity上,將資料寫入到檔案內,可以使用System.io.File(C#)來進行純文字或是二進位資料的儲存。但是在Android上,如有需將檔案存入SDCard內,則需先設定檔案存取權限。在Android的app開發上,需要去新增android.permission.WRITE_EXTERNAL_STORAGE。這個權限設定之後,就可以進行檔案的讀寫(在4.1以後,則需另外設定讀取權限,android.permission.READ_EXTERNAL_STORAGE)。然而這個權限設定,Unity幫我們做到了,因此我們不需要在另外設定AndroidManifest.xml,僅需將「PlayerSettings」->「Per-Platform Settings」->「Write Access」 設定為「SDCard」即可。


    C#的讀寫檔方式,必須自行指定資料夾的路徑,所以如果當我們要把檔案寫道SDCard內的話,資料夾路徑會是「/mnt/sdcard/...」。如果SDCard的路徑被變更的話,那麼在程式內所指定的路徑就又要重新設定過。

    然而Android有提供取得SDCard的路徑功能,所以這邊就利用JAR的方式,讓Unity可以直接使用Android所提供的API,取得系統內所記錄的SDCard的實際路徑,並將檔案寫入到SDCard內。
 
在JAR內的程式碼為:
package com.drillingsaru.io.sdcard;  
 //Java  
 import java.io.File;  
 import java.io.FileOutputStream;  
 import java.io.IOException;  
 //android  
 import android.os.Environment;  
 import android.util.Log;  
 public class BinaryFileIO   
 {  
 //Write binary data into a file  
     public void WriteData(String FileName, byte[] DataBuf)  
     {  
         if( !this.IsExternalStorageMounted() ) {  
             this.mErrNo = ERR_STORAGE_NOT_MOUNTED;  
             return;  
         }  
         try  
         {  
         //Get the path of root folder (Return = /mnt/sdcard )  
             File rootDir = Environment.getExternalStorageDirectory();  
         //Create a output stream object  
             FileOutputStream outStream = new FileOutputStream(new File(rootDir.getPath(), FileName));  
         //Write Data                          
             outStream.write(DataBuf);  
         //Flush it  
             outStream.flush();  
         //Close self  
             outStream.close();  
             this.mErrNo = ERR_NONE;  
         }catch(Exception e){  
             Log.d("--WriteData--", e.getMessage());  
         }          
     }  
 }  


在Unity的程式碼為
using UnityEngine;  
 using System.Collections;  
 using System.Collections.Generic;  
 public class WriteData2SDCard : MonoBehaviour   
 {  
 #region Definition      
 /// <summary>  
 /// The package name and class name  
 /// package name = com.DrillingSaru.io.sdcard
 /// </summary>  
     public readonly string PACKAGE_CLASS_NAME = "com.drillingsaru.io.sdcard.BinaryFileIO";  
 /// <summary>  
 /// Student information  
 /// </summary>  
     public    class StudentInfo  
     {  
         public    int    Language        = 0;  
         public    int    Math    = 0;  
         public    int    Science    = 0;  
         public StudentInfo(int lang, int math, int science)  
         {  
             this.Language    = lang;  
             this.Math        = math;  
             this.Science    = science;  
         }  

         public StudentInfo(byte[] DataBuf)  
         {  
             this.Set(DataBuf);  
         }

     //轉換成byte陣列
         public byte[] GetBytes()  
         {  
             List<byte> RtnData = new List<byte>();  
             RtnData.AddRange(System.BitConverter.GetBytes(this.Language));  
             RtnData.AddRange(System.BitConverter.GetBytes(this.Math));  
             RtnData.AddRange(System.BitConverter.GetBytes(this.Science));  
             return RtnData.ToArray();  
         } 

     //轉換成結構資料
          public void Set(byte[] DataBuf)
          {
             int Offset = sizeof(int);
             int Shift = 0;
  
             this.Language = System.BitConverter.ToInt32(DataBuf, Shift);
             Shift += Offset;
   
             this.Math = System.BitConverter.ToInt32(DataBuf, Shift);
             Shift += Offset;
   
             this.Science = System.BitConverter.ToInt32(DataBuf, Shift);
             Shift += Offset;      
         }
     }  
 #endregion      
 #region InputData  
 #endregion  
 #region DataMebmer  
     private    StudentInfo            mStudentInfo    = new StudentInfo(500, 5, 10);  
     private    AndroidJavaObject    mDataWriter        = null;  
 #endregion          
 #region MemberFunc  
     private void Init()  
     {          
         this.mDataWriter = new AndroidJavaObject(PACKAGE_CLASS_NAME);  
     }  

 /// <summary>  
 /// write binary data to file
 /// </summary>
     private void WriteData()  
     {  
         if( this.mDataWriter == null )  
             return;  
     //設定傳入參數
         object[] args = new object[2];  
         args[0] = "testfile.bin";  
         args[1] = (object)this.mStudentInfo.GetBytes();  

     //呼叫函式
         this.mDataWriter.Call("WriteData", args);  
     }  

/// <summary>  
/// read binary data from file
/// </summary>
     private void ReadData_Test()
     {
         if( this.mDataWriter == null )
             return;
  
         byte[] RtnList = new byte[1024];
         object[] args = new object[1];
         args[0] = "testfile.bin";
         RtnList = this.mDataWriter.Call("ReadData", args);  
  
         this.mStudentInfo.Set(RtnList);
     }
#endregion          

 #region SystemFunc  
     void Awake()  
     {  
         this.Init();  
     }  
     // Use this for initialization  
     void OnEnable ()   
     {  
         this.WriteData();  
     }  
     void Update ()   
     {  
     }  
 #endregion      
 }  

用了上述的Plugin後,就可以將二進位檔存至SDCard了。

[Unity]Plugin建立過程 - JAR

Version:
  1.Unity                        : 3.5.7f6
  2.Anddroid                 : 2.3.3
  3.Android SDK Tools : r22
  4.Eclipse                     : Juno SR2

 這邊使用簡單的計算範例來呈現,如何建立要給Unity使用的Plugin。 這邊的Plugin是使用JAR。

 1.建立新的Android專案 
    I.在Eclipse介面上,點選「File」->「New」->「Project」。

    II.選擇「Android Application Project」。


    III.設定相關設定,這邊就依個人的需求,自行設定即可。

    IV.專案建立過程中,[Create custom launcher icon]與[Create activity]預設都會被勾選。但是目前並用不到這兩個設定,所以在這裡就把這兩個設定設定為不選擇。



    V.取消[Create custom launcher icon]與[Create activity]後,按下「Finish」按鈕,就可以完成專案建立的流程了。


    VI.建立好的專案架構如下圖


    VII.加入新的[Package]:於[src]上按下滑鼠右鍵->[New]->[Package]


    VIII.設定[Package]的名稱,[Package]的名稱會在Unity內使用到。


    IX.新增[Class]給[Package]在新建立好的[package]上按下滑鼠右鍵->[New]->[Class]


    X.設定[Class]的名稱,這邊設定的名稱之後也會在Unity內使用到。


    XI.建立想要使用的功能的程式碼
         A.Number Adder:
package com.calculator.numberincreasing;  
 public class NumberAdder   
 {  
     public int Add(int base, int factor)  
     {  
         return (base + factor);  
     }  
 }  


         B.Number Multiple:
package com.calculator.numberincreasing;  
 public class NumberMultipler   
 {  
     public int Multiple(int base, int rate)  
     {  
         return (base * rate);  
     }  
 }  


    XII.程式碼編寫完成後,就可以透過[Export]的方式將程式封裝,並交給Unity使用。
           專案上按滑鼠右鍵->[Export]。

    XIII.選擇[Java]->[JAR File]

    XIV.把要匯出的資料勾選起來,這邊預設會勾選所有的資料夾。 然後指定JAR檔要輸出的位置即可。

2.如何在Unity內使用JAR檔內的方法:
I.把JAR檔放到"./Asset/Plugins/Android/"下即可。(Plugins與Android資料夾皆需要自行建立)

II.建立操作用的Script
public class Calculator : MonoBehaviour   
 {  
     private int Org = 10;  
     private int factor = 20;  
     private int Mutiple = 30;  
     private int Added = 0;  
     private int Mutiplier = 0;  
     private AndroidJavaObject    mCal = null;  
     // Use this for initialization  
     void Start ()   
     {          
     //產生NumberAdder物件  
         this.mCal = new AndroidJavaObject("com.calculator.numberincreasing.NumberAdder");  
     //建立傳入參數陣列  
         object[] arglist = new object[2];  
         arglist[0] = (object)this.Org;  
         arglist[1] = (object)this.factor;  
     //呼叫方法,並取得回傳值  
         this.Added = this.mCal.Call<int>("Add", arglist);          
         this.mCal = new AndroidJavaObject("com.calculator.numberincreasing.NumberMultipler");          
         arglist[0] = (object)this.Org;  
         arglist[1] = (object)this.Mutiple;  
         this.Mutiplier = this.mCal.Call<int>("Multiple", arglist);  
     }  

//輸出測試結果
     void OnGUI()  
     {  
         GUI.Label(new Rect(100.0f, 100.0f, 100.0f, 50.0f), string.Format("Org = {0}\nAdded = {1}\nMultipled = {2}", this.Org, this.Added, this.Mutiplier));  
     }  
 }  

   如果要使用JAR檔內的類別,需要透過AndroidJavaObject來產生目標類別的物件。 在產生AndroidJavaObject時所傳入的名稱為Package名稱與Class名稱的合稱。(Package = com.calculator.numberincreasing, Class = NumberAdder. Package.Class = com.calculator.numberincreasing.NumberAdder) 之後再使用AndroidJavaObject.Call()來操作指定的函式。

III.產生apk檔,並安裝到Android裝置內。

  透過上述方式,就可以把一些方法封裝到其它的Package內。然後就可以給其它的專案使用。
  如有需要使用到一些Android的API,也可以透過Plugins的方式來使用。

[Android]存取External Storage

在權限的部分,需在Manifest檔內加入:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
前者為設定可讀寫的權限。是必備的。
後者是設定可掛載或是卸載可移除式儲存裝置。(如沒有加入這個權限,則在進行檔案操作時,會出現"Permission Denied"的錯誤訊息)。

A.之後在進行檔案寫入的動作時
    1.如果是要寫入二進位的資料,可用FileOutputStream.write()直接寫出檔案。
private void WriteData(String Data)  
{
//Check the state of storage
    if( !Environment.MEDIA_MOUNTED.equals((Environment.getExternalStorageState())) ) {  
        Log.d("WriteData", "--SDCard has been removed.--");  
        return;  
    }  

    try  
    {  
    //Get the path of root folder  
        File rootDir = Environment.getExternalStorageDirectory();  

    //Create a output stream object  
        FileOutputStream outStream = new FileOutputStream(new File(rootDir.getPath(), "IOTestFile.dat"));  

    //Write Data  
        byte[] buffer = new byte[BUFFER_SIZE];  
        buffer[0] = 0x30;  
        buffer[1] = 0x31;  
        buffer[2] = 0x32;  
        buffer[3] = 0x33;  
        buffer[4] = 0x34;  
        outStream.write(buffer, 0, 5);  

    //Flush it  
        outStream.flush();  

    //Close self  
        outStream.close();  

    }catch(Exception e){  
        Log.d("--WriteData--", e.getMessage());  
    }          
}  
要注意的是,使用FileOutputStream時,要把FileOutputStream放到try...catch()內。 Environment.getExternalStorageDirectory()可以取得到目前External storage所在的路徑。
    2.如果是要寫入純文字檔,可用FileOutputStream與OutputStreamWriter.append()來將資料寫入檔案內。
private void WriteDataWithOutputStreamWriter()  
{  
    try  
    {  
        String FuncName = "WriteDataWithOutputStreamWriter";  

    //Check the SDCard state  
        if(!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()))  
        {  
            Log.d("WriteDataWithOutputStreamWriter", "======== External SD Card not mounted ===========");  
            return;  
        }  

    //Get the path of the root folder  
        File rootDir = Environment.getExternalStorageDirectory();  

    //Create file object  
        File FileContent = new File(String.format("%s/%s/IOTestFile.dat", rootDir.getParent(), rootDir.getName()));  

        if(!FileContent.createNewFile())  
        {  
            Log.d(FuncName, "Cannot create file");  
        }  
        Log.d("WriteDataWithOutputStreamWriter", String.format("======= Target Path = %s ============", FileContent.getPath()));  

    //Create a output stream object  
        FileOutputStream outStream = new FileOutputStream(FileContent);  

    //Create a output stream writer  
        OutputStreamWriter streamWriter = new OutputStreamWriter(outStream);  

    //write data  
        streamWriter.append("lalalala");  

    //close writer  
        streamWriter.close();  

    //close output stream object  
        outStream.close();  
    }catch(Exception e){  
        Log.d("--WriteDataByOutputStreamWriter--", e.getMessage());  
    }
}  
B.如果是要讀取檔案,則可以用:
    1.讀取二進位檔的內容,則可用FileInputStream來取得資料即可。
private String ReadData()  
{  
    try  
    {  
    //Get the path of root folder  
        File rootDir = Environment.getExternalStorageDirectory();  

    //Create a input Stream Object  
        FileInputStream inStream = new FileInputStream(new File(rootDir.getPath(), "IOTestFile.dat"));  

    //Load the data from target  
        byte[] buffer = new byte[BUFFER_SIZE];  
        int DataLen = inStream.read(buffer);  
        if(DataLen == -1)  
        {  
            Log.d("ReadData", "========= Reading Data has problem. ==================");  
            throw new IOException("Reading Data has problem.");  
        }  

        inStream.close();  

    //Show data  
        TextView text = (TextView)findViewById(R.id.editText1);  
        String OutText = "";  
        for(int i = 0 ; i < 5 ; i++)  
        {  
            Log.d("ReadData", String.format("--------- Data(%x) has been loaded from %s -------------", buffer[i], rootDir.getPath()));  
            OutText += String.format("%x", buffer[i]);  
        }  

        text.setText((CharSequence)OutText);
    }catch(Exception e){  
        Log.d("--ReadData--", e.getMessage());  
    }  
    return "";  
}  

Build docker image from multiple build contexts

Build docker image from multiple build contexts ...