とあるプログラマの備忘録

都内某所に住むプログラマが自分用に備忘録を残すという趣旨のブログです。はてなダイアリーから移動しました!

Unity 指定時間ローカルプッシュ通知処理を書いてみた Android編

-- 8/26追記 今北工業さんが私の記事のtypoを修正してくださりました〜! こちらも参考にしてくださいm( )m

blog.livedoor.jp

今月末に引っ越しを控え案件のタスクを残したくないraharuです
さて、前回に引き続きUnityのNative連携のAndro編です。

流れとしてはAlarmManagerで登録
レシーバーで受信したらローカル通知を投げるという事をしたいだけなのにクソはまった。。

※まだAndroidPluginの作成環境が出来てない人はこちらの記事で環境を構築してください。

パッケージ名は自由ですが、
今回はnet.raharu.localnotifypluginです
これあとでAndroidManifestに使うので重要

java

localNotification.java

package net.raharu.localnotifyplugin;

import java.util.Calendar;

import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

import com.unity3d.player.UnityPlayer;

public class localNotification {
    
    /**
    * 時間指定でローカル通知を投げる
    * @param message
    * @param unixtime
    * @param primary_key
    */
    public void sendNotification(String message, long unixtime, int primary_key) 
    {
        Log.i("Unity", "SendNotificationStart");
        
        // インテント作成
        Activity activity = UnityPlayer.currentActivity;
        Context context = activity.getApplicationContext();
        Intent intent = new Intent(context, NotificationReceiver.class);
        //渡す値
        intent.putExtra("MESSAGE", message);
        intent.putExtra("PRIMARY_KEY", primary_key);
        
        //10秒後にアラーム(デバック用)
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(System.currentTimeMillis());
        calendar.add(Calendar.SECOND, 10);
        
        PendingIntent sender = PendingIntent.getBroadcast(context,  primary_key, intent, PendingIntent.FLAG_UPDATE_CURRENT); 
        AlarmManager alarm = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
        alarm.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis() , sender);
    }
}

NotificationReceiver.java

package net.raharu.localnotifyplugin;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
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.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.v4.app.NotificationCompat;

/**
 * レシーバー
 * 
 * @author raharu
 */
public class NotificationReceiver extends BroadcastReceiver{
    
    @Override
    public void onReceive(Context context, Intent intent) {
        
        //値の取得
        String message = intent.getStringExtra("MESSAGE");
        Integer primary_key = intent.getIntExtra("PRIMARY_KEY", 0);
        
        // intentからPendingIntentを作成
        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
        
        // LargeIcon の Bitmap を生成
        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;
        Bitmap largeIcon = BitmapFactory.decodeResource(context.getResources(), appIconResId);
         
        // NotificationBuilderを作成
        NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
        builder.setContentIntent(pendingIntent);
        builder.setTicker("通知がとどきました");  //ステータスバーに届くテキスト
        builder.setSmallIcon(appIconResId);           //アイコン
        builder.setContentTitle("タイトルだよ!!");       // タイトル
        builder.setContentText(message);              // 本文(サブタイトル)
        builder.setLargeIcon(largeIcon);              //開いた時のアイコン
        builder.setWhen(System.currentTimeMillis());  //通知に表示される時間(※通知時間ではない!)
        
        // 通知時の音・バイブ・ライト
        builder.setDefaults(Notification.DEFAULT_ALL);
        builder.setAutoCancel(true);
         
        // NotificationManagerを取得
        NotificationManager manager = (NotificationManager) context.getSystemService(Service.NOTIFICATION_SERVICE);
        // Notificationを作成して通知
        manager.notify(primary_key, builder.build());
    }
 
}

このパッケージをjarとしてエクスポートしてAssets/Plugin/Androidの直下に起きます。

Unity側

