2014年3月16日 星期日

[OSX] Xcode 5.1 - Unused Entity Issue: Unused Variable @ Mac OS X 10.9.2

Unused Entity Issue - Unused Variable

上次手滑更新 Xcode 後,發現新版 Facebook SDK 3.13 (2014/03/06 15:39:14) 編譯時會出現 Unused Entity Issue: Unused Variable 的錯誤,且看到有人提出了,暫時就先自己處理一下吧 :P

2014年3月15日 星期六

存在感、表現慾望與建設性

cocoaheads-taipei

因高手的介紹,第一次在台北參加小型聚會 CocoaHeads.tw 2014 3月聚會,場地是由知名 PicCollage app 的公司 Cardinal Blue 所提供的,也是他們家上班場所,十分舒適啊!

cocoaheads-taipei 2014-03

經過這次小型聚會,讓我想起不少點滴,像是以前在研究單位時,每兩周的技術分享一樣,真的,一個人所能接收的訊息、機會都跟工作有關,有道是男怕入錯行,不是沒有原因的 XD 這次聽到 KKBOX iOS developer 分享 CarPlay 挺有趣的。

心得:
  1. Cardinal Blue 辦公室真漂亮
  2. 不少人正積極尋找舞台,力求發揮(iOS app 派的商業特質?)
  3. 工作著實地影響一個人精進,若在工作上無法吸收到新技術,下班再去做就真的太累了 XD 此時就比較適合挑其他人生目標、舞台?
  4. 早期 iOS app 適合單打獨鬥,小功能就容易吸引人,現況適合打群架,要豐富的應用(如軟硬結合)才比較能有話題。此外,一旦開發週期拉長,產品無法及時曝光商業價值就降低
  5. 雖然 idea 的新穎性是個創業的因素,我自己第一份工作也常會因為看的多&遠,常常對別人開槍,後來某天後來有了新體悟:同樣的路,走 100 遍各有各的滋味 :) 此外,市場規模、地域性仍是個關鍵點,如同台灣現況很夯的 web service (團購、群眾募資等)不少也是從國外引進來... :P 所以,開槍時若多一點建設性方向的分享,應該會提升交流品質
最後,則是著實地提醒自己,該排好時程精實執行!

2014年3月14日 星期五

Android 開發筆記 - 使用 Google Cloud Messaging for Android (GCM)

原來 Android Cloud to Device Messaging Framework (C2DM) has been officially deprecated as of June 26, 2012 ... 現在改用 Google Cloud Messaging for Android
Google Cloud Messaging for Android (GCM) is a free service that helps developers send data from servers to their Android applications on Android devices, and upstream messages from the user's device back to the cloud. This could be a lightweight message telling the Android application that there is new data to be fetched from the server (for instance, a "new email" notification informing the application that it is out of sync with the back end), or it could be a message containing up to 4kb of payload data (so apps like instant messaging can consume the message directly). The GCM service handles all aspects of queueing of messages and delivery to the target Android application running on the target device.
一遍後,其實 GCM 的架構跟 Apple Push Notification service (APNs) 很像,主要都是先由 Mobile device 跟 Android server (Apple server) 註冊一個 token,接著再請 Mobile device 將此 token 交給 Service Provide。而 Service Provider 就利用這個 token 跟 Mobile device 傳訊息。比較不一樣的是 GCM 提供的架構多了一些設計,詳細請參考官網資料,在此僅簡易筆記一下。

Google Developer console:
  1. 首先,先到 Google Developer Console 建立一個 project,可得到 Service Provider ID
  2. 啟用 GCM service
  3. 建立 keys,APIs & auth > Credentials > Create a new key > Server key  (測試上 IP 設定為 0.0.0.0/0)
  4. 得到一組 API key,之後就是透過這個 key 跟 Mobile device's token 丟訊息
