著名的工具軟體Clean Master有一項遊戲加速的功能, 它會放一個捷徑在桌面上, 而它的長得就像是跟資料夾一樣:

之前沒仔細去看它, 一直以為它是個小工具(AppWidget), 但其實它只是個捷徑而已, 點選它會跳出一個透明背景的對話窗, 所以很容易誤以為是桌面上的資料夾(如圖右)

這用了個小技巧, 雖然有點唬人, 但其實不難, 以下就如法炮製一個類似的吧!

在這實驗, 就把Youtube, Google plus, Google map三個app的圖示放一起, 邊框, 麻煩就先省略

利用"com.android.launcher.action.INSTALL_SHORTCUT"這個Intent可以在桌面上創建捷徑, 應該幾乎所有的桌面軟體都有支援, 關鍵點在於這Intent裡會帶的Intent.EXTRA_SHORTCUT_ICON, 這可以帶一個Bitmap來當作這個捷徑的圖示, 沒意外的, 就是從這邊動手

因為我們需要把四個圖示畫到一個上面, 所以每個圖示變成原本的1/4, 因此, 在用BitmapFactory.decodeResource載入圖示時, 可以把sample size設成2(也就是1/4大小), 這樣可以減少一些記憶體的使用, 取得了圖示後就可以把它們畫到另一個新的Bitmap了

要注意的是, 要用INSTALL_SHORTCUT的話, 要在AndroidManifest.xml裡加上:

<uses-permission android:name=“com.android.launcher.permission.INSTALL_SHORTCUT” />

這方法的缺點是, 一旦捷徑被創建好後, 就沒機會改它的圖示了

認真說的話, 這也算不上啥非官方API, 算是一個為了抓取Play store上資訊的一個小小工具: PlaystoreUtil

現在很多網路的服務, 大多有提供開放的REST API來供人寫原生的程式使用, 當然也有非常多並沒有, 像是Play store, 目前就沒開放的API可供存取, 剛好想要有個東西可以查詢某個app在play store上是屬於啥分類的, 所以就乾脆自己自製一個囉…

現在的網頁, 大多結構性很好, 所以就算沒有REST API, 其實也不難處理, 搭配上 jsoup , 可以說輕而易舉

jsoup是一個可以用css selector來解析html的Java函式庫, 有了這個, 解析html可以不用辛苦的爬dom tree, 只要幾行簡單的程式即可:

Document doc = Jsoup.connect("http://example.com/").get;

Elements links = doc.select("a[href]"); // a with href
Elements pngs = doc.select("img[src$=.png]");

再來看看play store

先看看每個app的資訊畫面, 以Facebook為例, 它的url是 –

https://play.google.com/store/apps/details?id=com.facebook.katana&hl=ja

很明顯的, id後面是package name, 另外如果加上"&hl=“可以指定語言, 然後再看到頁面上:

在Facebook (公司名稱)下方有個分類, 可以使用Chrome的開發人員工具(我比較習慣這個), 找到這個連結的css class名稱是”document-subtitle category“,而名稱則在它底下的一個span, 這span有個屬性itemprop, 值是genre

因此, 透過以下這段code就可以取到類別名稱囉

Document doc = Jsoup.connect("https://play.google.com/store/apps/details?id=" + packageName + "&hl=" + locale.getISO3Language()).get();
Elements elements = doc.select("span[itemprop=genre]");

當然, 這方法不只適用於play store, 其他網頁也可以嘗試用這個方法來取得資料

詳細的範例在: https://github.com/julianshen/PlaystoreUtil

Phantom.js好用的地方就是可以以下面這一段簡單的程式來抓取網頁的縮圖:

var page = require('webpage').create();
page.open('http://github.com/', function() {
  page.render('github.png');
  phantom.exit();
});    

結果是像這樣:

不過, 這邊要說的並不是Phantom.js, 而是在Android上怎像這樣抓網頁縮圖

在KitKat (API level 19)之前, WebView有capturePicture()這個API可以達成類似的效果, 不過, 這API在API level 19已經被deprecated掉了, 並且建議改用onDraw()

改用onDraw()其實也不太難, 只要在WebViewClient.onPageFinished()裡做:

    Bitmap mBitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(mBitmap);
    view.draw(canvas);

注意, 必須是在page finish之後才可以去做, 因為這時候網頁才算載入完畢, 不過結果有點不是很完美, 並非是整頁的

為了方便, 我寫成一個IntentService之後可以拿來重複利用:

WebThumbGrabber

寫成IntentService的好處是讓他可以在background thread去做, 前景的activity可以完全不用管它, 不過幾個webview的methods還是需要在main thread去跑, 為此, 在Service onCreate()引入了一個Handler, 為了避免IntentService在頁面還沒載入回來前就結束掉, 用了一個Semaphore來等待縮圖完成才結束整項工作

縮圖產生後, 必須要把產生的結果回傳給Activity, 這邊用的是"Activity.createPendingResult()“, 把產生的PendingIntent傳給IntentService來回傳結果, 這動作跟startActivityForResult類似, 回傳的結果也是由Activity.onActivityResult()來接收(可參考DemoActivity)

先說在前面…不是鼓勵盜版, 也不是鼓勵去操掛人家網站, 這篇的出發點是單純好玩…起因是, 雖然現在大多數的人看漫畫都用布卡來看, 但布卡也不是所有漫畫都找的到, 上次聽到有人說布卡找不到某某漫畫, 就Google了一下, 總還是會有些網站有, 而且發現, 這些網站的結構都差不多, 所以就想說來寫個東西抓這些圖檔離線來看, 以下的技巧當然不只可以用在這用途上…

抓取這類的非結構性資料的網站, 最直覺的方式就是把html抓回來解析, 不過這類型的網站, 為了保護資料, 所以很多東西都寫在javascript中, 這樣光解析靜態的html是不夠的, 還要去搞懂怎從他程式中抓到想要的資料是很累人的, 不過, 不管用了多少html, javascript, css的技巧, 最終還是得把整個頁面畫到browser才能讓使用者看到, 也就是如果在瀏覽器上, 到最後還是會有一個完整的DOM tree讓我們從某個節點取得我們想要的資料

不過, 這樣聽起來好像還是工程浩大, 還得綁個瀏覽器? 其實不用, 只要靠 phantom.js 就好

PhantomJS is a headless WebKit scriptable with a JavaScript API. It has fast and native support for various web standards: DOM handling, CSS selector, JSON, Canvas, and SVG.

反正呢….就是…這個可以拿來做這用途, 把它當一個scriptable沒畫面的Browser就對了

