Giovedì 22 Giugno 2017

Android: notifiche push, aggiornamento 2/2014

Creato il: 18 febbraio 2014

Tempo fa scrissi un articolo sull’integrazione delle notifiche push, ma le cose col tempo cambiano e Android non è da meno.
Eccomi qui con un nuovo articolo sull’integrazione del GCM, aggiornato a Febbraio 2014.

gcm-logo

In questo articolo NON spiegherò come integrare il tutto in php mysql e via dicendo per un database, perché sono cose già spiegate e che esulano da questo contesto.

1) OTTENERE IL SENDER ID
– andate nella pagina delle api di Google loggandovi con il vostro account google sviluppatore
– create un nuovo progetto, e tenete da conto il codice che vedrete nell’url (una cosa simile a https://code.google.com/apis/console/#project:460866929976), dopo i :, sarà il vostro sender_id
– cliccate su services e abilitate Google Cloud Messaging for Android

2) PREPARAZIONE DEL PROGETTO
Aprite il vostro build.gradle del modulo, e nelle dependancies inserite
compile “com.google.android.gms:play-services:3.1.+”
così da far compilare il gcm direttamente da Android Studio.
Date un ok e fate un refresh/clean di sicurezza.

3) L’ANDROID MANIFEST
Nel manifesto, bisogna ovviamente includere le capabilities per internet, e le activity/service/receiver del gcm (sostituite vostro_pacchetto, mi raccomando!), quindi:

nel nodo manifest

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<permission android:name="vostro_pacchetto.permission.C2D_MESSAGE" android:protectionLevel="signature" />
<uses-permission android:name="vostro_pacchetto.permission.C2D_MESSAGE" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />

nel nodo application

<receiver android:name="vostro_pacchetto.GCMBroadcastReceiver" android:permission="com.google.android.c2dm.permission.SEND" >
    <intent-filter>
        <action android:name="com.google.android.c2dm.intent.RECEIVE" />
        <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
        <category android:name="vostro_pacchetto" />
    </intent-filter>
</receiver>
<service android:name=".GCMIntentService" />

4) LA CLASSE GcmBroadcastReceiver.java
Questa classe si occupa di ricevere il messaggio broadcast e far partire la procedura di notifica, con wakelock se necessario:

package vostro_pacchetto;

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) {
        ComponentName comp = new ComponentName(context.getPackageName(),GCMIntentService.class.getName());
        startWakefulService(context, (intent.setComponent(comp)));
        setResultCode(Activity.RESULT_OK);
    }
}

5) LA CLASSE GCMIntentService
Questa classe si occupa della generazione vera e propria della notifica, e quindi di tutte le azioni che seguiranno ad essa (al suo click):

package vostro_pacchetto;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.media.RingtoneManager;
import android.net.Uri;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import com.google.android.gcm.GCMBaseIntentService;
import com.google.android.gcm.GCMConstants;

