2013-12-28

Unity 3D + Local Notification for Android

在 Android 底下使用 Notification Service 配合 Alarm Service 即可使系統在指定時間產生通知訊息。底下敘述如何使用 Android 的 Notification 並且將其編譯成 Plugin 與 Unity 整合。

Plugin 主要只有一個檔案 ( AlarmReceiver.java ) :
package com.macaronics.notification;

import java.util.Calendar;

import android.app.Activity;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.util.Log;
import com.unity3d.player.UnityPlayer;

public class AlarmReceiver extends BroadcastReceiver {
    public static void startAlarm(String name, String title, String label, int secondsFromNow){
        Activity act =UnityPlayer.currentActivity;
        Log.i("Unity", "startAlarm...");
      
        Calendar c = Calendar.getInstance();
        c.add(Calendar.SECOND, secondsFromNow);
        long alarmTime = c.getTimeInMillis();
        Log.i("Unity", "alarm time +"+secondsFromNow);
        
        // Schedule the alarm!
        AlarmManager am = (AlarmManager)act.getSystemService(Context.ALARM_SERVICE);
        Intent ii =new Intent(act, AlarmReceiver.class);
        ii.putExtra("name", name);
        ii.putExtra("title", title);
        ii.putExtra("label", label);
        am.set(AlarmManager.RTC_WAKEUP, alarmTime, PendingIntent.getBroadcast(act, 0, ii, 0));
    }
    
    //<receiver android:process=":remote_notification" android:name="AlarmReceiver"></receiver>
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d("Unity", "Alarm Recieved!");
      
        NotificationManager mNM = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
        
        Bundle bb =intent.getExtras();

        Class<?> cc = null;
        try {
          cc = context.getClassLoader().loadClass("com.unity3d.player.UnityPlayerProxyActivity");
        } catch (ClassNotFoundException e1) {
          e1.printStackTrace();
          return;
        }
            
        final PackageManager pm=context.getPackageManager();
        ApplicationInfo applicationInfo = null;
        try {
          applicationInfo = pm.getApplicationInfo(context.getPackageName(),PackageManager.GET_META_DATA);
        } catch (NameNotFoundException e) {
          e.printStackTrace();
          return;
        }
        final int appIconResId=applicationInfo.icon;
        Notification notification = new Notification(appIconResId, (String)bb.get("name"), System.currentTimeMillis());
        
        int id =(int)(Math.random()*10000.0f)+1;
        PendingIntent contentIntent = PendingIntent.getActivity(context, id, new Intent(context, cc), 0);
        notification.setLatestEventInfo(context, (String)bb.get("title"), (String)bb.get("label"), contentIntent);

        Log.i("Unity", "notify("+id+") with "+(String)bb.get("title")+", "+(String)bb.get("label"));
        mNM.notify(id, notification);
    }
}
其中函數 startAlarm 主要負責在 Alarm Service 設定 Alarm 事件的啟動時間,接收 Alarm 事件的對象 (com.macaronics.notification.AlarmReceiver ) ,傳遞的資料 (name, title, label),當 Alarm 事件呼叫啟動 onReceive 函數之後,onReceive 則是依照傳遞過來的資料利用 Notification Service 來產生 Notification。

編譯 AlarmReceiver.java (OSX 環境) :
#!/bin/sh

ANDROID_JAR=/Users/macaronics/Desktop/applications/adt-bundle-mac-x86_64-20130917/sdk/platforms/android-13/android.jar
UNITY_JAR=/Applications/Unity4.2.1/Unity.app/Contents/PlaybackEngines/AndroidPlayer/bin/classes.jar

javac ./*.java -cp $ANDROID_JAR:$UNITY_JAR -d .
jar cvfM ../AlarmReceiver.jar com/
rm -rf ./com

其中 ANDROID_JAR 是 Android SDK 底下的 android.jar 路徑,UNITY_JAR 是 Unity 底下 classes.jar 的路徑,javac 指令將目前資料夾底下所有的 java 檔案 compile 成 class 檔案並置於相對應於 package name 的資料夾路徑底下,jar 指令則是將資料夾打包成 jar 檔案 (值得注意的是 package name 與資料夾路徑必須要一致,例如 package name 為 com.macaronics.notification,則其 class 檔案須置於 ./com/macaronics/notification/ 底下。 )

設定 AndroidManifest.xml,在 AndroidManifest.xml 的 application 底下新增 :
<receiver android:process=":remote" android:name="com.macaronics.notification.AlarmReceiver"></receiver>
底下為 AndroidManifest.xml 完整內容 :
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="preferExternal" android:theme="@android:style/Theme.NoTitleBar" android:versionName="1.0" android:versionCode="10">
  <supports-screens android:smallScreens="true" android:normalScreens="true" android:largeScreens="true" android:xlargeScreens="true" android:anyDensity="true" />
  <application android:icon="@drawable/app_icon" android:label="@string/app_name" android:debuggable="false">  
    <receiver android:process=":remote" android:name="com.macaronics.notification.AlarmReceiver"></receiver>
    <activity android:name="com.unity3d.player.UnityPlayerProxyActivity" android:label="@string/app_name" android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen" >
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
    <activity android:name="com.unity3d.player.UnityPlayerActivity" android:label="@string/app_name" android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen" >
    </activity>
    <activity android:name="com.unity3d.player.UnityPlayerNativeActivity" android:label="@string/app_name" android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen" >
      <meta-data android:name="android.app.lib_name" android:value="unity" />
      <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="true" />
    </activity>
  </application>
  <uses-feature android:glEsVersion="0x00020000" />

  <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="17" />

</manifest>
將編譯完成的 AlarmReceiver.jar 以及 AndroidManifest.xml 置於 Unity 專案資料夾的 Assets/Plugins/Android/ 底下。

建立一個 C# Script 測試結果,其內容如下 :
using UnityEngine;
using System.Collections;

public class AlarmReceiver : MonoBehaviour {

  // Use this for initialization
  void Start () {

  }
 
  // Update is called once per frame
  void Update () {
 
  }

  AndroidJavaObject nativeObj =null;
  void OnGUI(){
    if (GUI.Button(new Rect(Screen.width*0.5f-90.0f, 100.0f, 180.0f, 100.0f), "Create Notification")){
      if (nativeObj ==null)
        nativeObj =new AndroidJavaObject("com.macaronics.notification.AlarmReceiver");

      nativeObj.CallStatic("startAlarm", new object[4]{"THIS IS NAME", "THIS IS TITLE", "THIS IS LABEL", 10});
    }
  }
}
當使用者按下 Create Notification 按鈕之後 Plugin 會建立一個 10 秒後顯示 Notification 的 Alarm 事件。

本範例的完整 Unity 程式碼可以在 http://github.com/phardera/unity3d_notification_android 下載。

2 則留言 :