【前言】
對於手游來說,什么時候需要推送呢?玩過一些帶體力限制的游戲就會發現,我的體力在恢復滿后,手機會收到一個通知告訴我體力已完全恢復了。這類通知通常是由本地的客戶端發起的,沒有經過服務端。
在安卓應用中,本地通知推送是通過調用系統級服務NotificationManager實現的。雖然U3D本身也有NotificationServices類可以進行通知推送,但僅限於iOS平台(這篇博文講了怎么使用它在iOS平台發起本機推送)。
而現在我們的游戲是使用U3D開發的,並不能像安卓開發一樣直接在代碼中調用服務。為了實現本地定時推送效果,需要自己寫一個插件來實現了。
由於推送通常發生在客戶端關閉的狀態,這個推送應該被放在一個延時服務中,否則玩游戲玩得好好的突然跳出來一條自己的推送,太詭異了。
於是我們需要完成一個提供三個功能的模塊:1、設定X秒后顯示一條推送通知;2、設定X秒后顯示一條通知,之后每天再顯示一次;3、清除本應用的所有推送。
【解決思路】
因為U3D引擎提供了調用jar包的方法,所以我們可以在jar包中調用安卓的類庫,實現消息推送,然后在jar包中留出接口供U3D使用即可,沒有必要走JNI層。
【所需工具】
● eclipse
● 安卓SDK(我使用的4.4)
● Unity編輯器(我使用的5.1.3)
【開工】
1、 創建jar包工程
創建的時候要引入兩個第三方jar包。
一個是Unity的包,地址: Unity安裝目錄\Editor\Data\PlaybackEngines\androidplayer\release\bin\classes.jar(貌似4.x的目錄和5.x不太一樣,但總之就是找到androidplayer里面的classes.jar)
還有一個是安卓SDK的包,地址: 安卓SDK安裝目錄\platforms\安卓版本\android.jar

2、 編碼
思路就是使用AlarmManager服務,在一定時間后發起廣播,然后通過接收器接受展示。如果你做過安卓開發,對這段代碼肯定不會陌生。如果沒做過也沒關系,當成一個黑盒,在需要的時候調接口就行。
首先添加一個Java類,注意父類要設為BroadcastReceiver。

添加完成后,就可以開始寫了:
package com.guyastudio.unityplugins;
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.os.Bundle;
import com.unity3d.player.UnityPlayer;
/**
* 用於生成 / 清除本地通知推送的插件
* 僅在安卓平台有效
*
* @author Weiren
*
*/
public class AndroidNotificator extends BroadcastReceiver {
private static int m_nLastID = 0;
/**
* 顯示數秒后的通知
*
* @param pAppName 應用名
* @param pTitle 通知標題
* @param pContent 通知內容
* @param pDelaySecond 延遲時間
* @param pIsDailyLoop 是否每日自動推送
* @throws IllegalArgumentException
*/
public static void ShowNotification(String pAppName, String pTitle, String pContent, int pDelaySecond, boolean pIsDailyLoop) throws IllegalArgumentException {
if(pDelaySecond < 0)
{
throw new IllegalArgumentException("The param: pDelaySecond < 0");
}
Activity curActivity = UnityPlayer.currentActivity;
Intent intent = new Intent("UNITY_NOTIFICATOR");
intent.putExtra("appname", pAppName);
intent.putExtra("title", pTitle);
intent.putExtra("content", pContent);
PendingIntent pi = PendingIntent.getBroadcast(curActivity, 0, intent, 0);
AlarmManager am = (AlarmManager)curActivity.getSystemService(Context.ALARM_SERVICE);
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND, pDelaySecond);
long alarmTime = calendar.getTimeInMillis();
if (pIsDailyLoop){
am.setRepeating(
AlarmManager.RTC_WAKEUP,
alarmTime,
86400, // 24 hours
pi);
} else {
am.set(
AlarmManager.RTC_WAKEUP,
alarmTime,
pi);
}
}
/**
* 清除所有通知,包括日常通知
*/
public static void ClearNotification() {
Activity act = UnityPlayer.currentActivity;
NotificationManager nManager = (NotificationManager)act.getSystemService(Context.NOTIFICATION_SERVICE);
for(int i = m_nLastID; i >= 0; i--) {
nManager.cancel(i);
}
m_nLastID = 0;
}
@SuppressWarnings("deprecation")
public void onReceive(Context pContext, Intent pIntent) {
Class<?> unityActivity = null;
try {
unityActivity = pContext.getClassLoader().loadClass("com.unity3d.player.UnityPlayerProxyActivity");
} catch (Exception ex) {
ex.printStackTrace();
return;
}
ApplicationInfo applicationInfo = null;
PackageManager pm = pContext.getPackageManager();
try {
applicationInfo = pm.getApplicationInfo(pContext.getPackageName(), PackageManager.GET_META_DATA);
} catch (Exception ex) {
ex.printStackTrace();
return;
}
Bundle bundle = pIntent.getExtras();
Notification notification = new Notification(
applicationInfo.icon,
(String)bundle.get("appname"),
System.currentTimeMillis());
PendingIntent contentIntent = PendingIntent.getActivity(
pContext,
m_nLastID,
new Intent(pContext, unityActivity),
0);
notification.setLatestEventInfo(
pContext,
(String)bundle.get("title"),
(String)bundle.get("content"),
contentIntent);
NotificationManager nm = (NotificationManager)pContext.getSystemService(Context.NOTIFICATION_SERVICE);
nm.notify(m_nLastID, notification);
m_nLastID++;
}
}
3、 導出jar包
在項目上右鍵——Export,導出為jar格式。

