我的環境配置:Windows 10、Unity 5.0.4f1、Java JDK 1.8.0_51、Android Studio 2.1.2。
2016/09/13 補充:加入取消本地端推播的方法,修正了點擊推播訊息前往 Unity 時會錯誤的問題,並且都修改到範例裡面。
如果你已經完成了之前的文章 "Android Studio 匯出 JAR 檔"、"Android Studio 匯出 JAR 檔 - 加入 Unity classes.jar"、"Android Studio 匯出 JAR 檔 - 在 Unity 中調用 Android Function" 的話。
那現在我們可以來嘗試加入 Android 的 "本地端推播 (LocalNotification)" 囉!
所謂的本地端推播,就是利用系統內建的 "鬧鐘系統 (AlarmManager)",設定在某個時間點時,發送我們設定好的 "廣播 (Broadcast)"。
而當這筆 "廣播 (Broadcast)" 被發送時,我們會在建立一個 "廣播接收器 (BroadcastReceiver)" 去接收該 "廣播 (Broadcast)"。
並且解析出內容後,使用 "推播系統 (NotificationManager)" 將資訊加入使用者的 "狀態列 (Status Bar)" 裡面,如此一來,我就們完成了 "本地端推播 (LocalNotification)" 功能了!
流程如下:
步驟一、
開啟 Android Studio。
相關的專案設置這邊不會再講,若不曉得的人,可以將 "Android Studio 匯出 JAR 檔" 系列的文章,快速瀏覽過一次。
或者直接下載 "Android Studio 匯出 JAR 檔 - 在 Unity 中調用 Android Function" 文章中的範例,也是可以的。
步驟二、
建立一個 Java Class。
名稱自訂,我這邊是命名為 "LocalNotificationActivity"
步驟三、
該 Class 是用來跟 Unity 溝通的,所以必須繼承 UnityPlayerActivity
public class LocalNotificationActivity extends UnityPlayerActivity {
}
步驟四、
再建立一個 Java Class。 名稱自訂,我這邊是命名為 "LocalNotificationReceiver"
步驟五、
該 Class 是用來接收廣播的,所以必須繼承 BroadcastReceiver。
public class LocalNotificationReceiver extends BroadcastReceiver {
public LocalNotificationReceiver() {
}
@Override
public void onReceive( Context context, Intent intent ) {
}
}
步驟六、
回到 LocalNotificationActivity.java 裡,這邊我們添加兩個靜態 function,分別 Add 跟 Cancel。
Add:
該函數主要是把推播需要的資源封裝起來,再建立一個鬧鐘,在鬧鐘響起時,會將封裝好的資源廣播出去,它接收的參數為 "幾秒後推播訊息"、"訊息標題"、"訊息內文"、"訊息圖示"、"訊息編號"。
PS:訊息圖示非常重要,若沒有圖示的話,推播訊息則不會顯示。
Cancel:
該函數主要是用來取消鬧鐘用的,它接收的參數只有一個 "訊息編號",這邊會去通知系統將相同編號的鬧鐘做取消的動作。
而取消的重點在於 requestCode 與 Intent 必須跟當初建立鬧鐘時一樣,而 PendingIntent 就算不同也沒關係
以下有提到 PendingIntent 以及 AlarmManager 參數,這邊做個簡單的說明。
// PendingIntent.FLAG_CANCEL_CURRENT = 如果已經存在 PendingIntent, 則會取消目前的 Intent 後再產生新的 Intent. // PendingIntent.FLAG_NO_CREATE = 如果並不存在 PendingIntent, 則傳回 null. // PendingIntent.FLAG_ONE_SHOT = 此 PendingIntent 只能使用一次. // PendingIntent.FLAG_UPDATE_CURRENT = 如果已存在 PendingIntent, 則更新 extra data. // AlarmManager.RTC_WAKEUP = 鬧鐘在睡眠模式底下會喚醒系統並執行功能. // AlarmManager.RTC = 鬧鐘在睡眠模式時不會運作.
public static void Add( final int second, final String title, final String content, final String iconSmall, final int requestCode ) {
UnityPlayer.currentActivity.runOnUiThread( new Runnable() {
public void run() {
// 取得定時呼叫管理器
AlarmManager alarmManager = (AlarmManager) UnityPlayer.currentActivity.getSystemService(Context.ALARM_SERVICE);
// 封裝訊息
Intent notificationIntent = new Intent(UnityPlayer.currentActivity, LocalNotificationReceiver.class);
notificationIntent.putExtra("title", title);
notificationIntent.putExtra("content", content);
notificationIntent.putExtra("iconSmall", iconSmall);
notificationIntent.putExtra("requestCode", requestCode);
// 整理定時需要的資訊
PendingIntent broadcast = PendingIntent.getBroadcast(UnityPlayer.currentActivity, requestCode, notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);
// 設定時間
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND, second );
// 加入定時
alarmManager.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), broadcast);
}
} );
}
public static void Cancel( int requestCode ) {
// 取得定時呼叫管理器
AlarmManager alarmManager = (AlarmManager) UnityPlayer.currentActivity.getSystemService(Context.ALARM_SERVICE);
// Intent 的 Receiver Class 必須跟當初建立鬧鐘時的 Receiver 一樣
Intent notificationIntent = new Intent(UnityPlayer.currentActivity, LocalNotificationReceiver.class);
// 取消定時, 這邊的重點在於 requestCode 與 Intent 必須跟當初建立鬧鐘時一樣,而 PendingIntent 就算不同也沒關係
alarmManager.cancel( PendingIntent.getBroadcast(UnityPlayer.currentActivity, requestCode, notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT) );
}
步驟七、
回到 LocalNotificationReceiver.java 裡面,這邊針對 onReceive 做了修改。
這邊主要是把鬧鐘廣播出去資源接收回來,之後解析出封裝好的資源後,再去建立推播。
這要注意到,因為我們需要返回 Unity 裡面,所以 Intent 必須是 new Intent(context, UnityPlayerActivity.class);。
可能有人發現取得 Resources 的方式,並非為 R.drawable.xxx。
這是因為,Android 專案打包 JAR 檔時,他只會打包你專案的 Class 而已,並不會打包專案內的 Resources,甚至引用的 Libraries 也不會打包在內。
所以這些資源必須額外複製一份到 Unity > Assets > Plugins > Android 裡面。
@Override
public void onReceive( Context context, Intent intent ) {
String title = intent.getStringExtra("title");
String content = intent.getStringExtra("content");
String iconSmall = intent.getStringExtra("iconSmall");
int requestCode = intent.getIntExtra("requestCode", 0);
int smallIcon = context.getResources().getIdentifier(iconSmall, "drawable", context.getPackageName());
// 這邊表示點擊推播訊息後, 要返回 Unity, 所以必須是 UnityPlayerActivity.class
Intent newIntent = new Intent(context, UnityPlayerActivity.class);
newIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(context, requestCode, newIntent, PendingIntent.FLAG_CANCEL_CURRENT); // 取得PendingIntent
// 建立推播內容
Notification.Builder builder = new Notification.Builder(context.getApplicationContext());
// setSmallIcon 尺寸建議 32 * 32
builder.setSmallIcon(smallIcon);
builder.setContentTitle(title);
builder.setContentText(content);
builder.setContentIntent(pendingIntent);
builder.setAutoCancel( true );
Notification notification = builder.build();
// 取得推播管理器, 執行推播
NotificationManager notificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(requestCode, notification);
}
步驟八、
在這裡,我們使用了 Notification.Builder,該 Class 是比較新的,需要 Android API 版本 16 以上才能夠使用,所以記得去 Unity 專案,在 Edit > ProjectSettings > Player > Android > Other Settings > Minimum API Level,這邊要改成 16 以上
Unity > Assets > Plugins > Android > AndroidManifest.xml 將 <uses-sdk android:minSdkVersion="9" /> 修改至 16 以上。
若你是用 Unity 自己的 AndroidManifest.xml,那可能會沒有 uses-sdk android:minSdkVersion,若沒有的話,該步驟可跳過。
Android Studio >Test > app > src > build.gradle 裡面 defaultConfig 的 minSdkVersion 也記得要改成 16
步驟九、
將會用到的 Resources 放到 Unity > Assets > Plugins > Android 裡面。
我簡單的製作一個小圖示來測試。
在 Unity 專案裡面建立資料夾 Unity > Assets > Plugins > Android > res > drawable。
接著把製作好的小圖示放進去,我命名為 icon.png。
這邊值得注意的是,新版 Android 似乎將狀態列的 Icon 改成只能顯示不透明與透明了,所有非透明的顏色都會以白色來顯示。
所以 Icon 設計上,盡量是以白色以及透明去製作,會比較精準一點,並且存成 PNG 檔。
步驟十、
將 Android 打包匯出 JAR,並且放入 Unity > Assets > Plugins > Android
步驟十一、
打開 Unity > Assets > Plugins > Android > AndroidManifest.xml。
在 <manifest> 裡面 <application> 的外面加入權限設定,分別為 "震動"、"手機喚醒" 功能
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
在 <application> 裡加入我們的 Activity 以及 Receiver
<activity android:name="com.test.tw.test.LocalNotificationActivity" /> <receiver android:name="com.test.tw.test.LocalNotificationReceiver" android:enabled="true" android:exported="true" />
步驟十二、
打開我們的測試腳本,將 OnGUI 修改如下
private void OnGUI()
{
Rect rect = new Rect( 0.0f, 0.0f, 200.0f, 100.0f );
if ( GUI.Button(rect, "本地推播") )
{
#if UNITY_ANDROID && !UNITY_EDITOR
using ( AndroidJavaClass unity = new AndroidJavaClass("com.test.tw.test.LocalNotificationActivity") )
{
unity.CallStatic( "Add", 5, "這是標題", "這是內文", "icon", 100 );
}
#endif
}
rect = new Rect( 0.0f, 100.0f, 200.0f, 100.0f );
if ( GUI.Button(rect, "取消推播") )
{
#if UNITY_ANDROID && !UNITY_EDITOR
using ( AndroidJavaClass unity = new AndroidJavaClass("com.test.tw.test.LocalNotificationActivity") )
{
unity.CallStatic( "Cancel", 100 );
}
#endif
}
}
步驟十三、
最後運行結果
PS:這邊有一點值得注意的是,鬧鐘似乎最低秒數為 5 秒,若低於 5 秒的話,系統似乎會以 5 秒代替。