AndroidMnifest.xmlにレシーバーと権限を追加

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="XXXXXXXXXXXX"
    android:installLocation="preferExternal"
    android:theme="@android:style/Theme.NoTitleBar"
    android:versionCode="1"
    android:versionName="1.0">
    <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="true">
        <activity android:name="com.unity3d.player.UnityPlayerNativeActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <meta-data android:name="unityplayer.UnityActivity" android:value="true" />
            <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="true" />
        </activity>
        <meta-data
    android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
        #ここに通知されるレシーバーを登録する(パッケージ名+クラス名)
        <receiver android:name="net.raharu.localnotifyplugin.NotificationReceiver" android:process=":remote" />
    </application>
    <uses-sdk android:minSdkVersion="9" android:targetSdkVersion="20" />
    <uses-feature android:glEsVersion="0x00020000" />

    <uses-permission android:name="android.permission.INTERNET" />
     #バイブレートの権限追加
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
</manifest>

こんな感じで呼び出せる

    #if UNITY_ANDROID
        AndroidJavaObject m_plugin = new AndroidJavaObject( "net.raharu.localnotifyplugin.localNotification");
        if (m_plugin != null){
            m_plugin.Call("sendNotification", "本文だよ!!!!", [unixtime], [unique_id]);
        }
    #endif

unixtimeは通知したい時間(今回は強制で10秒後にしてるけども。。)
第三引数はユニークな値を入れるとよし

これで時間指定したローカル通知が届くようになりました。

当たり前の事かも知れないけど一本アプリ作るのに
c# java obj-c phpと4言語扱うのに
mono eclipse xcode vimを言ったり来たりして段々今何言語書いてるのか分からなくなってくる。

ネイティブ連携は面倒くさいね!

参考にさせて頂きました

【Android】指定時間に処理を実行する【AlarmManager】 - アプリ開発者のチラシの裏

Android - スリープ時にもBroadcastを処理する方法 - Qiita

Unity ローカルプッシュ通知処理を書いてみた iOS編

iOS8からローカルプッシュ通知にもユーザー許可が必要になりました。
今迄のやり方だとローカル通知が遅れなくなってしまったので
起動時に認証ダイアログを表示させるようにします。

まずはAssets/Plugin/iOS以下に
LocalNotificationController.mmを作成します。
ソースはこんな感じで書きます

#import "iPhone_target_Prefix.pch"
#import "UnityAppController.h"

@interface LocalNotificationController : UnityAppController
+(void)load;
@end

@implementation LocalNotificationController
+(void)load
{
    extern const char* AppControllerClassName;
    AppControllerClassName = "LocalNotificationController";
}


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{

    [super application:application didFinishLaunchingWithOptions:launchOptions];
    float version = [[[UIDevice currentDevice] systemVersion] floatValue];
    if (version >= 8.0)
    {
        if ([application respondsToSelector:@selector(registerUserNotificationSettings:)]) {
            UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeSound categories:nil];
            [application registerUserNotificationSettings:settings];
        }
    }
    return YES;
}

@end

これで実機起動すれば認証ダイアログが出てくるはずです。(iOS8以上)

実際に通知を送ってみます。

using UnityEngine;
using System.Collections;

public class LocalNotifyScript : MonoBehaviour {


   void Start ()
    {
        addLocalPush ();
    }

    void addLocalPush ()
    {

        Debug.Log ("ローカル通知が走ります");
        #if UNITY_EDITOR
        #elif UNITY_ANDROID
        #else
        addLocalPushiOS();
        #endif
    }

    void addLocalPushiOS()
    {
        LocalNotification l = new LocalNotification();
        l.applicationIconBadgeNumber = 1;
        l.fireDate = System.DateTime.Now.AddSeconds(10);
        l.alertBody = "通知テストだよ!";
        NotificationServices.ScheduleLocalNotification(l);
    }

}

これを適当なGameObjectにアタッチします。

なかなか資料が見つからず四苦八苦しました。。。
安西先生、ネイティブ連携をサクサク書けるようになりたいです。。

