2015年4月28日 星期二

AWS 筆記 - 關於 Load Balancers 之 Health Check 處理心得

若是 web server,通常 Health check 都是定期去請求 /index.html 是否正常,以此來判斷服務是否正常。而檢查 /index.html 有很多含義,例如當 server response time 超過 10 秒,也能標記為不正常等。

最近碰到一個案例,是在處理外包案時,發現對 / 或 /index.html 時,一直不正常,但用瀏覽器去看又覺得無恙,例如用 wget -d 也行,最後發現用 telnet 看到問題 XD

$ telnet server_ip 80
GET / 或 GET /index.html


接著看到一些 PHP 噴 error message。

發現,外包在一些設定檔內,透過判斷 $_SERVER['HTTP_HOST'] 是不是他們家的 server ,以此決定開啟 debug mode。

<?php
if ($_SERVER['HTTP_HOST'] == 'xxx.yyy.com') {
    $is_test_server = true;
} else {
    $is_test_server = false;
}


這件事大概只能透過 telnet 等不送 HTTP header 要求時,才會發現的 bug。這個 bug 還滿好解的,例如加上一些 isset($_SERVER['HTTP_HOST']) 等,但有一些 PHP framework 使用上會要求提供網址位置,例如:

<?php
$protocol = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http';
$config['base_url'] = $protocol . '://' . $_SERVER['HTTP_HOST'] . '/';


這時又只能再爆一次。若有心解決,就繼續改 code ,無心解決的話,可以把一些 error message 都關掉,或是把 Health check 改成 TCP 80 模式吧 XD

2015年4月27日 星期一

透過 Script 合併 MPEG2-TS (Transport Stream / TS) 與 ffmpeg 轉檔

因緣際會,處理一些用 M3U 串起來的一堆瑣碎的 .ts 檔案,這些檔案透過 M3U 的格式,可清楚描述第幾秒要播放哪個片段。然而,在一些測試上會略顯麻煩 Orz 所以撰寫一些 script 跟 ffmpeg 來把這些東西在整合成單一檔案 :P

眾多 ts 檔案該怎樣合併成一則?其實單純用 file append 即可:

$ cat 1.ts 2.ts 3.ts > output.ts

所以,假設有一個 M3U 時,可以透過以下指令抓出:

$ grep -v "#" list.m3u | xargs
1.ts 2.ts 3.ts ...


可惜啊,資料比數太 command line 可能會出爆,建議改用 script 來處理,例如 PHP:

<?php
foreach(explode("\n", file_get_contents($m3u_input)) as $line) {
if (empty($line) || $line[0] == '#')
continue;
$raw = file_get_contents($file);
if (!empty($raw))
file_put_contents("$output.raw", $raw, FILE_APPEND);
}


如此一來,就可以把一票 .ts 檔重新建立成單一檔案 .raw 。接著,再用 ffmpeg 轉成想要測試的格式吧!關於 ts 轉 mp4 ,可以透過 ffmepg 處理,其中 ffmpeg 會自己判斷輸出的格式(此例是 mp4),所以有需要也可以用 output.avi:

$ ffmpeg -i input.ts -vcodec copy -acodec copy output.mp4
$ ffmpeg -i input.ts -vcodec copy -acodec copy -bsf:a aac_adtstoasc output.mp4


如果有一票目錄 + M3U 的話,就掃一下目錄建立一票相關的,接著可以用 find 再把這一票轉一轉:

$ find . -name "*.raw" -exec  ffmpeg -i {} -vcodec copy -acodec copy -bsf:a aac_adtstoasc {}.mp4 \;

2015年4月25日 星期六

PHP CodeIgniter - 使用 Memcached

官方文件寫得很簡單,簡單的無法看懂:

@ https://ellislab.com/codeigniter/user-guide/libraries/caching.html
$this->load->driver('cache');
$this->cache->memcached->save('foo', 'bar', 10);


後來我自己去 trace 原始碼才知道該怎樣設定跟測試。首先,新增一個設定檔:

