2013/05/30

小說閱讀器 v1.0.3

v 1.0.3
** 由於前一版閱讀紀錄有部份問題, 所以書籤中的書籍必須長按選擇"清除紀錄" **

* 新增"保持螢幕開啟"的設定
> 點選選單, 選擇設定

BUGS:
* 解決加入bookmark後, 啟動會 FC 的問題
* 修正卡提諾小說論壇顯示錯誤的問題
* 修正部份載入錯誤
* 修正搜尋後加入書籤會產生錯誤的問題

Google Play: 小說閱讀器 v1.0.3

2013/05/26

2013/05/25

Broken usb cable ??

使用 virtualbox, 突然 usb 不能使用, 然後插上去也不會亮燈.
解決方法如下 :
sudo rmmod ehci_hcd; sudo modprobe ehci-hcd

要小心, 最好這兩個動作同時做, 否則 rmmod 後, usb 鍵盤會不能使用了喔.

2013/05/23

小說閱讀器 v1.0.2

v 1.0.2
** 此版本由於修改了閱讀紀錄的設定, 所以升級後會清除原本的閱讀紀錄 **
** 造成不便請見諒 **

* 增加以下小說網站支援
> 金沙中文網

BUGS:
* 修正清除閱讀紀錄, 沒有清除閱讀頁數
* 修正部份導致 FC 的問題
* 修正在某些狀況讀取篇數錯誤的問題

Google Play: 小說閱讀器 v1.0.2

2013/05/19

ImageLoader with volley example

Google 2013/IO, 聽到了 Volley, 以下為測試實用一下.

環境:
SDK: android-17
IDE: eclipse
Device: Moto Atrix with 4.1.2
External Libraries: Volley
git clone --depth 1 https://android.googlesource.com/platform/frameworks/volley


Layout 為一個簡單的 GridView.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<GridView
android:id="@+id/gridview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawSelectorOnTop="true"
android:alwaysDrawnWithCache="false"
android:fadeScrollbars="true"
android:cacheColorHint="@android:color/transparent"
android:numColumns="3" 
android:columnWidth="160dp"/>

</RelativeLayout>

griview_row 就是一個很簡單的 imageview 而已
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<ImageView
android:id="@+id/imageview"
android:layout_width="160dp"
android:layout_height="160dp"
android:layout_centerInParent="true"
android:scaleType="center" 
android:contentDescription="@null"/>

</RelativeLayout>

實作過程中, 好像沒投影片那麼簡單, 投影片的 iamgeloader 使用 BitmapLruCache, 原本以為是包含在 volley 的 toolbox, 結果沒有, 必須自己去實作.
而本身 volley 則提供 DiskBaseCache, 簡單講就是暫存到外接記憶體.
Imageloader 的 cache, 必須實作 "ImageLoader.ImageCache" 這個 interface.
下面是實作部份, 此部份使用 picasso裡的 LruCache, 並且改成實作 volley 的 ImageCache.
public class BitmapLruCache implements ImageLoader.ImageCache {
final LinkedHashMap<String, Bitmap> map;
private final int maxSize;

private int size;

/**
* Create a cache using an appropriate portion of the available RAM as the maximum size.
*/
public BitmapLruCache(Context context) {
this(calculateMaxSize(context));
}

/**
* Create a cache with a given maximum size in bytes.
*/
public BitmapLruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("Max size must be positive.");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap<String, Bitmap>(0, 0.75f, true);
}

private Bitmap get(String key) {
if (key == null) {
throw new NullPointerException("key == null");
}

Bitmap mapValue;
synchronized (this) {
mapValue = map.get(key);
if (mapValue != null) {
return mapValue;
}
}

return null;
}

private void set(String key, Bitmap bitmap) {
if (key == null || bitmap == null) {
throw new NullPointerException("key == null || bitmap == null");
}

Bitmap previous;
synchronized (this) {
size += getBitmapBytes(bitmap);
previous = map.put(key, bitmap);
if (previous != null) {
size -= getBitmapBytes(previous);
}
}

trimToSize(maxSize);
}

private void trimToSize(int maxSize) {
while (true) {
String key;
Bitmap value;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(
getClass().getName() + ".sizeOf() is reporting inconsistent results!");
}

if (size <= maxSize || map.isEmpty()) {
break;
}

Map.Entry<String, Bitmap> toEvict = map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= getBitmapBytes(value);
}
}
}

/**
* Clear the cache.
*/
public final void clear() {
trimToSize(-1); // -1 will evict 0-sized elements
}