Android app:
  1. 下載 Google Play service library 並安置好
  2. 在 AndroidManifest.xml 宣告相關權限、並新增 <receiver> 來接收 Service Provider 的訊息
  3. 接著就是程式啟動時,透過 Google library 跟 GCM 註冊一個 token,此時需要指定 Service Provider ID 資訊
  4. 將 token 交給 Service Provider,如此一來 service provider 就可以傳訊息
  5. 實作好 <receiver>,當訊息進來時,啟動一個 service 發 notification 出去
接著是一些片段範例:
  • Service Provider ID: Your-Sender-ID
  • API KEY: API_Key
  • Android App Package Name: com.example.gcm
  • Mobile device token: MobileDeviceToken
  • Message:hello world
  • Payload: {"to":"MobileDeviceToken","data":{"message":"hello world","action":"com.example.gcm"}}
Service Provider 發訊息給指定的 Mobile device:

$ curl --header "Authorization: key=API_Key" --header Content-Type:"application/json" https://android.googleapis.com/gcm/send  -d '{"to":"MobileDeviceToken","data":{"message":"hello world","action":"com.example.gcm"}}'

Android app - AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.gcm"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="15" />

    <!--  ADD FOR Google Cloud Messaging ### Begin -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<permission android:name="com.example.gcm.permission.C2D_MESSAGE"
        android:protectionLevel="signature" />
<uses-permission android:name="com.example.gcm.permission.C2D_MESSAGE" />
    <!--  ADD FOR Google Cloud Messaging ### End -->


    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.gcm.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
   
        <!--  ADD FOR Google Cloud Messaging ### Begin -->
        <meta-data android:name="com.google.android.gms.version"
        android:value="@integer/google_play_services_version" />
        <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="com.example.gcm" />
            </intent-filter>
        </receiver>
        <service android:name=".GcmIntentService" />
        <!--  ADD FOR Google Cloud Messaging ### End -->

   
    </application>
</manifest>


Android app - Receiver:

package com.example.gcm;

import com.google.android.gms.gcm.GoogleCloudMessaging;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.content.WakefulBroadcastReceiver;
import android.util.Log;

public class GcmBroadcastReceiver extends WakefulBroadcastReceiver {
private static final String TAG = "GCM";

@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
Log.i(TAG, "GcmBroadcastReceiver");

GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(context);
String messageType = gcm.getMessageType(intent);
Log.i(TAG, "GcmBroadcastReceiver messageType:"+messageType);

Bundle extras = intent.getExtras();
if (!extras.isEmpty()) {
if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR.equals(messageType)) {

} else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED.equals(messageType)) {

} else if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)) {

}
Log.i(TAG, "GcmBroadcastReceiver Received: " + extras.toString());
}
}
}


Android app - MainActivity:

public class MainActivity extends ActionBarActivity {
private static final String TAG = "GCM";
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;
String SENDER_ID = "Your-Sender-ID";

GoogleCloudMessaging gcm;
AtomicInteger msgId = new AtomicInteger();
String regid;
Context context;

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 String getRegistrationId(Context context) {
   final SharedPreferences prefs = getGCMPreferences(context);
   String registrationId = prefs.getString(PROPERTY_REG_ID, "");
   if (registrationId.isEmpty()) {
       Log.i(TAG, "Registration not found.");
       return "";
   }
   // Check if app was updated; if so, it must clear the registration ID
   // since the existing regID is not guaranteed to work with the new
   // app version.
   int registeredVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE);
   int currentVersion = getAppVersion(context);
   if (registeredVersion != currentVersion) {
       Log.i(TAG, "App version changed.");
       return "";
   }
   return registrationId;
}

private SharedPreferences getGCMPreferences(Context context) {
   // This sample app persists the registration ID in shared preferences, but
   // how you store the regID in your app is up to you.
   return getSharedPreferences(MainActivity.class.getSimpleName(),
           Context.MODE_PRIVATE);
}