参考にさせていただきましたm( )m

UnityのiOSでAppDelegateに処理を追加する - ほげほげ(仮)

[Unity] iOS8でローカル通知が動かない: ものづくりログ

Unityで作ったiOSアプリにObj-CでPush通知を載せてみた | 丸ノ内テックブログ

UnityでiOSのネイティブコードを呼び出す最も簡単でシンプルな方法 - C# → Objective-C

[備忘録]AppController.mmあたりをいじっている時のUnity4.1.5迄からUnity4.2以上に上げる時の注意 - Qiita

Laravel4 Eloquent ORM SELECTをあれこれ試してみる

前回Createと触り程度をやってみたEloquent ORMですが
今回はもう少し踏み込んで見たいと思います。

クエリを試す

<?php

~~ skip
        //PK参照
        $records = Calendar::find(1);

        //条件参照
        $records = Calendar::where('area_name', 'tokyo')->get();

        //条件参照(比較演算子)
        $records = Calendar::where('id', '<=', 2)->get();

        //条件参照(LIKE)
        $records = Calendar::where('area_name', 'LIKE', "%kyo%")->get();

        //条件参照(IN)
        $records = Calendar::whereIn('area_name', array('tokyo', 'aomori'))->get();

        //条件参照(OR)
        $records = Calendar::whereOr('area_name', array('tokyo', 'aomori'))->get();

        //Order
        $records = Calendar::where('area_name', 'tokyo')->orderBy('id', 'DESC')->get();

        //Where最初とは限らない
        $records = Calendar::groupBy('area_name')->get();

        //OR句のネストだとこうなる(ちょっと複雑にしてみた)
        //select `id` from `calendar` where `calendar`.`deleted_at` is null and (`id` <= ? or `id` >= ?) and `area_name` = ? order by `id` desc
        $records = Calendar::select('id')
                            ->where(function($query)
                            {
                                $query->where('id', '<=', 10);
                                $query->orWhere('id', '>=', 50);
                            })
                            ->where('area_name', 'tokyo')
                            ->orderBy('id', "DESC")
                            ->get();


        // クエリログを確認する
        var_dump(DB::getQueryLog());

        //JSON形式で表示
        var_dump( Response::json($records));

これらが出来ればもう参照クエリは制覇したも当然です(基本JOINは使わないし)
特に困る様な事も無さそうです。

http://readouble.com/laravel/4/2/0/ja/queries.html

Laravel4 Eloquent ORMを試してみる

ドキュメントもしっかりしてるし考えられて作られているなーと思うLaravelですが、
日本で全くといって良いほど流行ってねー()
2年前からあるはずなのになんでこんなに過疎ってるんでしょうかね。。
もっと流行っても良いと思うんですけどね、ステッカー欲しいです

とま今回は個人プロジェクトのバック用に使用しているだけなので
別に流行ってようが流行ってなかろうが関係無いんですけどね。

今回はEloquent ORMを試します。

前回同様適当なテーブルを作成しておきます。

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateCalendarTable extends Migration {

>.../**
>... * Run the migrations.
>... *
>... * @return void
>... */
>...public function up()
>...{
        Schema::create('calendar', function($table){
            $table->increments('id');
            $table->string('area_name', 32);
            $table->date('onair_date');
            $table->time('onair_time');
            $table->string('title', 128);
            $table->string('url', 256);
            $table->string('station', 64);
            $table->string('state', 64);
            $table->string('episode', 32)->nullable();
            $table->string('week_name', 32);
            $table->timestamps();
            $table->softDeletes();
        });
>...}

>.../**
>... * Reverse the migrations.
>... *
>... * @return void
>... */
>...public function down()
>...{
        Schema::drop('calendar');
>...}
}

deleteは論理削除で行いたいのでsoftDeletesを追加しています。
これをマイグレートしてテーブルを作成しました