private static int calculateMaxSize(Context context) {
ActivityManager am = (ActivityManager) context.getSystemService(ACTIVITY_SERVICE);
boolean largeHeap = (context.getApplicationInfo().flags & FLAG_LARGE_HEAP) != 0;
int memoryClass = am.getMemoryClass();
if (largeHeap && Build.VERSION.SDK_INT <= Build.VERSION_CODES.HONEYCOMB) {
memoryClass = ActivityManagerHoneycomb.getLargeMemoryClass(am);
}
return 1024 * 1024 * memoryClass / 6;
}

@Override
public Bitmap getBitmap(String url) {
return get(url);
}

@Override
public void putBitmap(String url, Bitmap bitmap) {
set(url, bitmap);
}

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private static class ActivityManagerHoneycomb {
static int getLargeMemoryClass(ActivityManager activityManager) {
return activityManager.getLargeMemoryClass();
}
}


@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
private static class BitmapHoneycombMR1 {
static int getByteCount(Bitmap bitmap) {
return bitmap.getByteCount();
}
}

private static int getBitmapBytes(Bitmap bitmap) {
int result;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
result = BitmapHoneycombMR1.getByteCount(bitmap);
} else {
result = bitmap.getRowBytes() * bitmap.getHeight();
}
if (result < 0) {
throw new IllegalStateException("Negative size: " + bitmap);
}
return result;
}
}

基本上主要程式部份, 除了初始話 ImageLoader, 接著就是在 GridView 的 adapter 使用 ImageLoader 就可.
以下只列出部份.
此部份在初始化 ImageLoader, 還有 Volley 最主要的 Request Queue.
RequestQueue mRequestQ;
ImageLoader mImageLoader;
        
mRequestQ = Volley.newRequestQueue(getActivity());
mImageLoader = new ImageLoader(mRequestQ, new BitmapLruCache(getActivity()));

此部份是在取得 image resource.
holder.container = mImageLoader.get(item.url, new ImageLoader.ImageListener() {
@Override
public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) {
holder.iv.setImageBitmap(response.getBitmap());
}

@Override
public void onErrorResponse(VolleyError error) {
Toast.makeText(getContext(), error.getMessage(), Toast.LENGTH_SHORT).show();
}
}, 160,160);


執行結果如下 :



結論:
初步使用心得, 當 Imageloader 我個人認為就實作方面來說, 似乎還是有一些功夫需要作, 不過使用方面來說算是還滿簡單的.
另外值得一提的是, 對於某些會針對 Http-Header 來做某些行為的網站, 那使用 Volley 則必須要自己去實作 Request.


原始碼下載: VolleyExample.zip

XGallery v1.0.7

v 1.0.7
* Fix some FC problems.
* Change App Icon.

中文說明






v 1.0.7
* 修改部份 FC 問題
* 變更 APP Icon

2013/05/16

小說閱讀器 v1.0.0

小說閱讀器目前支援以下線上小說: (會陸續增加)

* Bestory 精品文學 [支援搜尋]
* 卡提諾 小說 [目前暫不支援搜尋]

主要有以下功能:
* 可加入書籤, 方便追蹤小說
* 可紀錄上次讀取位置, 下次會從上次閱讀位置開始
* 可切換顯示主題 (白底黑字 / 黑底白字)
* 支援線上搜尋
* 畫面點擊上半部可往前一頁, 點擊下半不可往後一頁
* 左右滑動可切換上下一篇
* 可切換是否顯示圖片 (開啟圖片顯示, 可能會影響紀錄的閱讀位置)

版本資訊:

v 1.0.0
* 初步功能
* 暫時支援兩個網站

Google Play: 小說閱讀器 v1.0.0


Box: v1.0.0

2013/05/13

蘋果日報 daily 新聞 v1.0.2

變更紀錄:
v 1.0.2
* 支援書籤功能
* 支援分享功能

Google Play: 蘋果日報 Daily 新聞

XGallery v1.0.6

v 1.0.6

* add Https Support
* Fix thumb loading problem.
* Fix duplicated entry bug when relogin.
* Fix progress indicator not dismiss problem.


中文說明







v 1.0.6

* 增加 Https 支援
* 修正縮圖某些狀況無法顯示問題
* 修正當重新login, 會出現相同的 entry 問題
* 修正進度視窗不會消失的問題

2013/05/10

2013/05/09

論壇瀏覽器 v2.1.1

v 2.1.1 -

* 增加恢復字體初始值的功能(在設定裡面)

* 增加連結使用新視窗的功能(長按連結)

* 增加消息,提醒功能

