注(2014/04/10)
Google Cloud Messaging for Android が非推奨になって、プッシュ通知はGoogle Play servicesに統合されたようです。
そのため以前の記述では正常に動作しません。
改訂部分は以下のとおり。

1:クライアント側

    クライアント作成時に、gcm.jarというライブラリを使っていましたが、今後はGoogle Play Serviceをインポートして使います。

    gcm.jarを使わないので、新規にWakefulBroadcastReceiverを継承したクラスを作成して使います。

    通知されたメッセージは、イテレータを使って取り出します。

    レジストレーションIDは、アプリがバージョンアップするごとに新規に取得します。



2:サーバー側

    以前は、データをシングルプッシュする場合プレーンテキストでもOKでしたが、今後はJSONのみになるようです。



以下、改訂版。



プッシュ通知とは、サーバー経由でアプリに情報を通知する技術のこと。

ユーザーが能動的に取りにいく(fetch,pull)のではなく、勝手にサーバーから通知されます。

ライン(LINE)などで使われている例のヤツです。

スマホの場合、Apple(iOS)とGoogle(Android)では形式が違いますが、ここではAndroidのGCMを使ってみます。

GCM(Google Cloud Message)は以下のような形で通知が行われます。



cronサーバーについて

本来、ユーザー・サーバーとcronはひとつである方が望ましいですが、レンタルサーバーの場合、頻繁にcronを走らせていると文句を言われる可能性が高いですし、へたすると有無を言わせず削除される場合があります。

だもんで、外部cronサーバーでスケジュールのみ管理したほうがいいかも...。

また、cronサーバーという位置づけとはチト異なりますが、スケジュールに合わせて「何か」を実行するのに便利なサービスにGoogle Apps Scriptがあります。
Google Apps Script自体がそういうサービスと言うわけではなく、これを使えばそういうサービスも実行できるという意味です。
これについてはまた別ページでご紹介しますが、なかなかの優れものです(サーバーサイドJavaScriptを使います...Nodeみたいなもんか?)。


また、プッシュ通知は面白いというか不思議な使い方もできます。

サーバーから通知が着たらアラートを出さずに、要求に応じて実装された機能を実行できます。

つまりサーバーからの要求に応じて、自分自身を起動しなくてもバックグラウンド処理ができるということです。

例えば、特定のアプリを起動させるとか、位置情報を記録しておくとか、サーバーに何か問い合わせるとか...なんやかんや。

何かの変化に対して人間系を介在させずに即応する必要がある場合も有効かも。

プッシュ通知とは、端末がサーバーから「何か」を受け取るインターフェースと考えれば理解しやすいかもしれません。


通知でもらったデータを起動している画面に表示するにはどうするの?という場合は、ページ下の「おまけ(ヒント)」を見てみてね。




プッシュ通知のシステムを構築する場合、以下のステップを踏みます。

1:Google Developersサイトで、Project NumberとAPIキーを取得

     このProject Numberは一般的に、Project IDとかSender IDとか呼ばれるものです。

     ここでは統一して、Project IDと呼ぶことにします。

2:プッシュ通知用アプリケーションの作成

     Project IDを使用します。

     GCMから登録ID(Registratin ID)を取得しておきます。

3:プッシュ通知を行うユーザー・サーバーのアプリ作成

     APIキーとRegistratin IDを使用します。

     サーバーサイドのアプリ作成には何を使ってもいいですが、ここではPHPを使ってみます。






:Google Developersサイトで、Project NumberとAPIキーを取得

GoogleのAPIs console に入り、右下の「Return to original console」をクリックしてDashboardに移動して、Project Numberをメモしておきます。

これがProject IDです。

この下のProject IDとあるのは、ユーザーが指定する場合のもの。覚えやすいようにユニークに指定しますが、これはProject Numberにひも付けられていて内容は同じもののようです。



Servicesをクリックして、All servicesの中から「Google Cloud Messaging for Anfroid」を探して、OFFになっていたらONにします。





API Accessをクリックして、「Create new Server Key」をクリック。