4、添加AndroidManifest.xml
安卓應用中如果要讓應用收到廣播,還需要在AndroidManifest.xml中加入receiver標簽。我們創建的項目是一個Java項目,不會自動生成AndroidManifest,所以需要手動寫一個:
<?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>
<receiver android:name="com.guyastudio.unityplugins.AndroidNotificator" >
<intent-filter>
<action android:name="UNITY_NOTIFICATOR" />
</intent-filter>
</receiver>
</application>
<uses-feature android:glEsVersion="0x00020000" />
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="17" />
</manifest>
● 注意“<action android:name="UNITY_NOTIFICATOR" />”這里名字要和前面Java代碼中的一致。
4、 在U3D項目中調用
創建一個新的U3D項目,在界面上放一個Text和兩個Button(為節約時間我用的源生UI):

然后將導出的jar文件和AndroidManifest.xml文件移動到 U3D項目目錄\Assets\Plugins\Android下:

在幾個UI的父節點上加一個腳本,內容如下:
using UnityEngine;
using UnityEngine.UI;
public class JARTest : MonoBehaviour {
public Text Text_Message;
#if UNITY_ANDROID
private AndroidJavaObject m_ANObj = null;
#endif
// Use this for initialization
void Start () { }
// Update is called once per frame
void Update () { }
public void Button_1_Clicked()
{
#if UNITY_ANDROID
if(InitNotificator())
{
m_ANObj.CallStatic(
"ShowNotification",
Application.productName,
"溫馨提示",
"你該食屎了",
10,
false);
this.Text_Message.text = "Notification will show in 10 sec.";
}
#endif
}
public void Button_2_Clicked()
{
#if UNITY_ANDROID
if(InitNotificator())
{
m_ANObj.CallStatic("ClearNotification");
this.Text_Message.text = "Notification has been cleaned";
}
#endif
}
#if UNITY_ANDROID
private bool InitNotificator()
{
if (m_ANObj == null)
{
try
{
m_ANObj = new AndroidJavaObject("com.guyastudio.unityplugins.AndroidNotificator");
}
catch
{
this.Text_Message.text = "Init AndroidNotificator Fail";
return false;
}
}
if (m_ANObj == null)
{
this.Text_Message.text = "AndroidNotificator Not Found.";
return false;
}
return true;
}
#endif
}
● 注意實例化AndroidJavaObject的參數名字要和Java工程的包名類名一致。
然后綁定控件和事件方法。綁定好后先編譯一下,如果通過了,就可以導出一個apk包了。將這個包安裝到安卓設備上。我手頭沒有安卓設備,就用模擬器來測試:

點擊“Show”按鈕,10秒后會收到通知(點擊后可將應用至后台,或殺掉):

而點擊“Clean”按鈕,通知都會被清除。
至此,這個通知插件就完成了。
【后記】
最近兩個月經歷了辭職,以休息的名義玩(主要是肝夏活,你懂的),苦逼地找工作,意外地入職這一系列過程,心情比較復雜,博客這一塊也一直沒上。直到今天在項目中搞了個這個模塊,才意識到可以稍微更新一下。
我知道你們想要譜面編輯器教程(
好吧,完整的制作過程我看來是沒時間寫出來了,這周末講一下核心部分的邏輯,核心搞懂了其他都很簡單了。不發直播拆大和,我是認真的!