$ vim application/config/memcached.php
$config['memcached'] = array(
        'hostname' => 'Your_memecached_cluster',
        'port' => 11211,
        'weight' => 1
);


接著,使用:

class My_Library {
public function __construct() {
$this->ci =& get_instance();
$this->init();
}
public function init() {
$this->ci->load->driver('cache');
return $this->ci->cache->is_supported('memcached');
}
public function set($key, $value, $tts) {
return $this->ci->cache->memcached->save($key, $value, $tts);
}
public function delete($key) {
$this->ci->cache->memcached->delete($key);
}
}


關鍵之處就是要呼叫 $this->ci->cache->is_supported('memcached') 這段,才會從 application/config/memcached.php 把資訊更新進來 Orz

若需要 debug 的話:

$ find . -name "*mem*"
./system/libraries/Cache/drivers/Cache_memcached.php


找到 $this->_memcached 可以善用它,例如 $this->_memcached->getResultCode();

2015年4月21日 星期二

iOS 開發筆記 - 製作 iOS Simulator app 版本送交給 Facebook 進行 review (creating-ios-simulator-build-for-review)

似乎...Facebook SDK 文件沒找到?只好隨意 Google 一些資料

$ cd /path/project
$ xcodebuild -showsdks
OS X SDKs:
OS X 10.9                     -sdk macosx10.9
OS X 10.10                     -sdk macosx10.10

iOS SDKs:
iOS 8.3                       -sdk iphoneos8.3

iOS Simulator SDKs:
Simulator - iOS 8.3           -sdk iphonesimulator8.3

$ xcodebuild -arch i386 -sdk iphonesimulator8.3


若用 CocoaPods (碰到 ld: library not found for -lPods-XXX ) 或 xcworkspace 維護的,需要多一點指令:

$ xcodebuild -arch i386 -sdk iphonesimulator8.3 -workspace YourName.xcworkspace -scheme YourName

接著,想執行看看:

$ ios-sim launch /Users/user/Library/Developer/Xcode/DerivedData/YourName-xxxxxx/Build/Products/Debug-iphonesimulator/YourName.app

其中 ios-sim 可以逛一下這邊:https://github.com/phonegap/ios-sim

2015年4月16日 星期四

PHP 逆向工程 - 列出已定的 function 及相關參數

最近接手一包程式碼,發現這個 php source code 裡頭的 function 定義搞了很多層:

<?php
if (!function_exists('a')) {
function a($input) {
$input = base64_decode($input);
// ...decoding
$raw = file_get_contents(__FILE__);
// comparing...
}
}
eval(a("XXXXX"));


因為是這樣的模式,猜測應該是有保護程式碼不能在其他地方用。為了想知道做了什麼事,就惡搞一下,試試能不能看到到底有哪些 function 被定義或是使用:

<?php
require '/path/enc_src.php';

print_r(get_defined_functions()['user']);
foreach(get_defined_functions()['user'] as $func) {
        $rf = new ReflectionFunction($func);
        echo "func name: [$func]\n";
        foreach( $rf->getParameters() as $param )
                print "$param\n";
}


如此一來,就可以發他定義了哪些 function 及參數意義等:

func name: [func_name_1]
Parameter #0 [ <required> $a ]
Parameter #1 [ <required> $b ]
Parameter #2 [ <required> $c ]
Parameter #3 [ <required> $d ]
Parameter #4 [ <required> $e ]
...

接下來有空再追開了什麼檔案...

2015年4月15日 星期三

AWS 筆記 - 使用 Amazon ElastiCache - Memcached Cluster



由於服務是儲存在 RDBMS ,以此做出許多關聯設計。有朋友分享實際案例,對於線上服務盡量不要用 JOIN 等技術,例如把查詢結果定期儲存一張表上來使用,然而,在開發階段只能一切求快 XD 因此,快速開發後就想一下有沒有快一點的強化服務方式,於是乎就想到透過 memcache 來暫存儲存結果。於是乎,來使用一下 Amazon ElastiCache。