**BUG:**

* 修正內文字體設定失效的問題


Google play store: https://play.google.com/store/apps/details?id=tw.clotai.forumreader

2013/05/08

蘋果 daily 新聞 v1.0.0

蘋果 Daily 新聞為非官方的 App, 方便可以在平板或手機上迅速瀏覽閱讀蘋果日報.

* 此軟體須依靠第三方播放軟體來播放動新聞. 推薦使用 MXPlayer
* 點擊圖片可放大縮小
* 拖曳到最底會自動載入下一頁內容
* 側邊選單方便可跳至任一類別

有任何問題請來信 weakapp@gmail.com

變更紀錄:
v 1.0.0
* 支援蘋果日報瀏覽
* 支援動新聞播放 (須第三方軟體)

Google Play: 蘋果日報 Daily 新聞


Box: v1.0.0

2013/05/02

如何在 2.3.x android OS 呼叫 java 函式 ?

之前提到 WebView 與 javascript 使用,
但卻不適用於 android 2.3.x, 因為會有問題. (App 直接關閉)

下面是我所使用的方式, 讓我們可以在 2.3.x 去調用 java 函式.

稍微修改了一下所使用的 html, 修改 showMsg 所使用的方式
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Example</title>
<meta name="viewport" content="user-scalable=yes,target-densityDpi=device-dpi,width=device-width" />
<script type='text/javascript'>
function showMsg() {
prompt("This is prompt");
}
</script>
</head>
<body>
<input type='button' value='Click Me' onclick='showMsg();' />
</body>
</html>

執行結果如下圖:


透過這個我們可以利用這個方式來非直接的調用 java 函式,
特別注意的是第 10 行, 如果不 cancel, 畫面就會因為這個 prompt 而無法操作喔.
 WebChromeClient mWebChromeClient = new WebChromeClient() {

  @Override
  public boolean onJsPrompt(WebView view, String url, String message,
    String defaultValue, JsPromptResult result) {

   if ((defaultValue != null) && (defaultValue.length() != 0)) {
    if (defaultValue.equalsIgnoreCase("jstesting")) {
     Toast.makeText(MainActivity.this, R.string.msg_js_call_toast, Toast.LENGTH_SHORT).show();
     result.cancel();
     return true;
    }
   }
   
   return super.onJsPrompt(view, url, message, defaultValue, result);
  }
  
 };

再次修改一下 html 檔.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Example</title>
<meta name="viewport" content="user-scalable=yes,target-densityDpi=device-dpi,width=device-width" />
<script type='text/javascript'>
function showMsg() {
prompt("This is prompt", "jstesting");
}
</script>
</head>
<body>
<input type='button' value='Click Me' onclick='showMsg();' />
</body>
</html>

這樣就可以調用 java 函式了.

如果覺得上面方式有點怪異或麻煩, 也可以使用參考資料 [1] 的方式.
不過對我而言以上方式就是最簡單的方式了.

範例原始碼: WebViewExample4_1

參考資料:
[1]. http://fred-zone.blogspot.tw/2011/12/android-23x-javascript-interface.html

WebView 與 javascript 使用


之前講過怎麼簡單的使用 webview,
這邊也稍微紀錄一下如何在 webview 與 javascript 的使用.

預設 webview 是不能使用 javascript 的, 所以在初始時必須開啟 javascript,
除此之外, 還必須設定 web chrome client, 否則在我的範例則會無法達到效果.
mWebView.setWebChromeClient(new WebChromeClient());
mWebView.getSettings().setJavaScriptEnabled(true);

我所使用的 html 檔案, 點擊按鈕會跳出一個訊息視窗
<!DOCTYPE html>
<html>
    <head>
     <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
     <title>Example</title>
        <meta name="viewport" content="user-scalable=yes,target-densityDpi=device-dpi,width=device-width" />
  <script type='text/javascript'> 
   function showMsg() {
    alert('This is alert dialog');
   }
  </script>
    </head>
 <body>
  <input type='button' value='Click Me' onclick='showMsg();'/>
 </body>
</html>

效果如下:


在 Webview 內使用 javascript 就是這麼簡單.

OK ! 接著如果要從 android 呼叫 html 裡的 javascript 呢?
只要使用以下方式就可以呼叫 html 裡的 javscript.
mWebView.loadUrl("javasript:showMsg();");
效果跟上圖一樣, 也是會跳出一個訊息視窗.

反過來, 可以從 html 呼叫 android 的 java 函式嗎 ?
可以, 一樣是透過 javascript 的方式!