2013-04-08

Unity 3D + Facebook SDK for Android


底下是將 Facebook SDK 打包成 Facebook Plugin 的步驟,執行的平台是 Android,Unity3D 的版本是 3.5。

1. 在這裡取得 Facebook SDK 。
2. 開啟 eclipse 之後在功能表上選擇 File --> Import...,出現對話框之後在 import source 選擇 Existing Android Code Into Workspace。

3. 在 Root Directory 選擇剛剛下載的 SDK 資料夾 (此範例為 /Users/macaronics/facebook-android-sdk-3.0.1),Project to Import 的部分只要勾選 facebook (Facebook SDK) 即可。

4. 按下 Finish 之後在 Package Explorer 應該可以發現 Eclipse 已經自動產生一些檔案 (FacebookSDK/gen) 並且自動編譯好 facebooksdk.jar (FacebookSDK/bin) 了。

5. 建立一個 Unity Project,並將 facebooksdk.jar 及 res 資料夾複製到 Unity 專案的 Assets/Plugins/Android 資料夾底下。(ps. res 資料夾是指 facebook-android-sdk-3.0.1/facebook/res)

6. 複製 android-support-v4.jar  至 Unity 專案的 Assets/Plugins/Android 資料夾底下。( ps. facebook sdk 裡的 android-support-v4.jar 版本似乎太舊所以在編譯時會出問題,因此檔案可以從 android sdk 裡的 extras/android/support/v4 資料夾取得)

7. 建立 AndroidManifest.xml 檔案至 Assets/Plugins/Android 資料夾,內容如下 :
<?xml version="1.0" encoding="utf-8"?>
<manifest android:versionCode="1" android:versionName="1.0" 
          android:installLocation="preferExternal" 
          package="com.macaronics.fbonandroid" 
          xmlns:android="http://schemas.android.com/apk/res/android">
  <supports-screens android:anyDensity="true" android:smallScreens="true" 
                    android:normalScreens="true" android:largeScreens="true" 
                    android:xlargeScreens="true" />
  <application android:label="@string/app_name" 
               android:icon="@drawable/app_icon" android:debuggable="false">
    <activity android:label="@string/app_name" 
              android:name="com.macaronics.iab.overrideActivity"
              android:screenOrientation="portrait" 
              android:configChanges=
"locale|mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"              
              >
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
    <activity android:label="@string/app_name" 
              android:name="com.unity3d.player.UnityPlayerActivity" 
              android:screenOrientation="portrait" 
              android:configChanges=
"locale|mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
              />
    <activity android:label="@string/app_name" 
              android:name="com.unity3d.player.UnityPlayerNativeActivity" 
              android:screenOrientation="portrait" 
              android:configChanges=
"locale|mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale"
              >
      <meta-data android:name="android.app.lib_name" android:value="unity" />
      <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" 
                 android:value="false" />
    </activity>
    <activity android:label="@string/app_name" 
              android:name="com.unity3d.player.VideoPlayer" 
              android:screenOrientation="behind" 
              android:configChanges=
"locale|mcc|mnc|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale" 
              />
    <activity android:name="com.facebook.LoginActivity" android:label="@string/app_name" />
  </application>
  <uses-feature android:glEsVersion="0x20000" />
 
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

</manifest>
值得注意的是這裡設定了 activity "com.facebook.LoginActivity" 以及 Permission "INTERNET"。