private static int getAppVersion(Context context) {
   try {
       PackageInfo packageInfo = context.getPackageManager()
               .getPackageInfo(context.getPackageName(), 0);
       return packageInfo.versionCode;
   } catch (NameNotFoundException e) {
       // should never happen
       throw new RuntimeException("Could not get package name: " + e);
   }
}
private void registerInBackground() {
new AsyncTask<Object, Object, Object>() {

@Override
protected Object doInBackground(final Object... arg0) {
// TODO Auto-generated method stub
String msg = "";
           try {
               if (gcm == null) {
                   gcm = GoogleCloudMessaging.getInstance(context);
               }
               regid = gcm.register(SENDER_ID);
               msg = "Device registered, registration ID=" + regid;

               // You should send the registration ID to your server over HTTP,
               // so it can use GCM/HTTP or CCS to send messages to your app.
               // The request to your server should be authenticated if your app
               // is using accounts.
               sendRegistrationIdToBackend();

               // For this demo: we don't need to send it because the device
               // will send upstream messages to a server that echo back the
               // message using the 'from' address in the message.

               // Persist the regID - no need to register again.
               storeRegistrationId(context, regid);
           } catch (IOException ex) {
               msg = "Error :" + ex.getMessage();
               // If there is an error, don't just keep trying to register.
               // Require the user to click a button again, or perform
               // exponential back-off.
           }
           return msg;
}

}.execute(null, null, null);
}

private void sendRegistrationIdToBackend() {
   // Your implementation here.
}

private void storeRegistrationId(Context context, String regId) {
   final SharedPreferences prefs = getGCMPreferences(context);
   int appVersion = getAppVersion(context);
   Log.i(TAG, "Saving regId on app version " + appVersion);
   Log.i(TAG, "Saving regId: " + regId);
   SharedPreferences.Editor editor = prefs.edit();
   editor.putString(PROPERTY_REG_ID, regId);
   editor.putInt(PROPERTY_APP_VERSION, appVersion);
   editor.commit();
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

        context = getApplicationContext();

        // Check device for Play Services APK. If check succeeds, proceed with
        //  GCM registration.
        if (checkPlayServices()) {
            gcm = GoogleCloudMessaging.getInstance(this);
            regid = getRegistrationId(context);

            if (regid.isEmpty()) {
                registerInBackground();
            }
        } else {
            Log.i(TAG, "No valid Google Play Services APK found.");
        }

if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new PlaceholderFragment()).commit();
}
}
// ...
}


更完整的範例請參考官網:GCM Client

Android 開發筆記 - 透過 Eclipse 新增 Android Project 時,勾選 Create Activity 卻沒自動建立

no activity

這個問題以前沒碰過,但類似的現象則有碰過,記得以前碰到時也是想到去看看 Eclipse 有沒更新,我是下載套裝軟體 (ADT Bundle for Mac),但一直新都沒東西 Orz

後來直接再加入 https://dl-ssl.google.com/android/eclipse/ 來使用,就終於有更新了,更新後的確也解決了。

只是追了一下,在 ADT Bundle for Mac -> Eclipse -> Help -> Install New Software -> Available Software Sites 中,其實有 Android Developer Tools Update Site (http://dl-ssl.google.com/android/eclipse/)。

但...我已經更新好了,就沒法手動更新看看,或是只要把 http 改成 https 也能解決?!

Android 開發筆記 - @integer/google_play_services_version no resource found

最近在把玩 Google Cloud Messaging 時,發現 AndroidManifest.xml 顯示這則錯誤訊息。

解法:

  1. 在 Android SDK Manager 中,確認 Extras -> Google Play service 已安裝
  2. 透過 Eclipse 進行 project import:File -> Import -> Android -> Existing Android Code Into Workspace -> 挑選 google-play-services_lib 的位置 (例如 /path/adt-bundle-mac-x86_64-20131030/sdk/extras/google/google_play_services/libproject/google-play-services_lib),記得要挑 libproject 裡的
  3. 在原本的 project 進行設定:Eclipse -> Project ->Properties -> Android -> Library -> Add -> 挑選 google-play-services_lib
如此一來,即可解決(Eclipse 有時不會馬上更新狀態,要把 project close 後再 open)。