public class GCMIntentService  extends GCMBaseIntentService {
	private static final String TAG = "Push Notification";
	public static String RegId = "";
	@Override
	protected void onError(Context context, String errorId) {
		if(GCMConstants.ERROR_ACCOUNT_MISSING.equalsIgnoreCase(errorId)) {
			Log.v(TAG, "Error Account Missing");
		} else if(GCMConstants.ERROR_AUTHENTICATION_FAILED.equalsIgnoreCase(errorId)) {
			Log.v(TAG, "Error Authentication Failed");
		} else if(GCMConstants.ERROR_INVALID_PARAMETERS.equalsIgnoreCase(errorId)) {
			Log.v(TAG, "Error Invalid Parameters");
		} else if(GCMConstants.ERROR_INVALID_SENDER.equalsIgnoreCase(errorId)) {
			Log.v(TAG, "Error Invalid Sender");
		} else if(GCMConstants.ERROR_PHONE_REGISTRATION_ERROR.equalsIgnoreCase(errorId)) {
			Log.v(TAG, "Error Phone Registration Error");
		} else if(GCMConstants.ERROR_SERVICE_NOT_AVAILABLE.equalsIgnoreCase(errorId)) {
			Log.v(TAG, "Error Service Not Available");
		} 
	}
	@Override
	protected void onMessage(Context context, Intent intent) {
		String message="";
		if(intent.hasExtra("message")){
			message = intent.getStringExtra("message");
		}else{
			message = intent.getStringExtra("price");
		}
	    generateNotification(context, message);	
	
	}
	private static void generateNotification(Context context, String message) {
		 NotificationManager mNotificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
	        PendingIntent contentIntent = PendingIntent.getActivity(context, 0,new Intent(context, LoginActivity.class), 0);
	        NotificationCompat.Builder mBuilder =
	        		new NotificationCompat.Builder(context)
	        	.setSmallIcon(R.drawable.app_icon)
	        	.setContentTitle(context.getString(R.string.app_name))
	        	.setStyle(new NotificationCompat.BigTextStyle()
	        	.bigText(message))
	        	.setDefaults(Notification.DEFAULT_VIBRATE)
	        	.setContentText(message);
	        Uri alarmSound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
	        mBuilder.setSound(alarmSound);
	        mBuilder.setContentIntent(contentIntent);
	        mNotificationManager.notify(0, mBuilder.build());
	}
	@Override
	protected void onRegistered(Context context, String regId) {
		RegId=regId;
		Log.v(TAG, "Successfull Registration : "+regId);
		SharedPreferences pref=getSharedPreferences("GCM", 0);
		Editor edit=pref.edit();
		edit.putString("regId", regId);
		edit.commit();
	}
	@Override
	protected void onUnregistered(Context context, String regId) {		
	}
	@Override
	protected String[] getSenderIds(Context context) {
		return super.getSenderIds(context);
	}
	@Override
	protected void onDeletedMessages(Context context, int total) {
		super.onDeletedMessages(context, total);
	}
	@Override
	protected boolean onRecoverableError(Context context, String errorId) {
		return super.onRecoverableError(context, errorId);
	}
}

voi dovrete lavorare nella funzione generateNotification per fare quello che vorrete.

6) LA MODIFICA DELLA CLASSE JAVA PRINCIPALE
Qui modificheremo la classe principale, quella in cui vorrete far registrare l’utente, la vostra main insomma:
– create queste variabili globali (prima dell’onCreate)

public static final String EXTRA_MESSAGE = "messaggio";
public static final String PROPERTY_REG_ID = "registration_id";
private static final String PROPERTY_APP_VERSION = "1.0";
private static final int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
GoogleCloudMessaging gcm;
AtomicInteger msgId = new AtomicInteger();
Context context;
String regid="";
String user_id="";
SharedPreferences preferenze;
static final String TAG = "sceglietelo_come_volete";
String SENDER_ID = "vostro_sender_id";

– nell’onCreate

preferenze = PreferenceManager.getDefaultSharedPreferences(vostra_classe.this);
context = getApplicationContext();
if (checkPlayServices()) {
    gcm = GoogleCloudMessaging.getInstance(this);
    regid = getRegistrationId(context);
    if (regid.equals("")) {
        registerInBackground();
    }
} else {
    Log.i(TAG, "No valid Google Play Services APK found.");
}

– dopo onCreate