あなたのユーザー・サーバーのIPアドレスを使って、Key for server apps (with IP locking)を取得します。

ここで取得されるのがAPIキーです。








:プッシュ通知用アプリケーションの作成

アプリの作成には「Google Play Service」を使います。

Eclipseを起動し、SDK Managerを開いて、ライブラリがインストールされていることを確認(まだの場合はInstallしてください)。



プロジェクトを新規に作成。

インストールされたら、google-play-service_libをインポートしておきます。

インポートは、ワークスペースにコピーする形で行います。そうしないと、Eclipseが元の場所を見失うことがあるようです。

例えば以下の場所にあります。

android-sdk/extras/google/google_play_services/libproject/google-play-services_lib

インポートしたら、これをライブラリとして使用します。





以下がプロジェクトコードです。

【AndroidManifest.xml】

xxxxxxには、あなたのアプリのパッケージ名をセットしてください。

赤字の部分は、新規に作成する2つのクラス名です。



<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>  
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />

<uses-permission
    android:name="xxxxxx.permission.RECEIVE" />

<permission
    android:name="xxxxxx.permission.C2D_MESSAGE"
    android:protectionLevel="signature" />
<uses-permission
    android:name="xxxxxx.permission.C2D_MESSAGE" />


<application

    ・・・・・・・・・・・
    ・・・・・・・・・・・
    ・・・・・・・・・・・

    
        <service android:name=".GcmIntentService" android:enabled="true"/>
        <receiver
                  android:name=".GcmBroadcastReceiver"
                  android:permission="com.google.android.c2dm.permission.SEND" >
                  <intent-filter>
                           <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                         <category android:name="xxxxxx" />
                  </intent-filter>
         </receiver>



    ・・・・・・・・・・・
    ・・・・・・・・・・・
    ・・・・・・・・・・・
</application>








追加
アプリを起動したままで通知を受けた場合、タイトルバーのメッセージをタップするとアプリが多重起動してしまいます。

これを回避するには、AndroidManifest.xmlのactivityに以下を設定してください。

android:launchMode="singleTop"

あるいは、topActivityが自前のActivityの場合は、通知バーに表示しないという手もあります。



【GcmIntentService.java】

import java.util.Iterator;
import com.google.android.gms.gcm.GoogleCloudMessaging;
import android.app.IntentService;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.SystemClock;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.widget.Toast;

public class GcmIntentService extends IntentService {
    public static final int NOTIFICATION_ID = 1;
    private NotificationManager mNotificationManager;
    NotificationCompat.Builder builder;

    public GcmIntentService() {
        super("GcmIntentService");
    }
    
    @Override
    protected void onHandleIntent(Intent intent) {
        Bundle extras = intent.getExtras();
        GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);
        String messageType = gcm.getMessageType(intent);
 
        if (!extras.isEmpty()) {
            if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR.equals(messageType)) {
                Log.d("LOG","messageType(error): " + messageType + ",body:" + extras.toString());
            } else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED.equals(messageType)) {
                Log.d("LOG","messageType(deleted): " + messageType + ",body:" + extras.toString());
            } else if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)) {
                Log.d("LOG","messageType(message): " + messageType + ",body:" + extras.toString());
                
                Iterator<String> it = extras.keySet().iterator();
                String key;
                String value;
                while(it.hasNext()) {
                    key = it.next();
                    value = extras.getString(key);
                    
                }
                
                //通知バーに表示
                sendNotification("Received Message");
            }
        }
        GcmBroadcastReceiver.completeWakefulIntent(intent);
         
         
    }
    
    private void sendNotification(String msg) {
        mNotificationManager = (NotificationManager)
                this.getSystemService(Context.NOTIFICATION_SERVICE);

        PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
                new Intent(this, MainActivity.class), 0);

        NotificationCompat.Builder mBuilder =
                new NotificationCompat.Builder(this)
        .setSmallIcon(R.drawable.ic_launcher)
        .setContentTitle("GCM Notification")
        .setStyle(new NotificationCompat.BigTextStyle()
        .bigText(msg))
        .setContentText(msg);

        mBuilder.setContentIntent(contentIntent);
        mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
    }
    
}