原先想自己在 EC2 上架設,接著就想到要維護 Cluster 的問題,所以就...先試一下別人的產品吧 XD 開幾台最便宜的 cache.t2.micro (555MB memory),然後開個兩台。

接著就可以得到一個入口點,如:

your_name.cfg.your_region.cache.amazonaws.com:11211

因此,有人幫你維護 cluster 也是件好事吧!有專人維護入口點,也有人維護節點,都可以在 AWS 控制界面看到。



以 PHP 程式範例就接用吧!

<?php

$memcache = new Memcache;
$memcache->connect('your_name.cfg.your_region.cache.amazonaws.com', 11211);

for ($i=0 ; $i<1024 * 1024 * 30 ; ++$i) {
        $key = "key-$i";
        $memcache->set($key, $i);
}


執行:

$ time php test.php

就可以在 Amzon ElastiCache 管理介面觀看那兩個 node 的情況,簡言之,兩個 node 是一直彼此 sync 的啦。且過程中也可以很便利的透過 Add Node 新增機器,而程式碼由於都寫定在一個入口點了,所以維護非常輕鬆。




若是把範例程式改成這樣,會發現跑一陣子後(約四千,大概跟 DNS Cache有關)會出現連不到的問題,我想,站在 cache 機制上,服務本身也不會出錯才對 :P

<?php
for ($i=0 ; $i<1024 * 1024 * 30 ; ++$i) {
        $key = "key-$i";

        $memcache = new Memcache;
        $memcache->connect('your_cache.cfg.region.cache.amazonaws.com', 11211);
        $memcache->set($key, $i);
        $memcache->get($key);
        $memcache->close();
        echo "finish $i\n";
}


$ php test.php
...

PHP Warning:  Memcache::connect(): php_network_getaddresses: getaddrinfo failed: Name or service not known in /path/test.php on line 6
PHP Stack trace:
PHP   1. {main}() /path/test.php:0
PHP   2. Memcache->connect() /path/test.php:6
PHP Notice:  Memcache::connect(): Server your_cache.cfg.region.cache.amazonaws.com (tcp 11211, udp 0) failed with: php_network_getaddresses: ge
taddrinfo failed: Name or service not known (0) in /path/test.php on line 6
PHP Stack trace:
PHP   1. {main}() /path/test.php:0
PHP   2. Memcache->connect() /path/test.php:6
PHP Warning:  Memcache::connect(): Can't connect to your_cache.cfg.region.cache.amazonaws.com:11211, php_network_getaddresses: getaddrinfo fail
ed: Name or service not known (0) in /path/test.php on line 6
PHP Stack trace:
PHP   1. {main}() /path/test.php:0
PHP   2. Memcache->connect() /path/test.php:6

2015年4月14日 星期二

與大老闆有約

最近跟幾位大老闆聊天,也回顧起前老闆的互動,有些經驗真的是自己親身體驗的,所以特別有感觸,有些則是像看著書看電影,不如聽人親口說出來:

  • 所謂的 startup 是個生活的方式,若是以音樂圈舉例,大概出門都想比個 rock 手勢 XD 整體上就一種工作生活的模式,並非 startup 就過得比較好、比較爽。
  • 有錢出錢,沒錢想出技術、時間,但是,所有的技術股本質上都是有對等的錢,有些工程師會想拿很大比例的股票,用的是技術股而非自己的錢去買,但技術股一開始規劃太多會讓投資人不敢投資。而技術股本身就是有等值的金錢價值,無論有沒有成功上市還是一張壁紙,在當下就是有對等價值。所以,有機會拿到技術股時,要想想這些股票其實是 co-founder 的錢,他們為了打造團隊忍痛分出來的
  • 有些新創會讓你把年薪的幾趴用技術股,但真正有價值的公司,股票是很貴的,能給錢就給錢,不會想給技術股,因為通常每一輪增資時,股價是飛快地成長,小則幾十倍,大則百倍、千倍 XD 你想想 co-founder 一開始手上的 1 萬,經過一輪變成一百萬時,你覺得他會想給你股票嗎?
  • 團隊永遠優於題目,對天使來說,投資的是你的團隊,而不是你的題目
  • 搞 startup 的方向有很多種,有人想要長期持有,也有人想要短期翻百倍、千倍脫手,並沒有不好之處
  • 公司願意給人高薪時,某個角度是資金狀況達到一個水準,某個角度是為了打造明星戰隊,好讓下一輪募資有更佳的價值,其實都是好事。當公司給高薪卻不給股票時,有時不要太貪心,至少拿到高薪了!換個角度來說,這可是 co-founder 早期的投資成果呢!不然你連高薪的機會都沒有。
  • 對天使來說,大概投資 25 間才會有一間成功,若早期報酬率有25倍,那只是剛好打平而已