f:id:raharu0425:20150107170756p:plain

このテーブルにレコード追加します 前回の記事では

DB::insert('insert into template (`key`, `value`, `created_at`, `updated_at`) values(?, ?, ?, ?)', array('template.v1', 'hogehogehogehoge', $now_timestamp, $now_timestamp));

こんな書き方だったのですが、今回はO/Rマッパーを使うのでそこら辺りもいい感じに
出来ると期待しています。

Eloquentモデルを作成する

app/models/以下にモデルの受け皿となるクラスを作成します。
(これを作成するartisanコマンドないのか。。。)

<?php

class Calendar extends Eloquent {

    // SoftDelete
    use SoftDeletingTrait;
    protected $dates = ['deleted_at'];

    //テーブル名
    protected $table = 'calendar';

    /**
     * createメソッド利用時に、入力を受け付けないカラムの指定
     */
    protected $guarded = array('id');

}

意外にprotected $guarded = array('id');
が重要でidはオートインクリメントされているのでインサート時に指定しなかったわけだが
それだとエラーにされるという落とし穴がある。

レコード作成例

<?php

~~skip
        $record = new Calendar;
        $record->area_name = $area_name;
        $record->title = $item->title;
        $record->url = $item->url;
        $record->onair_time = $item->time;
        $record->station = $item->station;
        $record->state = $item->state;
        $record->onair_date = $item->next;
        $record->week_name = $item->week;

        Calendar::firstOrCreate($record->getAttributes());

$record = new Calendar;
でテーブル本体をインスタンス化してカラムに値を追加しています
実際は$record->save()だけで事が済むんですが同一レコードを入れたく無かったので
今回はfirstOrCreateを使用しています

f:id:raharu0425:20150107173018p:plain

ばっちり入りました

これでSQLをガチで書く様な事をしなくてすっきり書けます。
いい感じですね、ベンチマーク取ってどの程度負荷に耐えられるのか試してみたくなりました。

次は参照やあれこれやってみたいと思っています。

Laravel4 DB操作とマイグレーションを試す

とりあえずサーバーにmysqlをインストールしておきます。

設定を行う

app/config/database.phpmysql配列を変更する

               'mysql' => array(
                   'driver'    => 'mysql',
                   'host'      => 'localhost',
                   'database'  => 'dbname',
                   'username'  => 'username',
                   'password'  => 'password',
                   'charset'   => 'utf8',
                   'collation' => 'utf8_unicode_ci',
                   'prefix'    => '',
               ),

変更箇所は自分の環境の場合は
databaseとusernameとpassword

databaseは適当に作っておきます。

マイグレーションファイルを作成する

$ php artisan migrate:make create_template_table

今回はテーブルのひな形テーブルを作成します。

app/database/migrations/2015_01_06_184420_create_template_table.php

のパスにファイルが作成されました。

これを修正します。

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateTemplateTable extends Migration {

>.../**
>... * Run the migrations.
>... *
>... * @return void
>... */
>...public function up()
>...{
        Schema::create('template', function($table){
            $table->increments('id');
            $table->string('key', 128)->unique();
            $table->string('value', 256)->nullable();
            $table->text('data')->nullable();
            $table->timestamps();
            $table->timestamp('deleted_at')->nullable();
            $table->enum('active', array('0', '1'))->default('1');
        });
>...}

>.../**
>... * Reverse the migrations.
>... *
>... * @return void
>... */
>...public function down()
>...{
        Schema::drop('template');
>...}

}

Schemaのコマンドは
DB:スキーマビルダー
に書かれています、ほとんど悩む事なくつくれました

マイグレーション実行

$ php artisan migrate

DBをみてみると

f:id:raharu0425:20150106212559p:plain

テーブルができました。

マイグレートのコマンドは以下の通り

php artisan list | grep migrate
  migrate                     Run the database migrations