8. 利用 git ( https://github.com/douglascrockford/JSON-java.git ) 取得 org.json 原始碼並以下述指令編譯 Java (在相同資料夾輸入指令) :
javac *.java -d .
完成之後輸入 :
jar cvfM json.jar org/
然後將產生的 json.jar 檔案置於 Assets/Plugins/Android 資料夾底下。

9. 建立 overrideActivity.jar ,功用在於 relay 系統資訊給 facebook SDK。首先建立 overrideActivity.java 檔案如下 :
package com.macaronics.iab;

import com.unity3d.player.UnityPlayerActivity;
import android.os.Bundle;
import android.util.Log;
import android.content.Intent;

public class overrideActivity extends UnityPlayerActivity {
    public interface cbEvent{
        public boolean cbEvent(int requestCode, int resultCode, Intent data);
    }
    
    public interface cbSaveInstState{
        public boolean cbSaveInstState(Bundle outState);
    }

    protected cbEvent ie;
    protected cbSaveInstState isis;
    protected Bundle mSavedInstState;

    static protected overrideActivity inst;
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        inst =this;
        mSavedInstState =savedInstanceState;
        
        // print debug message to logcat
        Log.d("Unity::overrideActivity", "onCreate called!");
    }

    @Override
 public void onDestroy(){
        super.onDestroy();
        inst =null;
        Log.d("Unity::overrideActivity", "onDestroy called!");
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data){
        Log.d("Unity::overrideActivity", "onActivityResult called!");
        
        boolean ret =false;
        if (ie !=null){
            try{
                ret =ie.cbEvent(requestCode, resultCode, data);
            }
            catch(Exception e){
                ret =false;
            }
        }

        if (ret ==false){
            super.onActivityResult(requestCode, resultCode, data);
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        Log.d("Unity::overrideActivity", "onSaveInstanceState called!");
    
        boolean ret =false;
        
        if (isis !=null)
        {
            try{
                ret =isis.cbSaveInstState(outState);
            }
            catch(Exception e){
                ret =false;
            }
        }
        
        if (ret ==false){
            super.onSaveInstanceState(outState);
        }
    }
    
    static public void registerOnActivityResultCBFunc(final cbEvent pcbfunc){
        if (inst !=null)
            inst.ie =pcbfunc;
    }
    
    static public void registerOnSaveInstanceSataeCBFunc(final cbSaveInstState pcbfunc){
        if (inst !=null)
            inst.isis =pcbfunc;
    }
    
    static public Bundle getSavedInstanceState()
    {
        if (inst !=null)
            return inst.mSavedInstState;
        
        return null;
    }
} 
編譯指令為 :
javac ./overrideActivity.java -cp /Users/macaronics/android-sdks/platforms/android-10/android.jar:/Applications/Unity3.5.6/Unity/Contents/PlaybackEngines/AndroidPlayer/bin/classes.jar -d .
其中,引入的 library 包含了 android-10/android.jar (Android 2.3.3) ,以及 Unity 的 classes.jar。編譯完成後將其打包成 overrideActivity.jar :
jar cvfM ../overrideActivity.jar com/
將產生的 overrideActivity.jar 放在 Unity 專案的 Assets/Plugins/Android/ 資料夾底下。此外,須注意資料夾 com 要刪除,以免後續執行 jar 時,打包到舊的 com package。

10. 建立 fbWrapper.jar (主要用於介接 facebook SDK 及 Unity),首先建立 fbWrapper.java 檔案如下 :
package com.macaronics;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
import android.util.Base64;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.widget.Toast;

import com.facebook.*;
import com.facebook.widget.WebDialog;
import com.facebook.widget.WebDialog.OnCompleteListener;
import com.facebook.model.GraphObject;

import com.unity3d.player.UnityPlayer;
import com.macaronics.iab.overrideActivity;

import org.json.JSONArray;


public class fbWrapper
{
    private String myHash;
    private String mEventHandler;
    private Activity mActivity;
    private String mAppId;
    
    public fbWrapper(String packageName, String applicationId, String strEventHandler){

        mActivity =UnityPlayer.currentActivity;
        mEventHandler =strEventHandler;
        mAppId =applicationId;
        
        //--------------------
        //fetch keyhash
        try
        {
            PackageInfo info =mActivity.getPackageManager().getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
            for (Signature sig : info.signatures){
                MessageDigest md =MessageDigest.getInstance("SHA");
                md.update(sig.toByteArray());
                myHash =Base64.encodeToString(md.digest(), Base64.DEFAULT);
            }
        }
        catch(NameNotFoundException e){
            Log.d("Unity::fbWrapper()", "error on fetch haskkey -name not found");
        }
        catch(NoSuchAlgorithmException e){
            Log.d("Unity::fbWrapper()", "error on fetch haskkey -no such algorithm");
        }

        
        //--------------------
        //register callback (onActivityResult)
        overrideActivity.registerOnActivityResultCBFunc(
            new overrideActivity.cbEvent(){
                public boolean cbEvent(int requestCode, int resultCode, Intent data)
                {
                    Session.getActiveSession().onActivityResult(mActivity, requestCode, resultCode, data);
                    return false;
                }
            });
        
        //--------------------
        //register callback (onSaveInstanceState)
        overrideActivity.registerOnSaveInstanceSataeCBFunc(
            new overrideActivity.cbSaveInstState(){
                public boolean cbSaveInstState(Bundle outState)
                {
                    Session session = Session.getActiveSession();
                    Session.saveSession(session, outState);
                    return false;
                }
            });
        
        //--------------------
  //facebook
        Bundle savedInstanceState =overrideActivity.getSavedInstanceState();
  Settings.addLoggingBehavior(LoggingBehavior.INCLUDE_ACCESS_TOKENS);
  Session session =Session.getActiveSession();
        if (session == null) {
            if (savedInstanceState != null) {
                session = Session.restoreSession(mActivity, null, statusCallback, savedInstanceState);
                Log.d("Unity::onCreate()", "Session.restoreSession()");
            }
            if (session == null) {
             session = new Session.Builder(mActivity).setApplicationId(mAppId).build();
                Log.d("Unity::onCreate()", "new Session.Builder(this)");
            }
            
            Session.setActiveSession(session);
            if (session.getState().equals(SessionState.CREATED_TOKEN_LOADED)) {
                session.openForRead(new Session.OpenRequest(mActivity).setCallback(statusCallback));
                Log.d("Unity::onCreate()", "openForRead()");
            }
        }        
    }

    public void dispose()
    {
        Log.d("Unity::dispose()", "dispose called");
    }
    
    public boolean isSessionOpened()
    {
        Session session =Session.getActiveSession();
        if (session !=null && session.isOpened()==true && session.getAccessToken() !=null)
        {
            return true;
        }
        
        return false;
    }

    public String getHash()
    {
        return myHash;
    }
    
    public void login()
    {
        Session session = Session.getActiveSession();
        if (!session.isOpened() && !session.isClosed()) {
            Log.d("Unity::onLogin()","session.openForRead()");
            session.openForRead(new Session.OpenRequest(mActivity).setCallback(statusCallback));
        }else if (session !=null){
            Log.d("Unity::onLogin()","Session.openActiveSession()");

            //regenerate session
            Session newsession = new Session.Builder(mActivity).setApplicationId(mAppId).build();
            Session.setActiveSession(newsession);
            newsession.openForRead(new Session.OpenRequest(mActivity).setCallback(statusCallback));
        }
        else
        {
         Log.d("Unity", "error -session ==null");
        }
    }
    
    public void logout()
    {
     Log.d("Unity::onLogout()","on logout called !");
        Session session = Session.getActiveSession();
        if (!session.isClosed()) {
            session.closeAndClearTokenInformation();
            Log.d("Unity::onLogout()","session.closeAndClearTokenInformation()");
        }
    }
    
    public void invokeQuery(String fqlQueryStr)
    {
     Session session =Session.getActiveSession();
     if (session.isOpened()==false)
     {
      Log.d("Unity::invokeQuery()", "invalid session");
      return;
     }

     Bundle params =new Bundle();
     params.putString("q", fqlQueryStr);

     final Request request =new Request(session,
       "/fql",
       params,
       HttpMethod.GET,
       new Request.Callback(){
        public void onCompleted(Response response){
                        GraphObject graphObject = response.getGraphObject();
                        if (graphObject !=null)
                        {
                            if (graphObject.getProperty("data")!=null &&
                                mEventHandler !=null)
                            {
                                try{
                                    String arr =graphObject.getProperty("data").toString();
                                    JSONArray pj =new JSONArray(arr);
                                    UnityPlayer.UnitySendMessage(mEventHandler, "msgReceiver", "{\"ret\":\"true\",\"dat\":"+pj.toString()+"}");
                                }
                                catch(Exception e)
                                {
                                    if (mEventHandler!=null)
                                        UnityPlayer.UnitySendMessage(mEventHandler, "msgReceiver", "{\"ret\":\"false\"}");
                                }
                                
                            }
                            else
                            {
                                if (mEventHandler!=null)
                                    UnityPlayer.UnitySendMessage(mEventHandler, "msgReceiver", "{\"ret\":\"false\"}");
                            }
                            
                        }
                        else
                        {
                            if (mEventHandler!=null)
                                UnityPlayer.UnitySendMessage(mEventHandler, "msgReceiver", "{\"ret\":\"false\"}");
                        }
                        
        }

       });

     Log.d("Unity::invokeQuery()", "send fql request...");
        
        mActivity.runOnUiThread(new Runnable(){
            public void run(){
                Request.executeBatchAsync(request);                
            }
        });


    }

    public void sendRequest(String reqMessage)
    {
     final Session session =Session.getActiveSession();
     if (session.isOpened()==false)
     {
      Log.d("Unity::SendRequest()", "invalid session");
      return;
     }

     final Bundle params =new Bundle();
     params.putString("message", reqMessage);

     final Activity act =mActivity;

        mActivity.runOnUiThread(new Runnable(){
            public void run(){
                
                WebDialog requestDialog =(
                    new WebDialog.RequestsDialogBuilder(act,
                        session.getActiveSession(),
                        params))
                        .setOnCompleteListener(new OnCompleteListener(){

                            @Override
                            public void onComplete(Bundle values,
                                    FacebookException error){
                                if (error !=null)
                                {
                                    if (error instanceof FacebookOperationCanceledException){
                                        Toast.makeText(act.getApplicationContext(),
                                        "Request cancelled",
                                        Toast.LENGTH_SHORT).show();
                                    }
                                    else
                                    {
                                        Toast.makeText(act.getApplicationContext(), "Network Error", Toast.LENGTH_SHORT).show();
                                    }
                                }
                                else
                                {
                                    final String requestId =values.getString("request");
                                    if (requestId !=null)
                                    {
                                        Toast.makeText(act.getApplicationContext(),
                                        "Request sent",
                                        Toast.LENGTH_SHORT).show();
                                    }
                                    else
                                    {
                                        Toast.makeText(act.getApplicationContext(),
                                        "Request cancelled",
                                        Toast.LENGTH_SHORT).show();
                                    }
                                }
                            }
                    })
                    .build();
                requestDialog.show();
                
            }
        });

    }
    
    private Session.StatusCallback statusCallback = new SessionStatusCallback();
    private class SessionStatusCallback implements Session.StatusCallback {
        @Override
        public void call(Session session, SessionState state, Exception exception) {
            if (state.isOpened())
            {
                Log.d("Unity::onSessionStateChange()", "state.isOpened()");
            }
            else
            {
                Log.d("Unity::onSessionStateChange()", "state.isOpened() ===false");
            }
        }
    }

}
其中除了 login 及 logout 外,還提供了兩個重要的功能,一是 FQL Query,另一個是 Requests Dialog。執行編譯的指令為 :
javac ./fbWrapper.java -cp /Users/macaronics/android-sdks/platforms/android-10/android.jar:/Applications/Unity/Unity.app/Contents/PlaybackEngines/AndroidPlayer/bin/classes.jar:./facebooksdk.jar:./overrideActivity.jar:./android-support-v4.jar:./json.jar -d .
其中,引入的 library 包含了 android-10/android.jar (Android 2.3.3),Unity 的 classes.jar,facebooksdk.jar,先前編譯的 overrideActivity.jar,json.jar,以及 android-support-v4.jar。編譯完成後將其打包成 fbWrapper.jar :
jar cvfM ../fbWrapper.jar com/
將產生的 fbWrapper.jar 放在 Unity 專案的 Assets/Plugins/Android/ 資料夾底下。直至目前為止,資料夾底下應該有這些檔案 :

11. 建立 fbWrapper.cs ,讓 fbWrapper class 更適用於 Unity 的 Component 架構之環境,程式碼如下 :
using UnityEngine;

using System.Collections;
using System.Collections.Generic;

public class fbWrapper : MonoBehaviour {

    AndroidJavaObject fbWrapperObj =null;
    public static fbWrapper inst =null;

 // Use this for initialization
 void Start () {
        inst =this;
 }

    void OnApplicationQuit(){
        dispose();
        inst =null;
    }

    public bool init(string unity_bundle_id, string fb_app_id)
    {
        dispose();
        fbWrapperObj =new AndroidJavaObject("com.macaronics.fbWrapper", new object[3]{unity_bundle_id, fb_app_id, "fbWrapper"});

        return true;
    }

    public void dispose()
    {
        if (fbWrapperObj !=null)
        {
            fbWrapperObj.Call("dispose");
            fbWrapperObj.Dispose();
            fbWrapperObj =null;
        }
    }

    void msgReceiver(string msg)
    {
        if (tmpCBFunc !=null)
        {
            Dictionary<string, object> cache =(Dictionary<string, object>)MiniJSON.Json.Deserialize(msg);
            if (cache.ContainsKey("ret")==true)
            {
                string ret =(string)cache["ret"];
                if (ret=="false")
                {
                    tmpCBFunc(false, null);
                }
                else
                {
                    if (cache.ContainsKey("dat")==true)
                    {
                        tmpCBFunc(true, cache["dat"]);
                    }
                }
            }
            else
            {
                tmpCBFunc(false, null);
            }
        }
    }

    //目前是否已登入
    public bool isSessionAvailable()
    {
        if(fbWrapperObj==null)
            return false;

        return fbWrapperObj.Call<bool>("isSessionOpened");
    }

    //登入
    public bool login()
    {
        if (fbWrapperObj !=null)
        {
            fbWrapperObj.Call("login");
            return true;
        }

        return false;
    }

    //登出
    public bool logout()
    {
        if (fbWrapperObj !=null)
        {
            fbWrapperObj.Call("logout");
            return true;
        }
        return false;
    }

    public string getHashKey()
    {
        string myhash =fbWrapperObj.Call<string>("getHash");
        if (myhash ==null)
            myhash ="package name not found";

        return myhash;
    }

    public delegate void queryCBFunc(bool ret, object data);
    protected queryCBFunc tmpCBFunc =null;
    public bool invokeQuery(string fql, queryCBFunc pcbfunc)
    {
        tmpCBFunc =pcbfunc;
        if (fbWrapperObj !=null)
        {
            fbWrapperObj.Call("invokeQuery", new object[1]{fql});
            return true;
        }
        return false;
    }

    //送出邀請
    public bool sendRequest(string message)
    {
        if (fbWrapperObj !=null)
        {
            fbWrapperObj.Call("sendRequest", new object[1]{message});
            return true;
        }
        return false;
    }

}
在 Unity 建立一個 GameObject 並命名為 fbWrapper,然後將 fbWrapper.cs 設定成此 GameObject 之 Component。

此外值得注意的是,函數 init 的第一個參數是 Unity 之 Bundle Identifier,第二個參數是 Facebook App ID,此 ID 應該在建立 Facebook App 時,由 Facebook 所提供,如圖所示 :

函數 getHashKey 所回傳的字串必須設定至 Native Android App 之 Key Hash 欄位 ,如圖所示 :

12. 底下是簡單的 fbWrapper 應用範例,建立一個  C# script 名稱為 test.cs 並設定成為 GameObject "Main Camera" 之 Component。
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class test : MonoBehaviour {

 // Use this for initialization
    string myhash =null;
 void Start () {
        fbWrapper.inst.init("com.macaronics.fbonandroid", "FB_APP_ID");
        myhash =fbWrapper.inst.getHashKey();
 }

    void OnGUI()
    {
        if (GUI.Button(new Rect(10,10,100,100), "login"))
        {
            fbWrapper.inst.login();
            myhash =fbWrapper.inst.getHashKey();
        }

        if (GUI.Button(new Rect(10,120,100,100), "logout"))
        {
            fbWrapper.inst.logout();
            queryresult =null;
            myhash =null;
        }

        if (fbWrapper.inst.isSessionAvailable())
        {
            if (GUI.Button(new Rect(10,230,100,100), "invoke query"))
            {
                fbWrapper.inst.invokeQuery("SELECT uid, name, pic_square, is_app_user FROM user WHERE uid IN (SELECT uid2 FROM friend WHERE uid1 =me())", queryCB);
            }

            if (GUI.Button(new Rect(10,340,100,100), "send request"))
            {
                fbWrapper.inst.sendRequest("check out my app !");
            }
        }

        if (myhash !=null)
        {
            GUI.Label(new Rect(120, 10, Screen.width-120, 40), "Hash Key: "+myhash);
        }
        if (queryresult !=null)
        {
            GUI.Label(new Rect(120, 50, Screen.width-120, Screen.height-50), queryresult);
        }
    }

    string queryresult =null;
    void queryCB(bool ret, object data)
    {
        string tmpstr ="";
        List<object> OBJARR =(List<object>)data;
        for (int i=0;i<OBJARR.Count;++i)
        {
            
            Dictionary<string, object> items =(Dictionary<string, object>)OBJARR[i];
            foreach(KeyValuePair<string, object> item in items)
            {
                tmpstr +=(item.Key+":"+item.Value+"\n");
            }
        }

        queryresult =tmpstr;
    }
}
將 Script 內的 FB_APP_ID 換成當初建立 FB APP 時,Facebook 給予的 App ID。此外,"com.macaronics.fbonandroid" 是本範例所設定之 Bundle Identifier 。另外,呼叫 invokeQuery 函數的第一個參數是 FQL 字串,第二個參數是作為回傳結果用的 Callback Function,回傳的結果字串屬於 JSON 格式,範例把字串內容解析並顯示出來。

13. 設定 Untiy 載入 Scene 之後,各 Script 的執行順序,將 fbWrapper 設定早於預設的時間 (Edit --> Project Settings --> Script Execution Order)。

14. 當一切設定就緒之後 (記得設定 Player Settings 之 Other Settings 的 Bundle Identifier),嘗試 Build apk 檔案,Build 完成之後,在 Unity 專案資料夾下會多出一個 Temp 資料夾,在路徑 Temp/StagingArea/gen/com/macaronics/fbonandroid/ 找到檔案 R.java。

打開此檔案,將 package com.macaronics.fbonandroid; 改為 package com.facebook.android; 之後另存成 R.java 在別的資料夾。

15. 編譯 R.java :
javac R.java -d .
打包成為 jar 檔案 :
jar cvfM facebooksdk_R.jar com/
將檔案放在 Unity 專案的 Assets/Plugins/Android/ 資料夾底下。

16. 重新編譯 apk 檔案。

此範例的原始碼可以從這裡 ( http://github.com/phardera/unity3d_fb_android.git ) 取得。

57 則留言 :

  1. 你好,想請問一下,session.isOpened() 為什麼一直都是回傳false呢??
    上網查了資料都說是hash key的問題,但我也照著你的做法去facebook設定hash key了
    但還是一直回傳false 想請問一下是為什麼??感謝

    回覆刪除
    回覆
    1. 按了 login 按鈕之後有出現 facebook 登入頁面,登入之後也都確認授權了,回到 unity 仍然 false 嗎 ?

      刪除
    2. 抱歉,後來發現是因為我在測試東西時把
      Session.getActiveSession().onActivityResult(mActivity, requestCode, resultCode, data);
      註解掉了....拿掉就OK了..
      不過我又碰到一個問題,就是我無法取得user name,除了user id 外,其他的我取到都是null,這是為什麼呢??我不是用fql的語法去取的,我是用newMeRequest,請問是有什麼要做設定嗎???感謝你的幫忙^^

      刪除
    3. 您好, 不知您的問題解決了嗎 ? 我剛試了一下,至少應該可以取得 Id, Name, 及 Username,相關的程式碼已經更新到 GIT 上,您參考看看~

      刪除
    4. 謝謝你,問題解決了,
      後來發現是logcat無法秀中文...所以有中文的都秀不出來...真是搞笑了 orz

      刪除
  2. 不好意思,我想請教一下,為什麼我的只顯示package name not found,
    是否還要設定甚麼呢??

    回覆刪除
    回覆
    1. 您好 ! 您可以檢查一下所有 "com.macaronics.fbonandroid" 字串的地方是否正確,此外,在 Player Settings (從 Edit->Project Settings-> Player),的 Other Settings 分頁裡的 Bundle Identifier 也確認是否輸入此字串! ~感謝

      刪除
  3. 請問一下~我執行你的範例之後,Hash Key不是我註冊的Hash Key,要如何變成我註冊的Hash Key呢??

    回覆刪除
    回覆
    1. 抱歉我是新手所以問題很多,但我很想學會,我想問一下,

      8. 利用 git ( https://github.com/douglascrockford/JSON-java.git ) 取得 org.json 原始碼並以下述指令編譯 Java (在相同資料夾輸入指令) :

      那麼語法是要打在哪個資料夾目錄下呢??下載來的git主要是在做甚麼的呢??麻煩了謝謝你~

      刪除
    2. 1.git下載檔案主要是做甚麼用的??
      2.指令是要下在哪個路徑呢?

      刪除
    3. 您好 ~ 原生 android 程式的 Key Hashes 欄位可以增加多筆的 Hash ,所以您再追加上去就可以了~

      刪除
    4. 您好~ 關於取得 JSON-java.git 的地方,您可以在任意的資料夾執行...

      例如您在某個 test 資料夾內 :
      1. 確認安裝好 git (http://git-scm.com),關於 git 方面的知識可以參考網路上的教學 :)
      2. 輸入 git clone https://github.com/douglascrockford/JSON-java.git (完成後應該會增加一個 JSON-java 資料夾)
      3. 進入 test/JSON-java/ 底下,輸入 " javac *.java -d . ",然後接著輸入 " jar cvfM json.jar org/ " (完成後資料夾內會多出檔案 json.jar )
      4. 將檔案複製到資料夾 Assets/Plugins/Android 後繼續第 9 步驟

      刪除
    5. 不好意思,我有在Facebook developers原生 android 程式的 Key Hashes 欄位Key上我的Key Hashes,但顯示出來始終是OeZ+....原本的那個,跟我的不一樣,還有其他需要設定的嗎?

      刪除
    6. 您好~ 您意思是說例如 : 您在 facebook 上輸入 key hash : abcd ,但手機上仍然顯示 oez+.. 嗎 ?... 那您可能有些誤解,這裡的意思是把手機上顯示的 hash : oez+... 直接輸入到 facebook 上的 hash 欄位即可 !

      刪除
    7. 非常感謝您~~

      刪除
  4. Hi~
    This Good Sample Thank. but i got following error
    java.lang.NoClassDefFoundError: com.facebook.android.R$layout

    this my blog post -> http://westwoodforever.blogspot.kr/2013/05/unity3d-facebook-javalangnoclassdeffoun_31.html
    check it plz~ ^^

    回覆刪除
    回覆
    1. Hi~
      I think the error may be caused by file "R.java". Because of the file should be auto generated by Unity and can only be used by itself. In the other words, you cannot just replace the file "R.java" with another one or use the file in another project.

      Please make sure the following steps are completed in order to create file "R.java" correctly :
      1. Copy foler "res" in facebook sdk (ex: facebook-android-sdk-3.0.1/facebook/res) to folder "Assets/Plugins/Android". And it should be look like : "http://2.bp.blogspot.com/-I5ofp4IsCpc/UWBvjNo_HgI/AAAAAAAAEAE/-03BkqdoO5k/s1600/unity3dfbplugins04.jpg".
      2. Push the "Build" button in Unity to create apk file.
      3. Checkout the Folder "Temp/StagingArea/gen/com/macaronics/fbonandroid". The "R.java" file should be there.
      4. Open the file and change the line : "package com.macaronics.fbonandroid;" to "package com.facebook.android;"
      5. Compile the R.java file to .jar format
      6. Put the .jar file to folder "Assets/Plugins/Android/".
      7. Rebuild apk file.

      刪除
    2. First, thank you for answer. It was as described above, but an error still occur. When I use the https://github.com/phardera/unity3d_fb_android/blob/master/Assets/Plugins/Android/facebooksdk_R.jar you made, it works fine.
      And then you will need to download the https://github.com/phardera/unity3d_fb_android/blob/master/Assets/Plugins/Android/src/R.java, I was using. Same problem occurs again. What is the problem?

      刪除
    3. Apologies for the delay in replying to your post.
      Do you mean you have tried using the "facebooksdk_R.jar" file, and it works fine.
      Then you try to compile "facebooksdk_R.jar" file yourself from "R.java", but it crash when executing. I have just confirm that file, it should OK.

      刪除
    4. Yes, I am. facebooksdk_R.jar works well. However, when used with I compile the R.java of you, it will not work.

      刪除
    5. 一樣的問題,只有使用下載下來的 facebooksdk_R.jar 才能 work,打開 jar 來看只有 R.class 不一樣

      刪除
  5. 請問我想作Facebook 塗鴉牆的部分,要怎麼著手呢?

    回覆刪除
    回覆
    1. 您好~
      不知您的意思是否為 Publish To Feed ? SDK 的使用方式可以參考 :
      (http://developers.facebook.com/docs/howtos/androidsdk/3.0/publish-to-feed/)
      晚點我再新增此功能並更新至 git !

      刪除
    2. 像是這個... Post to Wall

      http://developers.facebook.com/docs/tutorials/androidsdk/3.0/games/feed/

      刪除
    3. 您好, 我已經增加了這個功能, 並且更新到 git 上去了, 您參考參考~~

      刪除
  6. 你好,问个问题,我的eclipse项目将facebook android sdk作为库文件使用,在gen文件下包含两种R.java,一个是com.facebook.android的,一个是我自己项目的,并且自己项目的R.java已经有facebook中资源的字段,但是当我打包成class.jar包后,放入unity项目的 Assets/Plugins/Android/文件夹下,之前以为这样就好了,后来生成的apk,运行后还是报错,所以就想着应该是资源内容没有包含在 Assets/Plugins/Android/底下,可是当我把facebook那些资源文件夹和我自己的资源合并时,Untiy一直报“Failed to re-package resources with the following parameters”这种错误,我在网上找了些法子,都不行,特来求助,先谢过了。

    回覆刪除
    回覆
    1. 您好,
      R.java 檔案應該要使用 Unity 產生的才不會有問題,將 facebook sdk 的 res 以及您自己項目的 res 資料夾統一複製到 Assets/Plugins/Android/res 裡,利用 Unity Build apk 完成之後可以在 Unity 專案的 Temp/StagingArea/gen// 找到 Unity 編譯的 R.java。
      中間編譯時若是出現 resources 相關的問題多半是 res 資料夾內 layout 或其它 xml 檔案所連結的圖案或字串檔案找不到。

      刪除
    2. 先谢了。现在又加入了个library工程,然后出现一个老问题,就是eclipse项目中的R.java中string中的com_facebook_loading和用unity生成R.java中相应字段的数值是不同的,导致app 运行时,就会报android.content.res.Resources$NotFoundException: String resource ID #0x7f070011,只是ID数值不对。 我猜unity中res和eclipse项目中的res不完全一致,但是在我加入新的library project之前也是不一致,可两者数值是相同的,现在只不过多加了个library project中的资源,又出错了。 不知道为什么,我已经把新的library project中的res合并到unity项目底下的res了

      刪除
    3. 困扰了两天的问题,今天终于解决了,原来是引用库先后顺序问题, 新引用的库工程有自己的资源ID, 导致引用之后,项目生成的资源ID要根据这个新引用的库ID值进行整合,所以才更改了之前facebook相关的资源ID,无语了... 折磨了哥两天了...

      刪除
  7. 试了一下午, 算是找到问题了,但还是不知道原因。 我把facebook sdk res/layout文件夹下的xml整合到我的layout底下时,unity 出版本 就会报上面那个错误,其他的资源整合进去,没报错。

    回覆刪除
  8. 請問一下
    為什麼按下LGOIN後
    程式就出錯
    然後結束
    謝謝

    回覆刪除
    回覆
    1. 找到原因了是facebooksdk_R.jar的問題
      用自己編譯的會出錯然後結束
      請問一下
      我按了LOGIN後
      要求許可後按OK
      又跳回UNITY程式裡
      這樣算是正確嗎

      刪除
    2. 您好, 登入動作變得簡單是 Facebook single sign-on 的特色, 此外, 您也可以在登入後檢查 "session 是否建立", 來確定動作是否正確.

      刪除
    3. 感謝回復
      原來是KEY HASH的問題
      (I和L搞錯)

      刪除
    4. 不好意思想再請問一下
      key hash
      是依機器而不同嗎
      因為我用FB的範例所顯示的key hash和你的程式給的key hash是一樣的
      如果是因機器而變得化
      別的手機執行程式不就會發生錯誤

      謝謝

      刪除
    5. 您好, key hash 的值是依照您 build apk 檔案時, 簽署用的 private key, 所產生, 因此與手機硬體沒有關係喔

      刪除
    6. 謝謝回復
      請問一下
      如果我要UNITY的JAVA呼叫fbWrapper的化
      該怎麼做
      像C#直接打fbWrapper.inst.login();的化
      他顯示錯誤如下
      Unknown identifier: 'fbWrapper'.

      謝謝

      刪除
    7. 您好,
      unity 顯示找不到 fbWrapper, 是否在步驟 11, 建立 fbWrapper.cs 的地方遺漏了 ?

      刪除
  9. 請問
    fbWrapper.java 的檔要放在哪
    不然編譯都一直出錯
    謝謝

    回覆刪除
    回覆
    1. 您好,
      編譯 fbWrapper.java 在任何資料夾都可以執行, 但要注意後面 -cp 帶的參數的相對路徑要正確, 是否可以讓我看看顯示錯誤的資訊 ?

      刪除
    2. 她顯示太多這是一部分

      symbol: class Session
      location: class fbWrapper
      .\fbWrapper.java:246: error: cannot find symbol
      final Session session =Session.getActiveSession();
      ^
      symbol: variable Session
      location: class fbWrapper
      .\fbWrapper.java:249: error: cannot find symbol
      Log.d("Unity::SendRequest()", "invalid session");
      ^
      symbol: variable Log
      location: class fbWrapper
      .\fbWrapper.java:253: error: cannot find symbol
      final Bundle params =new Bundle();
      ^
      symbol: class Bundle
      location: class fbWrapper
      .\fbWrapper.java:253: error: cannot find symbol
      final Bundle params =new Bundle();
      ^
      symbol: class Bundle
      location: class fbWrapper
      100 errors
      謝謝

      刪除
    3. 您好,
      看起來似乎是因為編譯時, -cp 後面帶的 jar 路徑檔案找不到...

      刪除
    4. 謝謝回復
      我在試試

      想在請問一下
      我用了sendRequest函數
      FACEBOOK也寄出sendRequest
      也有顯示平板上也Request sent
      但我朋友卻沒有收到任何邀請
      請問是哪裡出錯了

      刪除
  10. 你好,我在第9歩的地方使用javac編譯但是他對classes.jar這個編譯怎麼編都編不過
    我已經把複製到同一個資料夾下也一樣

    回覆刪除
    回覆
    1. 您好, 可否提供部分編譯時的錯誤訊息以及執行環境資訊 ?

      刪除
  11. 貌似他根本沒找到classes.jar 單是我找不出原因
    輸入 javac fbWrapper.java -cp ./android.jar:./classes.jar -d .
    ^
    symbol: class Request
    location: class fbWrapper
    .\fbWrapper.java:175: error: cannot find symbol
    HttpMethod.GET,
    ^
    symbol: variable HttpMethod
    location: class fbWrapper
    .\fbWrapper.java:176: error: package Request does not exist
    new Request.Callback(){
    ^
    .\fbWrapper.java:213: error: cannot find symbol
    Log.d("Unity::invokeQuery()", "send fql request...");
    ^
    symbol: variable Log
    location: class fbWrapper
    .\fbWrapper.java:217: error: cannot find symbol
    Request.executeBatchAsync(request);
    ^
    symbol: variable Request
    .\fbWrapper.java:226: error: cannot find symbol
    final Session session =Session.getActiveSession();
    ^

    錯誤訊息大概像這樣。

    回覆刪除
    回覆
    1. 抱歉 忘記說 如果只有把android.jar放進去編譯錯誤就減少了,只剩下少了classes.jar的部分會報錯,但是只要一把classes.jar放入編譯就掛了

      刪除
    2. 您的執行環境是 osx or windows ? 若是在 windows 底下, 試著把 javac 指令中的 ":" 要改成 ";" 看看能不能成功。例如 : javac fbWrapper.java -cp android.jar;classes.jar -d .

      刪除
  12. 問題解決了謝謝,我想請問一下,我目前遇到開發unity(android平台),需要fb的東西,也需要用到其他的plugin(一個簡單的android Service),所以我目前無法使用基本的facebook提供給unity的sdk,我現在試著把您提供的這個部分跟我自己寫的部分整合起來,那是否從你overrideActivity那個地方去bind的住那個service.

    回覆刪除
    回覆
    1. 我對整個android開發沒有太熟悉,是要用到才來研究...
      之前單純引入plugin是去呼叫
      AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
      AndroidJavaObject jo =
      jc.GetStatic("currentActivity");
      這樣的方式去做的,可以請您指點一下,不好意思一直打擾

      刪除
    2. 您好, 我在這裡使用 overrideActivity 主要是讓 plugins 可以接收一些系統的 callback 呼叫 (例如 : onActivityResult ) , unity 與 plugins 主要還是透過 AndroidJavaObject 去呼叫 fbwrapper 來溝通及執行 fb 方面的事情。
      我的想法是讓這些 plugins 盡量各自獨立會是比較推薦的作法,因此若您要整合 plugins 則要看這些 plugins 是否有共同的部分 (例如 : overrideactivity 裡面的那些 function) ,再做整合。

      刪除
    3. 但是Android Service不是一定要Activity去呼叫嗎,因為overrideActivity是MainActivity,所以我才會想說直接在這去bind service,不然不是得在寫個activity去bind service,這樣不是就要切換activity,還是有其他方式可以分開寫,去達到獨立各plugin的作法

      刪除
    4. 您好,
      假若 plugins 需要用到 activity 的一些功能例如 onActivityResult, onSaveInstanceState,... 等,可能就沒辦法完全獨立開來實作了。此外 main activity 可以透過 UnityPlayer.currentActivity 取得,因此就算不實作 overrideActivity 也可以取得 main activity。

      刪除
  13. 作者已經移除這則留言。

    回覆刪除
  14. 你好,
    (版本Unity4.6.9) Crashed 可以請教是哪的問題嗎 ?

    Caused by: java.lang.RuntimeException: Unable to resume activity {com.macaronics.fbonandroid/com.facebook.LoginActivity}: android.content.res.Resources$NotFoundException: String resource ID #0x7f050011

    回覆刪除
  15. 你好,想请教一下,我这里有一种情况,Java程式码中有使用facebook sdk v4.x的登入,然后做成一个例如beluga SDK,那要在Unity3D 调用整个Beluga SDK,那像这种SDK 去使用Facebook SDk v4.x这种情况下我在unity的Plugin Android该怎么处理呢?能否指点指点,因为这问题已困惑很久。

    回覆刪除