至於進 startup 到底要幹嘛?基本上選擇當 co-founder 後,已經是一條不歸路了,無論成功或失敗,自身都是很有價值的,要記得最重要的永遠是人脈圈,練人脈等同練信任感,失敗並不會怎樣,至少天使會知道你下次不會再犯一樣的錯了。

不過,有一個非常現實的問題...連續創業可以創造價值沒錯,但是 co-founder 的股份初期再怎樣還是用等價的錢打造的,所以想連續創業...還是要有一些錢錢才可以進行。別忘了 co-founder 還是要盡量保持大量持股,因為每一輪的角色會稀釋,甚至最後要找專業經理人時,必須自己拿股份出來吸引專業經理人。這筆股份/錢,我想任何投資人都不想出的。

最後一提,若30歲了加入公司的角度還是想著練功,那永遠都無法跳脫員工的角度。30歲這個年紀,已經該去指揮別人了。

2015年4月13日 星期一

iOS 開發筆記 - 使用 Facebook SDK 4.0 分享至塗鴉牆與簡易的 publish_actions 權限判斷流程

大概都遵循 Facebook 文件,這邊指紀錄在一個 ViewController 中,啟動分享至使用者塗鴉牆的流程,包括判斷使用者是否安裝過 Facebook app、是否授權 publish_actions 等,以及選擇用 Facebook app 分享或是用 Facebook SDK 分享等。

#import <FBSDKCoreKit/FBSDKCoreKit.h>
#import <FBSDKShareKit/FBSDKShareLinkContent.h>
#import <FBSDKShareKit/FBSDKShareDialog.h>
#import <FBSDKShareKit/FBSDKShareAPI.h>
#import <FBSDKShareKit/FBSDKShareOpenGraphAction.h>

#import <FBSDKLoginKit/FBSDKLoginManager.h>
#import <FBSDKLoginKit/FBSDKLoginManagerLoginResult.h>

@interface ViewController () <FBSDKSharingDelegate>
@property (nonatomic, strong) NSString *url;
@end

@implementation ViewController

#pragma mark - FBSDKSharingDelegate

- (void)sharer:(id<FBSDKSharing>)sharer didCompleteWithResults:(NSDictionary *)results {
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"INFO" message:@"done" delegate:nil cancelButtonTitle:@"ok" otherButtonTitles: nil];
    [alertView show];
}

- (void)sharer:(id<FBSDKSharing>)sharer didFailWithError:(NSError *)error {
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"INFO" message:@"error" delegate:nil cancelButtonTitle:@"ok" otherButtonTitles: nil];
    [alertView show];
}

- (void)sharerDidCancel:(id<FBSDKSharing>)sharer {
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"INFO" message:@"cancel" delegate:nil cancelButtonTitle:@"ok" otherButtonTitles: nil];
    [alertView show];
}

#pragma mark - share methods

- (void)useFacebookApp {
    FBSDKShareLinkContent *content = [[FBSDKShareLinkContent alloc] init];
    content.contentURL = [NSURL URLWithString:self.url];
    [FBSDKShareDialog showFromViewController:self
                                 withContent:content
                                    delegate:nil];
}