你好~~能否去请教您呢?我是Leo。 我在 Unity 裡加入 Android 资源,开发环境是android studio,我有依照你的步骤执行,可是错误显示需要re-package? 不知能否帮上忙?这问题实在困惑很久了~!谢谢,希望你能经快回复!
我猜大概是你的圖片的副檔名打錯,圖片副檔名要是PNG
這問題我第一次使用 Android Studio 似乎也有遇到,當初是 Build Tools Version 版本等等的亂七八糟問題。 但現在重建專案也不會遇到了,無法重現...所也我也不太明白為何會有這錯誤。 所以我是建議 Android SDK 可更新就更新吧~ 接著 Build Tools Version 版本 可以試著使用其他版本看看。
不好意思,請問一下如果推播的計時到一半,想要取消的話要怎麼取消推播?
你好: 請使用下面程式碼 Intent notificationIntent = new Intent(UnityPlayer.currentActivity, LocalNotificationReceiver.class); AlarmManager alarmManager = (AlarmManager) UnityPlayer.currentActivity.getSystemService(Context.ALARM_SERVICE); alarmManager.cancel( PendingIntent.getBroadcast(UnityPlayer.currentActivity, 100, notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT) ); 就可以取消範例的推播囉! 其它詳細的說明,我會再補到文章中。 希望有幫到你。
不好意思想再問一下,如果有一則以上不同時間的推播的話該怎麼做,目前用這個方法只會推播最後一則,謝謝。
可以針對編號去做取消唷~ 詳細可看下面留言。
是不是在Unity管理那個requestCode就可以了啊? 那個requestCode可以是任意數字嗎?
是的~ requestCode 就像是編號的意思,你可以在 Unity 的裡面就決定好每一則推播對應的編號。 假如說: 我有 A 訊息,我設定編號 100。 B 訊息我設定編號 200。 我就可以使用以下程式碼,去建立兩則推播 // 建立 A 訊息推播,編號我給 100 unity.CallStatic( "Add", 5, "這是A標題", "這是A內文", "icon", 100 ); // 建立 B 訊息推播,編號我給 200 unity.CallStatic( "Add", 5, "這是B標題", "這是B內文", "icon", 200 ); // 取消 A 訊息推播,因為編號 100 是 A 訊息的編號 unity.CallStatic( "Cancel", 100 ); // 取消 B 訊息推播,因為編號 200 是 B 訊息的編號 unity.CallStatic( "Cancel", 200 ); 範例裡面也有提到這一塊唷~可以去試看看~ 希望有幫到你!!
你好想請問如果我點按推播之後想要跳到不同的場景該如何做呢~?感謝
你好: 請問是 Unity 嗎? 這邊我的想法是這樣的,在推播的時候,會順帶傳遞一個參數,可能是一個編號。 當玩家點擊推播訊息時,這個編號會被 Android 儲存在手機的某個位置。 而 Unity 被啟動時,會去檢查這個位置,是否存在編號,若編號存在的話,表示玩家是藉由推播進來遊戲的,這時候 Unity 會再依照該編號切換到其它場景上。 這邊我會再找時間寫個範例,因為需要時間,所以最快也是下禮拜才能夠釋出。 希望有幫到你!!
謝謝 讓我有一些想法 期待你的範例~
不好意思,目前有事在忙,短時間內沒辦法給你完整範例。 下面是我測試一半的範例,你可以先拿去參考。 https://drive.google.com/open?id=0B0QPre3qvuw-bTl2QXRGWC1BREk Unity 裡面的 AndroidManifest.xml 要修改,改成我們自己建立的 Activity。 Android 裡面有一隻 MyActivity.java,這隻會在 App 完全被閉關後,點擊推播重新打開 App 才會接收到參數值,因為我是在 onCreate 去接收參數的,這邊再麻煩你自己修改為每次開 App 都可以接收到參數了。 之後再看怎麼將參數值回傳給 Unity,切換至對應的場景。 謝謝。 2017/09/29 更新: 你好,我已經建立範例囉,歡迎前往參考。 http://gn02214231.pixnet.net/blog/post/218769144-unity-%E5%A6%82%E4%BD%95%E5%9C%A8%E9%BB%9E%E6%93%8A%E6%8E%A8%E6%92%AD%E4%B9%8B%E5%BE%8C%E5%88%87%E6%8F%9B%E5%A0%B4%E6%99%AF
不好意思,我有點好奇,能不能在靜音模式讓這個呼叫的通知依然發出聲音呢?像是系統鬧鐘的行為?
你好,這邊我還真沒試過耶,下禮拜我來測試看看。
不好意思,震動跟聲音如何呈現? 程式執行的時候沒有
你好: 你可以透過 Notification.Builder 的 setSound 與 setVibrate 去做設定。 例如: builder.setSound(Settings.System.DEFAULT_NOTIFICATION_URI); builder.setVibrate(new long[] { 1000, 1000}); 我會再找時間加入範例裡面。 謝謝。