【GcmBroadcastReceiver.java】

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.support.v4.content.WakefulBroadcastReceiver;

public class GcmBroadcastReceiver extends WakefulBroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {

        // Explicitly specify that GcmMessageHandler will handle the intent.
        ComponentName comp = new ComponentName(context.getPackageName(),
                GcmIntentService.class.getName());

        // Start the service, keeping the device awake while it is launching.
        startWakefulService(context, (intent.setComponent(comp)));
        setResultCode(Activity.RESULT_OK);
    }
}




【MainActivity.java】

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.gcm.GoogleCloudMessaging;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;

import android.os.AsyncTask;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;


import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;

public class MainActivity extends Activity {
       private final String PROJECT_ID = "<プロジェクトID>";
        AsyncTask<Void, Void, String> registtask = null;
        public static final String EXTRA_MESSAGE = "message";
        public static final String PROPERTY_REG_ID = "registration_id";
        private static final String PROPERTY_APP_VERSION = "appVersion";
        private final static int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;

        public GoogleCloudMessaging gcm;
        public String regid = "";
        Context context;


    @Override
    public void onStart() {
        super.onStart();
        
        context = getApplicationContext();
        
            // Play serviceが有効かチェック
            if (checkPlayServices()) {
                
                gcm = GoogleCloudMessaging.getInstance(context);
                regid = getRegistrationId(context);
                
                if(regid.equals("")){
                    
                    regist_id();
                }
                
            } else {
                Log.i("", "Google Play Services は無効");
            }
    }