- (void)useFacebookSDK {
    FBSDKShareLinkContent *content = [[FBSDKShareLinkContent alloc] init];
    content.contentURL = [NSURL URLWithString:self.url];
    [FBSDKShareAPI shareWithContent:content delegate:self];
}

#pragma mark - init action

- (void)doShare
{
    if (![FBSDKAccessToken currentAccessToken]) {
        FBSDKLoginManager *login = [[FBSDKLoginManager alloc] init];
        [login logInWithPublishPermissions:@[@"publish_actions"] handler:^(FBSDKLoginManagerLoginResult *result, NSError *error) {
            if (error) {
                // Process error
                NSLog(@"Process error");
            } else if (result.isCancelled) {
                // Handle cancellations
            } else {
                if ([result.grantedPermissions containsObject:@"publish_actions"]) {
                    [self useFacebookSDK];
                } else {
                    [self useFacebookApp];
                }
            }
        }];
        return;
    } else if ([[FBSDKAccessToken currentAccessToken] hasGranted:@"publish_actions"]) {
        [self useFacebookSDK];
    } else {
        [self useFacebookApp];
    }
    return;
}

@end

2015年4月12日 星期日

16個夏天

去年就留意到這個劇名,跟自己的 blog 高度相似 XD 要不是 Yahoo! 首頁正在推某家免費看台灣戲劇,我應該也不會去看吧!就這樣開心地線上看著免錢的正版戲劇,每次在家運動就看一集,好像也超過一個月了?!今天很悶不想寫程式,就坐在電視前面,一口氣連看了六集,不知不覺也就快看完了,果然好看的戲,應該靜靜地享受情感流串啊,這才是尊重導演、編劇啊。

原先被一開始的學生情懷牽動,直到有一天跑去看 wiki 才發現案情不單純 XDD 之後反而就沒那麼有興趣,因為這個年代對愛情議題比就無感 XD 但這部戲劇情編排不錯,戲中點滴牽動著台灣相關事件,像 921 大地震、SARS、金融海嘯,甚至hTC、無名小站、臉書等有,反而很對味,也想起周邊一些人、事、物。

想想看周邊人在幹嘛?有換工作的、有結婚的、有生小孩的,也有遲疑著人生的,也有挑戰人生的,更有剛裝潢好辦公室準備創業的,不過聊聊最貼切自己的資訊產業,真的有種說不出的微妙感。不知是不是 open source 使得門檻降低?不知道是不是慾望還貪念,不少人都在進行所謂的 side project 呢。大家還是常說台灣資訊業的不是,或是拿著過去台灣經濟起飛的時代評論著現在,轉個彎來想想:當時的人會知道那時將經濟起飛嗎?有一件事仍是不變的,大家都不想錯過什麼吧?就這樣繼續讓生命更加豐富吧!

如同 16 個夏天一樣,沒有過去,哪有現在,更別害怕失敗而止步啦。

2015年4月9日 星期四

iOS 開發筆記 - Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[0]'

閒暇時,追了這個 bug 很久...

主因是 source code 又沒看到 NSPlaceholderArray 關鍵字,最後才發現這是只是單純的 NSArray 相關更動事件。

要找的關鍵處有幾個:
  • [arrayObject addObject:object];
  • [arrayObject addObjectsFromArray:arrayObject];
  • [NSArray arrayWithObject:object];
  • ...
結果漏了一個生成 NSArray 很好用的語法:
  • @[]
出包之處就是 @[] 相關動作啦,例如 @[obj1, obj2, obj3]; 其中有個 object 是 nil 啦

2015年4月8日 星期三

PHP CodeIgniter - 透過 Apache RewriteRule 限制 CGI 功能

基於一些開發需求,想要侷限網站的功能。剛好這個網站服務是用 PHP CodeIgniter 開發的,整套都是走 RewriteRule 來進行,例如一個 URL 位置,都一律導向到 PHP CodeIgniter Project 的 index.php?/path 來分析。

假設原本網站有 3 個主要網址,分別是 /api, /shopping, / 等,假設想要讓此網站只開啟 /api 時,這時就可以透過 Apache RewriteRule 來限制:

<VirtualHost *:80>
DocumentRoot /ci-project/
DirectoryIndex index.html index.php index.htm

# empty page
Alias /empty     /var/www/html/

<Directory />
Options FollowSymLinks
AllowOverride None
</Directory>

<Directory /var/www/html/>
Options FollowSymLinks
AllowOverride None
DirectoryIndex index.html index.php index.htm
Require all granted
Satisfy Any
</Directory>

<Directory /ci-project>
Options FollowSymLinks
DirectoryIndex index.php
AllowOverride None
#AllowOverride All
Require all granted
Satisfy Any

<IfModule mod_rewrite.c>
RewriteEngine On
#LogLevel alert rewrite:trace6
RewriteBase /

# disable index.php with empty QUERY_STRING
RewriteCond %{QUERY_STRING} ^$
RewriteRule ^index\.php$ /empty/ [L]

RewriteCond $1 !^(index\.php|images|css|js|favicon\.ico)
# enable /api only
RewriteCond $1 ^api.*$
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ ./index.php?/$1 [L,QSA]
</IfModule>
</Directory>
</VirtualHost>


如此一來,只要使用者逛 / 位置,則會顯示 /var/www/html 的資料,而逛 /api 系列時,則是可以正常引導到 PHP CodeIgniter 相關程式碼來處理。

2015年4月5日 星期日

人生第一台 NAS - Synology DS215j 與 DiskStation 初體驗


假日閒聊時,發現家人正困惑著資料儲存的問題,四五年前買的筆電、桌機有的硬碟大概才 256GB、512GB 而已,隨著數位相機、手機拍照的盛起,有越來越多資料不知該怎樣處理,甚至許多歷史因素懶得處理 Orz 想著想著,看來是個時機點,買一台 NAS 了!周邊有花錢買 NAS 的,有八成以上有抓 BT 的習慣,少數是本身就是拍照的愛好者,有的甚至就是用外接硬碟來備份,為此也困惑許久,最後就當作嘗鮮吧 XD

至於為何買 Synology ?單純看到前同事用這系列,而型號只是單純買 2015 年的產品。雖然我自己土砲一下要裝個 server 也不是難事,只是時間寶貴啊,花一點錢試試看別人的產品,一台機殼、主機板,算起來跟自己去組一台要花的成本相當,差只差在自己組裝的硬體規格會比較好,可以接的硬碟會比較多,但通常也不會比專門做 NAS 的省電,並且整體空間設計也很難比別人好看。

 

使用心得還不賴!以前也開發過這類產品,例如一個 embedded linux 搭配 Web UI 來提供服務,但直到我試了 Synology DiskStation 的介面,真的輸人輸太多!Synology 真的做得很好 :P 過去開發產品容易執著在技術上,而使用者其實更在意絕對是 UIUX 的部分。

安裝完硬碟且接上網路後,只要是在同一個無線網路 AP 下,直接用瀏覽器輸入 find.synology.com 即可連線到,透過 DNS 的機制 UIUX 做的真的不錯,十分親民。而 Mobile app 也是一樣的道理,下載完專屬的 app 後,可以透過本地網路的瀏覽方可找到機器。而機器的設定全部都走 Web UI ,沒點幾下就都搞定了!



不少服務擴充:





整體上,對我這個產品的門外漢來說,做的真的不錯,分數有 90 分以上 :P 目前使用上都還順利,若真的要挑惕的話,大概是我買的這台是兩顆碟,已備份技術來看,就是會損失一顆硬碟的空間(買兩顆 1TB,一顆備份,真正只能用 1TB空間),另外則是我在 MBP 2012 機器上,透過 FileZilla 走 FTP 傳資料時,若一口氣開 10 個連線,平均 5~10分鐘會出現連線 timeout 的現象,不確定是 MBP 跑不動,還是 NAS 連線數應付不來。

2015年4月3日 星期五

iOS 開發筆記 - 使用 Facebook iOS SDK 4 之處理 FBSDKLoginManager result.isCancelled = true 問題