接下來我們拿"看漫畫"(http://tw.seemh.com/)這網站來當個範例

以"夜王"這部漫畫(http://tw.seemh.com/comic/6563/)來說, 從url可以猜到6563是他的ID, 而進入這個link之中, 有一堆單行本的列表, 所以第一個打算當然是用程式取的這些列表跟他的url囉, 所以打開Chrome的開發人員工具

image

從這上面可以看到, 這些連結有個共通點, 就是它們的class都是"status0", 為了證實這一想法, 切到console用"document.querySelectorAll(’.status0’)“來測試一下:

image

Bingo!果然如此, 因此這就適合我們拿phantom.js來抓取了url, 因此我們可以採用以下的code (書名的class是book-title)

function getComics() {
   var url = 'http://tw.seemh.com/comic/6563/';
   page.open(url, function (status) {
       var book_title = page.evaluate(function() {
           var title = document.querySelector('.book-title');
           return title.innerText;
       });
       console.log(book_title);
       var elements = page.evaluate(function() {
           var elems = document.querySelectorAll('a.status0');
           var titles = [];
           for(var i=0;i<elems.length;i++) {
               titles.push({title:elems[i].title, url:elems[i].href});
           }
           return titles;
       });

       elements.forEach(function(arg, i) {
         console.log(arg.title);
       });
       console.log('end');
       phantom.exit();
   });
};

Phantom.js可以直接用page.evaluate()對網頁執行javascript, 因此我們可以在這之內用document.querySelector來取得我們所需要的節點

接下來進入每本單行版的頁面, 如 http://tw.seemh.com/comic/6563/75102.html , 這邊我們有興趣的是, 到底有幾頁, 以及, 圖片到底在哪?

所幸這也很簡單

image

頁面的資訊就在上方那條, 有個下拉選擇的部份, 用同樣的方法我們可知, 它的id是"pageSelect”, 因此, 我們可以以下面的方式取的頁面數量:

nPages = page.evaluate(function() {
    return document.getElementById('pageSelect').length;
});

而圖片的id則是"mangaFile", 透過同樣的方式也可以取得檔名

雖然在phantom.js可以用page.render來直接存圖檔, 但缺點是不能指定檔名跟存放位置, 所以我這邊的作法是直接輸出成shell script, 使用curl來做圖形抓取的動作

完整的範例在: https://github.com/julianshen/comic_grabber (沒完全測試, 可能有bug)

補充一點, phantom.js有個bug, 在evaluate裡的array如果傳到外面來用的話, 使用pop(), shift()會出問題, 這也就是範例中, 要先把array給變成JSON string再轉回去的原因

之前一直沒在淘寶買過東西, 所以也沒體驗過從對岸買個東西到台灣到底多快, 這個第一次呢, 就給了小米盒子 2

之所以突然想買小米盒子, 主要的原因是, 之前看影片, 都在電腦上射手影音播放器看, 很方便也不用煩惱字幕的問題, 但搬到了新家之後, 有大的電視後, 再用電腦的小螢幕看就有點過不去了

本來是用PS3透過DLNA看放在電腦上的影片, 電腦上跑得是Universal Media Server, 不得不說這是一個不錯的open source軟體, 可以轉碼也可以結合字幕, 但一個問題就是, 常常中文(而且是大部分的)字幕顯示有問題, 雖然後來發現是MEncoder的問題, 改用ffmpeg的話就正常了, 但字幕還是得自己先上網找好, 懶慣了, 實在很不想手動, 後來看到小米盒子看影片, 甚至透過DLNA看, 都有自動尋找字幕功能, 一看到這個就毫不猶豫的上淘寶下訂了


今天下午, 小狗狗, 喔…不是, 是快遞, 送來了一個包裹:

先來看看, 這包裹旅行了多少的時間:
其實也沒很久, 4/30下訂, 5/3就收到了, 還蠻有效率的, 而且運費也不算貴
包裝還蠻仔細的, 除了快遞郵包外, 裡面又一個紙箱包住原先的包裝, 存心讓人多拍一張開箱照的(誤), 裡面多了一張使用說明說, 這是店家附的, 並不是小米盒子原本的說明書, 由於我買的是所謂的強化版, 是店家改過的, 所以多了這一張, 還算貼心
這就是原本的包裝, 就很簡潔, 小米的風格, 這樣的設計除美觀外, 應該也降低不少印刷成本吧
打開盒子, 不意外的盒子就躺在裡面(繞口令呀?!), 也是黑黑的, 說實在的…不漂亮, 談不上啥設計感, 不過還蠻小的, 如果不漂亮, 做成電視棒或許更好, 我還有機會把它藏起來 XD
本體現身, 真的是沒啥, 黑黑的印個"mi", 倒是真的不大啦! (我是不是說過了?), 另外還有個遙控器, 店家還送了矽膠保護套, 這遙控器, 第一時間還是讓我想起Apple的遙控器, 像的亂七八糟的, 只是Apple的銀色黑色設計還是比較好看, 不過也不能這樣比啦, Apple那隻遙控器就差不多快這一台一半價錢了
開箱之後當然要裝上電視實測囉, 先開個影片看看:
對呀, 到底誰這麼狠心? 我也想知道…喔, 不, 離題了….第一印象, 其實不太好, 一開始播影片時斷斷續續的, 並不是很流暢, 我接的還是有線網路
官方版的, 看起來有些影片並不能在台灣播, 還有不少是得付費的, 老實說, 看到一開始那品質我不會想付費, 雖然把環繞音設定打開, 但在我家的音響上感覺不出多聲道, MOD的音質都比它好
由於是所謂的"強化版", 所以除了原先的小米盒子桌面外另外還有裝了米睿桌面
從這邊就有比較多…嗯…就…那個…影片.. (喂, 想到哪去啦?是X劇那類….)
由於這些影片, 來自於各大知名網站(像是愛奇藝), 所以片源好像蠻充足的, 但實際上看起來的品質…就別期待了, 影片目前看到也都只有兩聲道, 我還是透過DLNA看我電腦上的比較實在
DLNA, 一如預期, 自動會幫我配對字幕, 字幕當然是由射手網下來的, 這的確是我要的功能, 5.1聲道音效也還在, 已經達標了….但玩了一下, 好像不只這麼簡單, 居然還可以開啟3D, 這感覺好像很不得了, 趕緊打開戴上3D眼鏡看…..看了一分鐘後….呃…還是關掉好了…這啥鬼….
東西還真只要做到一項打中使用者想用的, 就會有人買單, 看來小米成功的其中一個因素就是這個吧…對我來說, 字幕這功能真的實用呀….