migrate
  migrate:install             Create the migration repository
  migrate:make                Create a new migration file
  migrate:publish             Publish a package's migrations to the application
  migrate:refresh             Reset and re-run all migrations
  migrate:reset               Rollback all database migrations
  migrate:rollback            Rollback the last database migration

直感的な動きをしますので各コマンドを試してみるのが良いと思います。

インサートしてみる

$now_timestamp = date("Y-m-d h:i:s");
DB::insert('insert into template (`key`, `value`, `created_at`, `updated_at`) values(?, ?, ?, ?)', array('template.v1', 'hogehogehogehoge', $now_timestamp, $now_timestamp));

試しにこんなクエリを作ってみました。 実行してみると

f:id:raharu0425:20150106224214p:plain

ちゃんと入っています。

Laravel今日一日ためしてみましたが、何かと機能が充実してる感じで使いやすいです。 ドキュメントもしっかり作られているので戸惑う事も少ない良フレームワークだと思います。

次はEloquent ORMを試します。

Laravel4 バッチ処理を書いてみる

ネイティブアプリのバックエンドでLeravelの導入をしようとしているので スケジューラーで動かすことが前提です。

ですのでバッチモードで動かないとお話になりません。

今回はそんな話

コマンドファイルの作成

$ php artisan command:make LoadInformation --command=batch:loadinfo

このコマンドでapp/commands以下に LoadInformation.phpが作成され、コマンド名はbatch:loadinfoとなります。

コマンド名は後から変更出来ます。

コマンドの登録

app/start/artisan.phpにさっき作ったコマンドを登録します。

Artisan::add(new LoadInformationi());

コマンドが登録されたか確認します

$ php artisan list

~~ skip

batch
  batch:loadinfo              コマンドラインからの情報ロードバッチ

こんな感じでlistに表示されました。

実行してみる

$ php artisan batch:loadinfo


  [RuntimeException]
  Not enough arguments.



batch:loadinfo [--exampl[="..."]] example

初期作成のバッチは必ず引数とオプション入れろと起こられます。 引数は後にしたくてとりあえず動く所が見たいので app/commands/LoadInformation.phpの以下をコメントアウト

~~ skip

 // array('example', InputArgument::REQUIRED, 'An example argument.'),

~~ skip

 // array('exampl', null, InputOption::VALUE_OPTIONAL, 'An example option.', null),
 

これでエラーが出ずに抜けるはずです

出力を試す

public function fire()以下がexecuteメソッドなのでそこに記述します。

     public function fire()
     {
        //出力系
        var_dump('出力テスト');
        $this->info('標準出力');
        $this->error('エラー出力');
     }

再度実行

$ php artisan batch:loadinfo
string(15) "出力テスト"
標準出力
エラー出力

なるほどここまでくればあとはごにょごにょ試せば大体書けそうです早速バッチを作成してみる事にします

ここをみながらやってみましょう CLI:開発

補足

php artisan command:make TestCommand --path=app/batch --namespace=Batch --command=batch:test

例えばこんなコマンド任意な場所に作成したコマンドをapp/start/artisan.php にどうやって登録するのか、、イマイチ分からないので次に試したいと思う

Laravel4始めました

去年あたりから巷で人気のPHPフレームワーク
プロジェクトを属人化させないようにもちょっと触ってみたい所だったのでやってみようと思う。

環境

VPS サクラVPS
OS CentOS release 6.3
Apache Apache/2.2.15
PHP PHP 5.5.19

事前にPHPのバージョンを上げる必要がある。

導入

こちらを参考にさせていただきました。

多少の注意点としては

Error in exception handler.と言われたら

app/storageにパーミションが足りないと言われているので その際には 権限を付与してあげるのが良い。

$ chmod -R 777 app/storage

自分の場合はApacheのDocumentRootにシンボリックリンクを貼っているので
パスの通ったURLにアクセスすれば問題なく表示された。

f:id:raharu0425:20150106121553p:plain