很久沒用 FB iOS SDK 了,一不小心發現已經進入到 4.0 版本,接著一堆 code 失效 Orz 接著,又來適應一下新板 SDK 的情況。

看著文件刻了一下:

#import <FBSDKCoreKit/FBSDKCoreKit.h>
#import <FBSDKLoginKit/FBSDKLoginManager.h>
#import <FBSDKLoginKit/FBSDKLoginManagerLoginResult.h>
- (void)testFB {
    if (![FBSDKAccessToken currentAccessToken]) {
        NSLog(@"do login");
     
        FBSDKLoginManager *login = [[FBSDKLoginManager alloc] init];
        [login logInWithPublishPermissions:@[@"publish_actions"] handler:^(FBSDKLoginManagerLoginResult *result, NSError *error) {
            if (error) {
                // Process error
                NSLog(@"Process error");
            } else if (result.isCancelled) {
                // Handle cancellations
                NSLog(@"Handle cancellations: %@, %@, %@", result, result.grantedPermissions, [FBSDKAccessToken currentAccessToken]);
            } else {
                if ([result.grantedPermissions containsObject:@"publish_actions"]) {
                    NSLog(@"with publish_actions");
                } else {
                    NSLog(@"without publish_actions");
                }
            }
        }];
        return;
    } else if ([[FBSDKAccessToken currentAccessToken] hasGranted:@"publish_actions"]) {
        NSLog(@"use publish_actions");
    }
 
    NSLog(@"token: %@", [FBSDKAccessToken currentAccessToken]);
}


很奇妙地,無論使用者如何授權此 Facebook app ,永遠都會落入 result.isCancelled == true 的事件中,然後 token 永遠都 null。

追了一下,原來還要設定 AppDelegate 太久沒用都忘記了,啊 FB iOS SDK 文件上也沒提醒 Orz

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
    return [[FBSDKApplicationDelegate sharedInstance] application:application
                                                          openURL:url
                                                sourceApplication:sourceApplication
                                                       annotation:annotation];
}


如此一來,有幾種情境就都能抓到了:
  • 使用者不想授權此 app => Handle cancellations
  • 使用者授權此 app ,但不想給 publish_actions 權限 => without publish_actions
  • 使用者授權此 app ,並給予 publish_actions 權限 => with publish_actions

2015年4月2日 星期四

Android 開發筆記 - 嘗試取出 WebView Javascript console.log @ 小米2S Android 4.1.1

原先 Android 4.4 已經有漂亮的解法:Debugging Web Apps,但是,對於其他版本的就 糟糕了。

所幸小米2S的系統還算 ok,只需啟動開發者模式,並播打電話 *#*#717717#*#* 後,即可開啟完整的開發者模式,透過 adb 連線。

$ adb devices
...至少要看到小米2S機器

$ adb logcat com.android.chromium:* | grep 'WebViewerFragment'
...
D/WebViewerFragment(25584): onScroll:192.34418
D/WebViewerFragment(25584): onScroll:344.65582
D/WebViewerFragment(25584): onFling:-4593.181
D/WebViewerFragment(25584): onScroll:-71.85919
D/WebViewerFragment(25584): onScroll:-183.00793
D/WebViewerFragment(25584): onScroll:-61.014893
D/WebViewerFragment(25584): onScroll:-77.11798
D/WebViewerFragment(25584): onFling:5177.8477
D/WebViewerFragment(25584): onScroll:-42.316345
D/WebViewerFragment(25584): onScroll:-70.78821
D/WebViewerFragment(25584): onScroll:-89.440186
D/WebViewerFragment(25584): onScroll:-105.551575
D/WebViewerFragment(25584): onScroll:-117.46338
D/WebViewerFragment(25584): onFling:8612.66
...


如此,簡易版搞定 :P 上述訊息是 javascript console.log。另一外提的是,我把小米2s系統預設瀏覽器改成 chrom browser ,不知使用系統預設 browser 是否一樣,以及我安裝的 app 可能是 debug 版本,有一說 release 版本可能看不到。