@Override
protected void onResume() {
    super.onResume();
    checkPlayServices();
}
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(TAG, "This device is not supported.");
			finish();
		}
		return false;
	}
	return true;
}
private void storeRegistrationId(Context context, String regId) {
	int appVersion = getAppVersion(context);
	Log.i(TAG, "Saving regId on app version " + appVersion);
	preferenze.edit().putString(PROPERTY_REG_ID,regId).commit();
	preferenze.edit().putInt(PROPERTY_APP_VERSION,appVersion).commit();
}
private String getRegistrationId(Context context) {
	String registrationId = preferenze.getString(PROPERTY_REG_ID, "");
	if (registrationId.equals("")) {
		Log.i(TAG, "Registration not found.");
		return "";
	}
	int registeredVersion = preferenze.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE);
	int currentVersion = getAppVersion(context);
	if (registeredVersion != currentVersion) {
		Log.i(TAG, "App version changed.");
		return "";
	}
	return registrationId;
}
private void registerInBackground() {
	new AsyncTask<Void, Void, String>() {
		@Override
		protected String doInBackground(Void... params) {
			String msg = "";
			try {
				if (gcm == null) {
					gcm = GoogleCloudMessaging.getInstance(context);
				}
				regid = gcm.register(SENDER_ID);
				msg = "Device registered, registration ID=" + regid;
				sendRegistrationIdToBackend();
				storeRegistrationId(context, regid);
			} catch (IOException ex) {
				msg = "Error :" + ex.getMessage();
			}
			return msg;
		}
		@Override
		protected void onPostExecute(String msg) {
			System.out.println("NOTIFICHE: "+msg);
		}
	}.execute(null, null, null);
}
@Override
protected void onDestroy() {
	super.onDestroy();
}
private static int getAppVersion(Context context) {
	try {
		PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
		return packageInfo.versionCode;
	} catch (NameNotFoundException e) {
		throw new RuntimeException("Could not get package name: " + e);
	}
}
private void sendRegistrationIdToBackend(){
	if(!regid.equals("")){
		qua_si_scrive_un_http_post_per_salvare_su_server
	}
}

7) FINE
Potete far riferimento al vecchio articolo (Android: integrare le notifiche push) per la parte server, se volete e vi serve.
Inoltre potete trovare in android-sdk/extras/google/gcm/samples/gcm-demo-client/ un progetto di esempio, o scaricarlo da me.