    private boolean checkPlayServices() {
        int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
        if (resultCode != ConnectionResult.SUCCESS) {
            if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)) {
                GooglePlayServicesUtil.getErrorDialog(resultCode, this,
                        PLAY_SERVICES_RESOLUTION_REQUEST).show();
            } else {
                Log.i("", "Play Service not support");
            }
            return false;
        }
        return true;
    }
    
    
    private String getRegistrationId(Context context) {
        final SharedPreferences prefs = getGCMPreferences(context);
        String registrationId = prefs.getString(PROPERTY_REG_ID, "");
        if (regid.equals("")) {
            return "";
        }
        // アプリケーションがバージョンアップされていた場合、レジストレーションIDをクリア
        int registeredVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE);
        
        int currentVersion = getAppVersion(context);
        if (registeredVersion != currentVersion) {
            return "";
        }

        return registrationId;
    }
    
    
    private SharedPreferences getGCMPreferences(Context context) {
        return getSharedPreferences(MainActivity.class.getSimpleName(),
                Context.MODE_PRIVATE);
    }
    
    
    private int getAppVersion(Context context) {
        
        try {
            PackageInfo packageInfo = context.getPackageManager()
                    .getPackageInfo(context.getPackageName(), 0);
            return packageInfo.versionCode;
        } catch (NameNotFoundException e) {
            throw new RuntimeException("package not found : " + e);
        }
        
        
    }
    
    
    private void storeRegistrationId(Context context, String regId) {
        final SharedPreferences prefs = getGCMPreferences(context);
        int appVersion = getAppVersion(context);
        SharedPreferences.Editor editor = prefs.edit();
        editor.putString(PROPERTY_REG_ID, regId);
        editor.putInt(PROPERTY_APP_VERSION, appVersion);
        editor.commit();
    }
    
    private void regist_id(){
        if (regid.equals("")) {
            registtask = new AsyncTask<Void, Void, String>() {
                @Override
                protected String doInBackground(Void... params) {
                    if (gcm == null) {
                        gcm = GoogleCloudMessaging.getInstance(context);
                    }
                    try {
                        //GCMサーバーへ登録
                        regid = gcm.register(PROJECT_ID);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    //取得したレジストレーションIDを自分のサーバーへ送信して記録しておく
          //サーバーサイドでは、この レジストレーションIDを使ってGCMに通知を要求します
                    registeid2YouServer(regid);
                    
                    // レジストレーションIDを端末に保存
                    storeRegistrationId(context, regid);
                    return null;
                }

                @Override
                protected void onPostExecute(String result) {
                    registtask = null;
                    
                    
                }
            };
            registtask.execute(null, null, null);
        }

    }
    
    
    public boolean registid2YouServer(String regId) {
        
        
        String serverUrl = "<あなたのサーバーのURLと実行ファイル名>";
        Map<String, String> params = new HashMap<String, String>();
        params.put("my_id", regId);
        try {
            post(serverUrl, params);
            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }
    
    public static void post(String endpoint, Map<String, String> params)
            throws IOException {
        URL url;
        try {
            url = new URL(endpoint);
        } catch (MalformedURLException e) {
            throw new IllegalArgumentException("invalid url: " + endpoint);
        }
        StringBuilder bodyBuilder = new StringBuilder();
        Iterator<Entry<String, String>> iterator = params.entrySet().iterator();
        while (iterator.hasNext()) {
            Entry<String, String> param = iterator.next();
            bodyBuilder.append(param.getKey()).append('=')
                    .append(param.getValue());
            if (iterator.hasNext()) {
                bodyBuilder.append('&');
            }
        }
        String body = bodyBuilder.toString();
        byte[] bytes = body.getBytes();
        HttpURLConnection conn = null;
        try {
            conn = (HttpURLConnection) url.openConnection();
            conn.setDoOutput(true);
            conn.setUseCaches(false);
            conn.setFixedLengthStreamingMode(bytes.length);
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Content-Type","application/x-www-form-urlencoded;charset=UTF-8");
            OutputStream out = conn.getOutputStream();
            out.write(bytes);
            out.close();
            int status = conn.getResponseCode();
            if (status != 200) {
              throw new IOException("Post failed with error code " + status);
            }
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }
      }
    





}









:プッシュ通知を行うユーザー・サーバーのアプリ作成

<?php
    $registatoin_ids = array();
    
    //レジストレーションIDの配列セットは記録したDBからとって来るなりなんなりしてやってください
    
    array_push($registatoin_ids, '<レジストレーションID>');
    
    
    //以下はダミー
    $name = "dummy";
    $title = "dummy";
    $message = "dummy";
    
    
    $gcm = new GCM();
    $send_content = array("name"=> $name ,"title"=> $title ,"message"=> $message);
    
    
    $result_android = $gcm->send_notification($registatoin_ids,$send_content,$ttl);
    
    
    class GCM{
        function __construct(){}
        public function send_notification($registatoin_ids,$send_content){
            // GOOGLE API KEY
            define("GOOGLE_API_KEY","<APIキー>");
            $url = "https://android.googleapis.com/gcm/send";
            $fields = array(
                "collapse_key" => "score_update",
                "delay_while_idle" => true,
                "registration_ids" => $registatoin_ids,
                "data" => $send_content
                
            );
            
            $headers = array(
                "Authorization: key=".GOOGLE_API_KEY,
                "Content-Type: application/json"
            );
            $ch = curl_init();
            curl_setopt($ch,CURLOPT_URL,$url);
            curl_setopt($ch,CURLOPT_POST,true);
            curl_setopt($ch,CURLOPT_HTTPHEADER,$headers);
            curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);
            curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,false);
            curl_setopt($ch,CURLOPT_POSTFIELDS,json_encode($fields));
            $result = curl_exec($ch);
            if($result === FALSE){
                die("Curl failed: ".curl_error($ch));
            }
            curl_close($ch);
            echo $result;
        }
    }
    
    
?> 




おまけ(ヒント)
GcmIntentServiceはActivityを経由していないので、このままでは他のActivityにデータを渡せません。
putExtraしてstartActivityするにしても、FLAG_ACTIVITY_NEW_TASKしか使えないので、getExtraしてもデータはnullになってます。
なので、intentをPendingIntentしてからstartActivityします。
起動される側では、onCreateでonNewIntent(getIntent());
onNewIntent(Intent intent)をOverrideして使います。
これで、getExtraでデータを取得できます。
多重起動を防ぐ意味で、AndroidManifest.xmlでこのActivityはsingleTopにしておきます。