image

在今年Google IO時, Google宣布了m 未來Chrome OS上可以跑Android的應用程式, 可能有人對Chrome OS不以為意, 覺得既然有了Android何必又有一個Chrome OS, 對於沒用過Chrome OS的人可能會有這樣的感覺, 不過我必須說, 兩者設計的導向是不同的, Chrome OS速度快, 輕便, 這也是Android所比不上的, Google這一舉動, 讓Chrome OS又可以補足功能性不足的部分, 雖然不會全部的Android 應用程式都可以適用, 不過這已經足夠開啟另一個可能性了

目前開放放在Chrome App store的還不多, 只有像是Vine, Evernote這些少數Google已經測試過的, 如可以在這邊找到Evernote: https://chrome.google.com/webstore/detail/evernote/dhfolfjkgpeaojbiicgheljefkfbbfkc

但這些以外的呢?除了等Google大神以外, 還有其他管道嗎?所幸還有其他大神放出相關的工具讓你包裝, 還是Open source:

https://github.com/vladikoff/chromeos-apk

使用這工具你首先必須要有安裝node.js, 之後用npm安裝chromeos-apk這個module即可: “npm i chromeos-apk -g”

使用方法也很簡單, 只要:

chromeos-apk xxxx.apk

這樣即可, 就會產生一個Chrome extension的目錄, 然後進到Chome, 用"chrome://extensions"進入安裝extension的畫面, 載入unpacked extension即可

不過, 我在安裝時碰到一個問題, 出現了一個訊息: 

There is no “message” element for key extName

這問題是由於產生的檔案中在_locale/en底下那個檔, extName的部分只有descriiption沒有message這屬性, 補上去就好, 內容可以是你的app name

著名的工具軟體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再轉回去的原因