Buon coding!

  • Antongiacomo

    Ciao,
    il servizio GCMBaseIntentService risulta essere deprecato e google consiglia di usare le Api GoogleCloudMessaging . Ti andrebbe di aggiornare la guida? Sto cercando in rete da ore ma non trovo nulla che faccia al caso mio, nè una guida chiara e aggiornata

    • Volentieri, ma dammi tempo fino al weekend perché in questi giorni sono troppo full. Spero di ricordarmi di avvisarti.

    • tony

      a me non importa:

      import com.google.android.gcm.GCMBaseIntentService;
      import com.google.android.gcm.GCMConstants;

      segna errore su GCM che vuol dire ?

  • Roberto Scarciello

    Grazie, è stato un ottimo punto di partenza, sono riuscito ad implementare le push in poche ore.

  • Emanuele

    Ciao Davide posso chiederti di darmi il source da te testato di un http_post_per_salvare_su_server? ho provato quello che avevi nei sorgenti precedenti ma sinceramente non riesco a farlo funzionare

    Grazie

  • Emanuele

    Ciao
    nella parte ” qua_si_scrive_un_http_post_per_salvare_su_server”
    hai per caso del codice già scritto? scusa non è per infingardia ma ne ho provato parecchi in giro per la rete e sembrano non funzionare, se lo hai provato te mi fido 🙂

    Grazie mille
    Ema

  • Piero

    ciao, cosa intendi con:
    “qua_si_scrive_un_http_post_per_salvare_su_server”
    dentro al metodo: “sendRegistrationIdToBackend”?

    in questo punto si deve richiamare la pagina register.php?

    • Intendo che li è dove scrivi il codice per mandare l’id del telefono al tuo server, per salvarlo poi su db

  • Francesco

    Ho seguito il tuo tutorial, ho provato ieri sera, subito dopo aver installato l’app, e funzionava. Riprovato stamattina… “key expired”. Ho disinstallato e reinstallato l’app e ha subito funzionato. Come faccio a risolvere questo problema? Grazie

  • Cristiano

    Grazie finalmente un tutorial chiaro, sono settimane che cerco…
    Chiedo gentilmente se riesci a fornire un link per una parte server in Java, magari qualcosa da integrare nella stessa app, tipo con app Engine… Qualcosa del genere dato che PHP non lo conosco proprio come linguaggio. Grazie e complimenti.

  • Marco

    Salve, avrei un domanda specifica: la notifica appena spedita, oltre ad essere mostrata dall’ app, viene messa automaticamente nella lista delle notifiche del dispositivo (notification bar). Cliccando la notifica dalla notification bar viene riaperta l’app ma non viene più mostrato il testo! Come si potrebbe integrare il tutto con il tuo ottimo tutorial?
    Grazie Anticipatamente

    • Ciao, mi spiace ma non è possibile, sono due cose collegate a livello di sistema operativo.
      Se hai l’app già aperta, la notifica appare all’istante nell’app.
      Se hai l’app chiusa, clicchi sulla notifica e te la cancella mostrandoti l’app (e l’intent che tu hai scelto).
      L’app non cancella la notifica dalla notification (a meno di non seguire questa dev: http://developer.android.com/reference/android/app/NotificationManager.html#cancel%28int), la notification cancella la notifica e apre l’app.

      • Marco

        Grazie per la tempestività della risposta, però forse mi sono espresso non molto chiaramente. La serie di eventi che vorrei si verificasse è la seguente:
        1 – arriva notification (ad app chiusa)
        2 – il dispositivo la mostra a video e dopo n secondi va in notification bar
        3 – se successivamente apro notification bar e clicco sulla notifica, vorrei mi si aprisse l’app e che venisse visualizzato il testo della notifica dall’app stessa.

        I punti 1 e 2 già funzionano, mentre non riesco a fare in modo che si verifichi il punto 3.

      • ah! si ti eri spiegato male 🙂
        puoi giocare con la funzione generateNotification dentro a GCMIntentService.java
        – o cambi la classe di arrivo ad una tua fatta apposta
        – o aggiunti un extra all’intent per poi vedere se esiste e nel caso fare quello che vuoi (alert,toast,…)
        – o anziché far partire un intent fai qualcosìaltro che vuoi
        per fare la seconda opzione, per esempio, modifica:
        Intent intent_notifica=new Intent(context, LoginActivity.class);
        intent_notifica.putExtra(“notifica”,”ciao”);
        PendingIntent contentIntent = PendingIntent.getActivity(context, 0,intent_notifica, 0);
        e se nel LoginActivity (o quello che è) esiste un extra (hasExtra(“notifica”)) allora fai apparire qualcosa come più piace a te

      • Marco

        Non credo di aver capito! Quando dalla notification bar del dispositivo clicco su di una notifica relativa alla mia app, come faccio, all’apertura della mia app, ad intercettare la notifica stessa? Quale evento del codice gestisce tale azione?

      • te l’ho scritto…. la notifica viene generata in toto dalla funzione generateNotification dentro a GCMIntentService.java.
        basta modificare quella per fare quello che vuoi te.
        Se in quella funzione scrivi altro, o modifichi quello che c’è, puoi fare quello che vuoi.
        Io nel caso del tutorial genero una notifica classica, ma tu puoi generare un toast, un alert, blablabla.
        Se non riesci a capire quella funzione, devi studiare un po’ di più android, perché è una normalissima NotificationManager

  • Marco

    Ciao, grazie per l’aggiornamento; ma che differenza c’è con il precedente tutorial?
    Grazie anticipatamente

    • semplicemente che quello vecchio non era asincrono, ed è obsoleto

  • Pingback: Android: integrare le notifiche push | Davide Ferrari()

Back
I cookie ci aiutano a migliorare i nostri servizi. Utilizzando il nostro sito, accetti i cookie sul tuo dispositivo. Più informazioni | Chiudi