Akita (A Kit of Android) v1.2 User Manual

Copyright (c) 1999-2101 Alibaba.com

开发者:杨喆 zhe.yangz@alibaba-inc.com

1       概述 Introduction    3

1.1     AkitaApp的关系      3

1.2     akita 1.0特性一览      3

1.2.1      定义即实现的API Framework   3

1.2.2      支持RestfulWeb服务调用      5

1.2.3      支持灵活的URL及参数样式      5

1.2.4      反序列化可灵活自定义      6

1.2.5      简单的网络访问封装      7

1.2.6      统一的异常处理框架      7

1.2.7      通用Cache框架      8

1.2.8      不断积累的UI组件库      9

1.2.9      丰富的工具类      9

2       快速入门 Quick Start   9

2.1      环境依赖      9

2.2     Hello Akita 例子      10

2.2.1      开发环境      10

2.2.2      创建项目      10

2.2.3      添加依赖包      10

2.2.4      实现一个测试API接口      11

2.3      其他      12

3       网络IO    13

4       异常处理      13

5       远程API 框架      14

6       通用Cache框架      14

6.1      内存Cache组件      14

6.2      简单Cache组件      15

6.3      文件系统Cache组件      16

7     Widget组件库      17

7.1      缩放拖动图片组件 PinchZoomImageView    17

7.2      远程图片组件 RemoteImageView    17

7.3      拖动指示器组件 PageIndicator   18

7.4      页面拖动组件 ViewPager   19

8     UI组件库      21

8.1      异步调用      21

8.1.1      简单异步调用      21

8.2      适配器      22

8.2.1     AkBaseAdapter<T>   22

8.2.2     AkPagerAdapter<T>   23

8.3      对话框      24

8.3.1     SimpleLoadingDialog   24

8.4      对话框      错误! 未定义书签。

9       工具类库      25

9.1     AndroidUtil  25

9.2     DateUtil  25

10      单元测试支持      26

10.1     @AkTest  26

 

 


 

1       概述 Introduction

akita是一套Android快速开发框架及组件库,建立在Android 官方SDK基础之上,涵盖了网络IOAPI调用动态代理注解、Json自动解析、远程图片组件、异步调用组件、通用Cache组件等方面的有效实现,是多年Android App开发所积累下来的有效代码的重构组织。

使用akita库开发Android App,可以让你用更少的代码、更短的时间完成相同的功能和界面,并且稳定、高效、可读性良好。

1.1     AkitaApp的关系

akita被设计为一个通用的快速开发库,用来支持80%以上的Android App的开发,主要分为NetworkIOAPI FrameworkCacheUtilWidgtAsyncUITemplate等模块。

1-1 一般Android App架构图

AliExpress Android App为例,一般的Android App架构如图1-1所示。

1.2     akita 1.0特性一览

1.2.1    定义即实现的API Framework

通过动态代理和注解等Java高级特性,支持“定义即实现”的服务侧接口调用方式。

比如要实现一个Taobao的获取商品详情的API接口:

http://gw.api.taobao.com/router/rest?sign=F05F6769F2F4C22D679C70CE054D7798&timestamp=2012-05-14+17%3A01%3A05&v=2.0&app_key=12129701&method=taobao.item.get&partner_id=top-apitools&format=json&num_iid=15824668129&fields=detail_url,num_iid,title,nick,type,cid,seller_cids,desc

Json返回如下:

通常在Android上实现这样一个接口,需要调用网络访问代码,判断返回状态,获取返回的Response,定义一些Parser,手写很多json解析代码,然后赋值到新创建的pojo对象中。在这个过程中的很多代码都是重复冗余的。

而现在,akita通过提供的统一API调用框架,减少了冗余代码,最大程度简化实现过程。使用akita进行调用,只需定义如下代码:

public interface TopApi {

    @AkGET

    @AkAPI(url="http://gw.api.taobao.com/router/rest")

    ItemGetResult taobao_item_get(

            @AkParam("sign") String sign,

            @AkParam("timestamp") String timestamp,

            @AkParam("v") String v,

            @AkParam("app_key") int app_key,

            @AkParam("method") String method,

            @AkParam("partner_id") String partner_id,

            @AkParam("format") String format,

            @AkParam("num_iid") long num_iid,

            @AkParam("fields") String fields

    ) throws AkInvokeException, AkServerStatusException;

}

定义pojo对象,或者可以考虑建立统一的业务模型:

public class ItemGetResult {

    public ItemGetResponse item_get_response;

    public static class ItemGetResponse {

        public Item item;

        public static class Item {

            public int cid;

            public String desc;

            public String detail_url;

            public String nick;

            public long num_iid;

            public String seller_cids;

            public String title;

            public String type;

        }

    }

}

然后,就可以在业务逻辑层随时调用该API了。含异常处理的调用API代码如下:

        TopApi topApi = ProxyFactory.getProxy(TopApi.class);

        try {

            ItemGetResult result = topApi.taobao_item_get(

                    "F05F6769F2F4C22D679C70CE054D7798", "2012-05-14+17%3A01%3A05",

                    "2.0", 12129701, "taobao.item.get", "top-apitools", "json",

                    15824668129L, "detail_url,num_iid,title,nick,type,cid,seller_cids,desc");

            // ...

        } catch (AkInvokeException e) {

            e.printStackTrace();  //defaults

        } catch (AkServerStatusException e) {

            e.printStackTrace();  //defaults

        }

“定义即实现”,通过以上的例子,可以看到,只需定义API方法和参数名称、业务模型等类和方法,就能通过ProxyFactory自动实现该API调用类。

1.2.2    支持RestfulWeb服务调用

支持POSTGETPUTDELETE等请求方式,使用@AkGET@AkPOST等注解,标识了特定API使用特定请求方式。

1.2.3    支持灵活的URL及参数样式

一般的URL样式为:

http://server.com/apiname1?param1=value1&param2=value2

对应的接口定义代码:

public interface TestApi {

    @AkGET

    @AkAPI(url="http://server.com/apiname1")

    String method1(

            @AkParam("param1") String param1,

            @AkParam("param2") String param2

    ) throws AkInvokeException, AkServerStatusException;

}

传参还可以另外一种形式简便定义:

public interface TestApi {

    @AkGET

    @AkAPI(url="http://server.com/apiname1")

    String method1(

            @AkParam("$paramMap") Map<String, String> paramMap,

    ) throws AkInvokeException, AkServerStatusException;

}

在使用时,对paramMap传入对应参数后即可调用。

对于文件上传可以加入如下参数:

@AkParam("$filesToSend") Map<String, File> filesToSend,

另外,对于特殊的URL样式(参数为url中拼接)如:

http://server.com/getItem/{itemId}.json

// 例如活动itemid110023item内容

http://server.com/getItem/110023.json

akita支持定义接口如下:

public interface TestApi {

    @AkGET

    @AkAPI(url="http://server.com/getItem/{itemId}.json")

    String method1(

            @AkParam("itemId") String itemId,

    ) throws AkInvokeException, AkServerStatusException;

}

1.2.4    反序列化可灵活自定义

对于返回的json格式中有固定报头及调用状态等信息的,如下面的json返回格式:

业务内容会在body中出现,而返回格式中始终会有head部分,存放本次调用的成功与否的代码code和对于消息体message

那么,可以使用如下代码定义该接口:

// 使用OceanParam2Result<T>作为返回值

public interface TestApi {

    @AkGET

    @AkAPI(url="http://server.com/getTouch)

    OceanParam2Result<GetTouchResult> method1(

            @AkParam("itemId") String itemId,

    ) throws AkInvokeException, AkServerStatusException;

}

// 定义对应于业务内容的业务模型

public static class GetTouchResult {

    public String sessionId;

    public String country;

}

其中,OceanParam2Result<T>可以预先一次性定义如下:

public class OceanParam2Result<T> {

    private static final String TAG = "OceanParam2Result<T>";

    public Head head;

    public static class Head {

        public String message;

        public String code;

    }

    public JsonNode body;

    @SuppressWarnings("unchecked")

    public T getBody(Class<T> entityClass) throws AkInvokeException{

        if(body != null && body.size()>0){

           

            try {

                T t = JsonMapper.node2pojo(body, entityClass);

                try {

                    Method method = entityClass.getDeclaredMethod("fulFill");

                    method.setAccessible(true);

                    t = (T) method.invoke(t);

                } catch (SecurityException e) {

                    Log.v(TAG, "Security in fulFill");

                } catch (NoSuchMethodException e) {

                    Log.v(TAG, "NoSuchMethod of fulFill.");

                }

                return t;

            } catch (JsonProcessingException e) {

                Log.e(TAG, body.toString());  // log can print the error return-string

                throw new AkInvokeException(AkInvokeException.CODE_JSONPROCESS_EXCEPTION,

                        e.getMessage(), e);

            } catch (IOException e) {

                throw new AkInvokeException(AkInvokeException.CODE_IO_EXCEPTION,

                        e.getMessage(), e);

            } catch (InvocationTargetException ite) {

                throw new AkInvokeException(AkInvokeException.CODE_FULFILL_INVOKE_EXCEPTION,

                        ite.getMessage(), ite);

            } catch (IllegalAccessException e) {

                throw new AkInvokeException(AkInvokeException.CODE_FULFILL_INVOKE_EXCEPTION,

                        e.getMessage(), e);

            }

        }

        return null;

    }

}

这样,每次调用如下代码就可以得到业务内容结果:

GetTouchResult result = oceanParam2Result.getBody(GetTouchResult.class);

同时,对于head的读取和检查也可以通过oceanParam2Result的调用实现:

assertTrue(“200”.oceanParam2Result.head.code);

assertNotNull(oceanParam2Result.head.message);

1.2.5    简单的网络访问封装

HttpInvoker中封装了基于HttpClient的网络调用,并且支持图片上传。HttpInvoker中的调用如下:

public static String get(String url);

public static String post(String url, ArrayList<NameValuePair> params);

public static String put(String url, HashMap<String, String> map);

public static String delete(String url);

public static String postWithFilesUsingURLConnection(

            String actionUrl, ArrayList<NameValuePair> params, Map<String, File> files);

public static Bitmap getBitmapFromUrl(String imgUrl, int inSampleSize);

public static Bitmap getImageFromUrl(String imgUrl, int inSampleSize);

使用线程安全的HttpClient支持多线程调用,支持传输数据gzip自动解压缩,减少网络流量的耗费。

1.2.6    统一的异常处理框架

akita中定义的异常有:AkServerStatusException, AkInvokeException

以上异常都继承于AkException,其中Ak开头的为Akita库定义,在开发上层SDK或应用时可以基于AkException定义上层异常,比如可以以Ae开头,定义AE SDK中使用的异常。

n akita异常说明

AkServerStatusExceptionAkita实现的APIHTTP(S)访问时得到的返回状态错误,会引起此异常。

AkInvokeExceptionAkita实现的API调用,除了引起HTTP返回状态异常外的其他情况。如网络不通、超时等。

n 上层自定义异常举例

AeResultException:处理AE SDK中的业务Api调用返回的业务层面的状态,即ws-mobile接口返回的Headcodemessage

AeNeedLoginException:需要登录后使用的Api,在未登录时,抛出此异常。

n 异常处理框架

ExceptionHandlerExecutorAkita库中实现的统一异常处理执行器。

AeExceptionHandler:具体处理的策略可以自定义实现。AeExceptionHandler继承自Akita库中BasicExceptionHandlerBasicExceptionHandler实现ExceptionHandler接口,接口定义了public void handle(AkException e)方法。

n 使用实例

一般在应用或SDK中调用API方法的默认异常处理例子:

        try {

            // Api 调用 code here.

        } catch (AkException e) {

            ExceptionHandlerExecutor.execute(new AeExceptionHandler(mContext), e);

        }

其中AeExceptionHandler继承于akita中的BasicExceptionHandler,一种默认实现如下:

public class AeExceptionHandler extends BasicExceptionHandler {

    private static final String TAG = "AeExceptionHandler";

    public AeExceptionHandler(Context c) { super(c); }

    @Override

    public void handle(AkException e) { Log.e(TAG, e.toString, e); }

}

1.2.7    通用Cache框架

1-2 Cache框架

目前akita中对于Cache分为三种类型:MemCacheFilesCacheSimpleCache,开发者可以根据实际需求选择使用。

MemCache:特点是存储在内存中,KV都可以存复杂对象。目前有使用LruCache(only Android 3+),和使用软引用SoftReference实现。

FilesCache:特点是持久存储在文件()结构中,K固定为StringV可以存复杂对象(需定义序列化过程)。目前有实现在SDCache空间中,以文件夹结构方式。

SimpleCache:特点是简单存储,KV都只能为String。可以持久或临时性存储。目前以Sqlite存储实现。

App上的绝大部分Cache需求可以通过以上三种Cache实现,但根据不同需求可能需要再增加合适的Impl实现。

akita中使用akCacheManager来统一管理、获取各种类型Cache

1.2.8    不断积累的UI组件库

远程图片组件、简单异步调用组件、积累的适配器、对话框等组件。

1.2.9    丰富的工具类

1-3 丰富并不断积累的工具类

2       快速入门 Quick Start

本章包含akita库基本的使用步骤,方便你快速入门。

2.1     环境依赖

n 依赖包

Jackson解析库:akita1.0依赖Jackson 1.9.0coremapper包。Jackson libs已经在默认的akita包中包含,但是为了节省空间已经你本来就引入了Jackson包的情况下,可以使用剥离Jackson libsakita包。

2-1 依赖的Jackson版本

Android SDK:在Android 4.0 SDK上测试稳定,akita库设计支持2.2+sdk,但akita 1.0版本由于时间的关系,只验证过在Android 4.0 SDK上的稳定和正确性。

2.2     Hello Akita 例子

2.2.1    开发环境

Eclipse + ADT

或者

IDEA IntelliJ (with Android support) 推荐

2.2.2    创建项目

(以IDEA IntelliJ为例)

New Module => Create module from scratch => Android Module

2-2 Android Module

Next => Next => Finish.

2.2.3    添加依赖包

Project中选中刚刚新建项目的名称,

右键 => Open Module Settings (或按F4) => 进入Project Structure对话框,

然后如下图所示添加akita

 

2-3 添加akita

或者如果你直接导入了akita1.0项目的话,选择Module Dependency,然后添加akita 1.0项目作为依赖。

2.2.4    实现一个测试API接口

在项目自动创建的MainActivity中实现一个测试API接口,这里使用淘宝TOP平台的获取商品详情的接口taobao.item.get,代码如下:

public class MainActivity extends AbsBottomTabActivity {

 

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);    // default Templates

 

        TopApi topApi = ProxyFactory.getProxy(TopApi.class);

        try {

            ItemGetResult result = topApi.taobao_item_get(

                    "F05F6769F2F4C22D679C70CE054D7798", "2012-05-14+17%3A01%3A05",

                    "2.0", 12129701, "taobao.item.get", "top-apitools", "json",

                    15824668129L, "detail_url,num_iid,title,nick,type,cid,seller_cids,desc");

            Toast.makeText(this, result.item_get_response.item.desc,

                                                              Toast.LENGTH_LONG).show();

            // ...

        } catch (AkInvokeException e) {

            e.printStackTrace();  //defaults

        } catch (AkServerStatusException e) {

            e.printStackTrace();  //defaults

        }

    }

 

    public interface TopApi {

        @AkGET

        @AkAPI(url="http://gw.api.taobao.com/router/rest")

        ItemGetResult taobao_item_get(

                @AkParam("sign") String sign,

                @AkParam("timestamp") String timestamp,

                @AkParam("v") String v,

                @AkParam("app_key") int app_key,

                @AkParam("method") String method,

                @AkParam("partner_id") String partner_id,

                @AkParam("format") String format,

                @AkParam("num_iid") long num_iid,

                @AkParam("fields") String fields

        ) throws AkInvokeException, AkServerStatusException;

    }

}

以上,即已经实现了此API接口及它的调用。

2.3     其他

akita其他的功能特性将分别在以下章节详述。同时,可以下载akita-samples-1.0领略全部功能的演示。


 

 

3       网络IO

n 说明

一般情况下,这部分网络IO调用不会被开发者直接使用,它们已经自动集成进远程API框架中作为网络的基础组件。但是,在一些特殊的情况下,开发者可能必须直接使用。

那就以诸如以下方式直接使用:

String response = HttpInvoker.get(url);

n 网络接口使用

// http GET

public static String get(String url);

// http POST

public static String post(String url, ArrayList<NameValuePair> params);

 

// to be impl.

public static String put(String url, HashMap<String, String> map);

// to be impl.

public static String delete(String url);

 

// with Files

public static String postWithFilesUsingURLConnection(

String actionUrl, ArrayList<NameValuePair> params, Map<String, File> files);

 

// version 2: remoteimageview download impl, use byte[] to decode. Recomanded.

public static Bitmap getBitmapFromUrl(String imgUrl, int inSampleSize);

 

// version 1: remoteimageview download impl, use InputStream to decode.

public static Bitmap getImageFromUrl(String imgUrl, int inSampleSize);

 

4       异常处理

n 统一的异常处理

akita中定义的异常有:AkServerStatusException, AkInvokeException

以上异常都继承于AkException,其中Ak开头的为Akita库定义,在开发上层SDK或应用时可以基于AkException定义上层异常,比如可以以Ae开头,定义AE SDK中使用的异常。

n akita异常说明

AkServerStatusExceptionAkita实现的APIHTTP(S)访问时得到的返回状态错误,会引起此异常。

AkInvokeExceptionAkita实现的API调用,除了引起HTTP返回状态异常外的其他情况。如网络不通、超时等。

n 上层自定义异常举例

AeResultException:处理AE SDK中的业务Api调用返回的业务层面的状态,即ws-mobile接口返回的Headcodemessage

AeNeedLoginException:需要登录后使用的Api,在未登录时,抛出此异常。

n 异常处理框架

ExceptionHandlerExecutorAkita库中实现的统一异常处理执行器。

AeExceptionHandler:具体处理的策略可以自定义实现。AeExceptionHandler继承自Akita库中BasicExceptionHandlerBasicExceptionHandler实现ExceptionHandler接口,接口定义了public void handle(AkException e)方法。

n 使用实例

一般在应用或SDK中调用API方法的默认异常处理例子:

        try {

            // Api 调用 code here.

        } catch (AkException e) {

            ExceptionHandlerExecutor.execute(new AeExceptionHandler(mContext), e);

        }

其中AeExceptionHandler继承于akita中的BasicExceptionHandler,一种默认实现如下:

public class AeExceptionHandler extends BasicExceptionHandler {

    private static final String TAG = "AeExceptionHandler";

    public AeExceptionHandler(Context c) { super(c); }

    @Override

    public void handle(AkException e) { Log.e(TAG, e.toString, e); }

}

5       远程API 框架

1.2.1 - 1.2.4的描述。

 

6       通用Cache框架

6.1     内存Cache组件

n 组件位置

com.alibaba.akita.cache.MemCache<K, V>

n 接口定义

public interface MemCache<K, V> {

 

    public V get(K key);

    public V put(K key, V value);

    public V remove(K key);

 

    /**

     * Clear all kvs of this MemCache

     */

    public void clear();

 

    /**

     * Get a snapshot of cache

     */

    public Map<K, V> snapshot();

   

}

n 使用说明

目前有两种MemCache,分别可以用AkCacheManager的静态方法创建。

分别为LruMemCache(必须在API Level >= 12的设备上使用,因为使用的是基于android.util.LruCache的封装),及SoftRefMemCache(这个不常使用,目前使用在RemoteImageView的内存级别图片本地缓存的实现中)。

n 使用方法举例

// 创建一个20个元素大小的memCacheLRU算法更新

MemCache<String, String> memCache = AkCacheManager.newMemLruCache(20);

 

// 软引用memCache

MemCache<String, String> memCache = AkCacheManager.newMemSoftRefCache();

 

6.2     简单Cache组件

n 组件位置

com.alibaba.akita.cache.SimpleCache

n 接口定义

public interface SimpleCache {

 

    public String get(String key);

 

    /**

     * get Latest items, max to num

     * @param num

     * @return

     */

    public ArrayList<String> getLatest(int num);

    public String put(String key, String value);

    public String remove(String key);

    public void removeAll();

    /**

     * Close the db and sth. else.

     * @return

     */

    public void close();

}

n 使用说明

SimpleCache适合于kv对的持久性存储,采用SQLite作为底层数据库实现,提供上层kv调用接口。数据文件存储在系统区相关app对应的 private存储区的.db文件中。根据应用场景不同分为CacheAppData两种。

n 使用举例

// “defaulttable” 表中,数据文件为simplecache.db,默认保留24小时

SimpleCache simpleCache1 = AkCacheManager.getSimpleCache(context);

simpleCache1.close();

 

// tagName 表中, 数据文件为simplecache.db,默认保留24小时

SimpleCache simpleCache2 = AkCacheManager.getSimpleCache(context, tagName);

simpleCache2.close();

 

// tagName 表中, 数据文件为simplecache.db, 保留hours小时

SimpleCache simpleCache3 = AkCacheManager.getSimpleCache(context, tagName, hours);

simpleCache3.close();

 

// “defaulttable”表中,数据文件为appdata.db,默认保留365

SimpleCache appData1 = AkCacheManager.getAppData(context);

appData1.close();

 

// tagName 表中,数据文件为appdata.db,默认保留365

SimpleCache appData2 = AkCacaheManager.getAppData(context, tagName);

appData2.close();

6.3     文件系统Cache组件

n 组件位置

com.alibaba.akita.cache.FilesCache<V>

n 接口定义

public interface FilesCache<V> {

   

    public V get(String key);

 

    /**

     * @param key

     * @param value if value is null, no effect and return null

     * @return oldValue or null if has no oldValue

     */

    public V put(String key, V value);

    public V remove(String key);

 

    /**

     * evict cache data according to the cache size set.

     * @return

     */

    public void evict();

 

    /**

     * Clear all this cache data

     * Note: this method maybe Time-consuming, you may run it async

     */

    public void clearCache();

 

    /**

     * get current cache size in MB

     * @return

     */

    public double getCacheCurrentSizeMB();

 

    /**

     * set normal cache size limit in MB

     * @param cacheSizeInMB

     */

    public void setCacheSize(int cacheSizeInMB);

   

}

n 说明

Widget组件库中的RemoteImageView集成了本地文件系统Cache

注意:千万不要忘记在权限设置上添加以下行,否则此Cache只能用到内存部分。

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

n 使用举例

虽然这主要是给RemoteImageView提供的基础图片文件系统Cache功能,但也可以被外部使用。外部使用图片FilesCache时,通过以下方式获得。

FilesCache<Bitmap> imageCache = AkCacheManager.getImageFilesCache(context);

imageCache.setCacheSize(cacheSizeInMB);

// 执行evict操作,在大于mCacheSize时,会清空一部分Cache数据

imageCache.evict();

// 得到目前存储的文件系统cache大小

imageCache.getCacheCurrentSizeMB();

// 全部清空

imageCache.clearCache();

 

7       Widget组件库

7.1     缩放拖动图片组件 PinchZoomImageView

<com.alibaba.akita.widget.PinchZoomImageView

        android:layout_width=”match_parent”

        android:layout_height=”match_parent” />

支持图片的多次触摸方式缩放,xy平面移动等。使用方式类似ImageView

7.2     远程图片组件 RemoteImageView

<com.alibaba.akita.widget.RemoteImageView

        xmlns:android="http://schemas.android.com/apk/res/android"

        xmlns:akita="http://schemas.alibaba.com/apk/res/akita"

        android:layout_width="match_parent"

        android:layout_height="match_parent"

        android:scaleType="centerInside"

        akita:fadeIn="true"

        akita:pinchZoom="true" />

n 自定义参数设置

akita:fadeIn=”true|fasle” 启用图片淡入效果

akita:pinchZoom=”true|fasle” 启用图片组件支持缩放、拖动

akita:showProgress=”true|false” 启用载入图片时显示实际下载进度条

akita: imgBoxWidth=”100” 设定图片组件的限制盒宽度

akita: imgBoxHeight=”20” 设定图片组件的限制盒高度

7.3     拖动指示器组件 PageIndicator

用来标识ViewPager组件的当前页。

n 参考代码

界面定义:

    <android.support.v4.view.ViewPager

            android:id="@+id/vp_banner"

            android:layout_width="fill_parent"

            android:layout_height="100dp"

            />

    <com.alibaba.akita.widget.CirclePageIndicator

            android:id="@+id/cpi_indicator1"

            android:padding="3dp"

            android:layout_marginBottom="2dp"

            android:layout_height="wrap_content"

            android:layout_width="fill_parent"

            />

设置代码:

        mViewPager = (ViewPager) v.findViewById(R.id.vp_banner);

        mViewPager.setAdapter(new BannerPageAdapter());

        mIndicator = (CirclePageIndicator) v.findViewById(R.id.cpi_indicator1);

        mIndicator.setViewPager(mViewPager);

        mIndicator.setFillColor(getResources().getColor(R.color.Orange));

        mIndicator.setPageColor(getResources().getColor(R.color.Black));

ViewPager采用v4包中标准实现,一个简单内容适配器代码:

    protected class BannerPageAdapter extends PagerAdapter {

        @Override

        public Object instantiateItem(ViewGroup container, int position) {

            RemoteImageView iv = (RemoteImageView)

                    getActivity().getLayoutInflater().inflate(R.layout.iv_banner, null);

            iv.setImageUrl("http://i03.i.aliimg.com/images/cms/upload/wholesale/" +

                    "home_banner/2012/05/childrensday/banner/590x200.jpg");

            iv.loadImage();

            container.addView(iv, 0);

            return iv;

        }

        @Override

        public void destroyItem(ViewGroup container, int position, Object object) {

            container.removeView((RemoteImageView) object);

        }

        @Override

        public int getCount() {

            return 3;  //defaults

        }

        @Override

        public boolean isViewFromObject(View view, Object o) {

            return view == o;

        }

}

也可参见UI适配器章节中,8.2.2小结,封装的AkPagerAdapter例子。

或者参加下一节v4兼容包中的ViewPager用法介绍。

7.4     页面拖动组件 ViewPager

类似于Workspace的实现,是google v4兼容包中发布的标准实现组件。

n 使用方法参考

界面定义:

    <android.support.v4.view.ViewPager

            android:id="@+id/vp1"

            android:layout_width="fill_parent"

            android:layout_height="fill_parent "

            />

关于其中内容的填充,除了上节中提到的简单适配器实现,还可以用Fragment方式的适配器:

class TestFragmentAdapter extends FragmentPagerAdapter {

  protected static final String[] CONTENT = new String[] { "This", "Is", "A", "Test", };

  private int mCount = CONTENT.length;

 

  public TestFragmentAdapter(FragmentManager fm) {

      super(fm);

  }

 

  @Override

  public Fragment getItem(int position) {

      return TestFragment.newInstance(CONTENT[position % CONTENT.length]);

  }

 

  @Override

  public int getCount() {

      return mCount;

  }

 

  public void setCount(int count) {

      if (count > 0 && count <= 10) {

          mCount = count;

          notifyDataSetChanged();

      }

  }

}

其中TestFragmentnewInstance关键代码:

  public static TestFragment newInstance(String content) {

      TestFragment fragment = new TestFragment();

 

      StringBuilder builder = new StringBuilder();

      for (int i = 0; i < 20; i++) {

          builder.append(content).append(" ");

      }

      builder.deleteCharAt(builder.length() - 1);

      fragment.mContent = builder.toString();

     

      return fragment;

  }

n 注意

目前,ViewPagerScrollView可能会在触摸响应处理时产生冲突,如果ViewPager作为子View放置在ScrollView里边的话,可能在onFling等响应事件上产生失败。这时候,一种可能的解决方案是使用ScrollViewFixed代替ScrollViewScrollViewFixed对拦截的时机进行了处理,代码如下:

public class ScrollViewFixed extends ScrollView {

    private GestureDetector mGestureDetector;

    View.OnTouchListener mGestureListener;

 

    public ScrollViewFixed(Context context, AttributeSet attrs) {

        super(context, attrs);    //defaults

        mGestureDetector = new GestureDetector(new YScrollDetector());

    }

 

    @Override

    public boolean onInterceptTouchEvent(MotionEvent ev) {

        return super.onInterceptTouchEvent(ev) && mGestureDetector.onTouchEvent(ev);

    }

 

    // Return false if we're scrolling in the x direction

    class YScrollDetector extends GestureDetector.SimpleOnGestureListener {

        @Override

        public boolean onScroll(MotionEvent e1, MotionEvent e2,

                                    float distanceX, float distanceY) {

            if(Math.abs(distanceY) > Math.abs(distanceX)) {

                return true;

            }

            return false;

        }

    }

}

或者

public class ScrollViewFixed extends ScrollView {

    private GestureDetector mGestureDetector;

    View.OnTouchListener mGestureListener;

 

    public ScrollViewFixed(Context context, AttributeSet attrs) {

        super(context, attrs);    //defaults

        setFadingEdgeLength(0);

    }

 

    @Override

    public boolean onInterceptTouchEvent(MotionEvent ev) {

        return super.onInterceptTouchEvent(ev) && (ev.getY()>( 200 -this.getScrollY()));

}

}

8       UI组件库

8.1     异步调用

8.1.1    简单异步调用

n 组件信息

com.alibaba.akita.ui.async.SimpleAsyncTask<T> extends AsyncTask<Integer, Integer, T>

n 说明

SimpleAsyncTask是在AsyncTask基础上做的简化,方便开发者更快更简洁的使用异步框架。会用AsyncTask的人对SimpleAsyncTask相信无需多做说明了,本来在以前我们开发的app中使用了AsyncTask的函数调用形式,后来经过比较和权衡,渐渐不使用函数形式而用SimpleAsyncTask来代替。

SimpleAsycTask将原本的execute方法封装成为了fire(),这个方法很形象的说明了SimpleAsyncTask的用处,就是new出来,然后发射,然后不去管了。

Android API LEVEL >= 11的情况下,AsyncTaskexecute()默认是单线程执行的,使用SerialExecutor。要使其在线程池中执行,需要指定THREAD_POOL_EXECUTOR。对此,我们对应封装了fireOnParallel()

n 举例

Detail页面加载过程

new SimpleAsyncTask<ProductDetail>() {                                                           

    // UI线程中执行,并且在Worker线程开始之前                                                                                              

    @Override                                                                                    

    protected void onUIBefore() throws AkException {                                              

        getView().findViewById(R.id.ll_loading).setVisibility(View.VISIBLE);                     

    }                                                                                            

   

    // 在开启的Worker线程中执行                                                                                             

    @Override                                                                                    

    protected ProductDetail onDoAsync() throws AkException {                                      

        return AEApp.getApp().aeClient.productGetWholeProductDetail(productId);                  

    }                                                                                            

   

// Worker线程执行完之后,在UI线程中执行,入参是Worker中返回的。

// 注意:如果onDoAsync及之前的方法异常中断走入AkException处理流程,此方法将不执行。将跳入onHandleAkException方法中。                                                                                             

    @Override                                                                                     

    protected void onUIAfter(ProductDetail productDetail) throws AkException {                   

        if (productDetail != null) {                                                             

            detailFragmentSupport.setProductDetail(productDetail);                               

            doRefreshData();                                                                     

        }                                                                                         

    }                                                                                            

   

    // 遇到异常后会跳入此方法                                                                                             

    @Override                                                                                     

    protected void onHandleAkException(AkException mAkException) {                               

        try{                                                                                      

            ExceptionHandlerExecutor.execute(new AeExceptionHandler(getActivity()), mAkException);

        } catch (Exception e) {e.printStackTrace();}                                             

        try {                                                                                     

            getView().findViewById(R.id.ll_fail).setVisibility(View.VISIBLE);                    

        } catch (NullPointerException npe) {}                                                     

    }                                                                                            

   

    // 此方法在UI线程中执行,肯定会被执行,且在其他方法执行后。                                                                                             

    @Override                                                                                    

    protected void onUITaskEnd() {                                                               

        try {                                                                                     

            getView().findViewById(R.id.ll_loading).setVisibility(View.GONE);                    

        } catch (NullPointerException npe) {}                                                    

    }                                                                                             

}.fire();                                                                                        

8.2     适配器

8.2.1    AkBaseAdapter<T>

n 举例

商品列表Adapter

public static class ProductListAdapter

            extends AkBaseAdapter<ProductSearchResult.ProductSummary> {

 

        public ProductListAdapter(Context c) {

            super(c);

        }

 

        @Override

        public View getView(int position, View convertView, ViewGroup parent) {

            ViewHolder viewHolder;

            if (convertView == null) {

                ViewGroup rl_productlist_productsummary = (ViewGroup) mInflater.inflate(

                        R.layout.griditem_productlist_productsummary, null);

                viewHolder = new ViewHolder();

                viewHolder.riv_productsummary_img = (RemoteImageView)

                        rl_productlist_productsummary.findViewById(R.id.riv_productsummary_img);

                viewHolder.riv_productsummary_img.setLocalImage(R.drawable.aliexpress);

                viewHolder.tv_productsummary_title = (TextView)

                        rl_productlist_productsummary.findViewById(R.id.tv_productsummary_title);

                viewHolder.tv_productsummary_price = (TextView)

                        rl_productlist_productsummary.findViewById(R.id.tv_productsummary_price);

                viewHolder.tv_productsummary_minorder = (TextView)

                        rl_productlist_productsummary.findViewById(R.id.tv_productsummary_minorder);

                viewHolder.tv_productsummary_sold = (TextView)

                        rl_productlist_productsummary.findViewById(R.id.tv_productsummary_soldnum);

                viewHolder.tv_productsummary_freeshipping = (TextView)

                        rl_productlist_productsummary.findViewById(R.id.tv_productsummary_freeshipping);

                convertView = rl_productlist_productsummary;

                convertView.setTag(viewHolder);

            } else {

                viewHolder = (ViewHolder) convertView.getTag();

            }

 

            ProductSearchResult.ProductSummary productSummary = mData.get(position);

            ProductUtil.ProductSummaryVO productSummaryVO = ProductUtil.getProductSummaryVO(productSummary);

            viewHolder.riv_productsummary_img.setImageUrl(productSummary.img_url);

            viewHolder.riv_productsummary_img.loadImage();

            viewHolder.tv_productsummary_title.setText(productSummary.subject);

            viewHolder.tv_productsummary_price.setText(Html.fromHtml(MessageFormat.format(

                    mContext.getString(R.string.productsummary_price),

                    productSummaryVO.price,

                    productSummaryVO.pieceUnit

            )));

            viewHolder.tv_productsummary_minorder.setText(MessageFormat.format(

                    mContext.getString(R.string.productsummary_minorder),

                    productSummaryVO.minOrder,

                    productSummaryVO.minOrderUnit

            ));

            viewHolder.tv_productsummary_sold.setText(MessageFormat.format(

                    mContext.getString(R.string.productsummary_sold),

                    productSummaryVO.sold

            ));

            if ("y".equals(productSummary.is_free_ship)) {

                viewHolder.tv_productsummary_freeshipping.setVisibility(View.VISIBLE);

            } else {

                viewHolder.tv_productsummary_freeshipping.setVisibility(View.GONE);

            }

 

            return convertView;  //defaults

        }

 

        static class ViewHolder {

            public RemoteImageView riv_productsummary_img;

            public TextView tv_productsummary_title;

            public TextView tv_productsummary_price;

            public TextView tv_productsummary_minorder;

            public TextView tv_productsummary_sold;

            public TextView tv_productsummary_freeshipping;

        }

    }

8.2.2    AkPagerAdapter<T>

封装了PagerAdapter及对象数组存储,配合作为ViewPager的简单适配器使用。

n 相关方法

public int getCount();

public void addItem(final T item);

public void addItem(int idx, final T item);

public void clearItems();

public T getItem(int position);

n 参考代码

适配器简单例程:

    protected class PicViewImgsAdapter extends AkPagerAdapter<PicViewItem> {

 

        public PicViewImgsAdapter(Context c) {

            super(c);

        }

 

        @Override

        public Object instantiateItem(ViewGroup container, int position) {

            RemoteImageView iv = (RemoteImageView)

                 mInflater.inflate(R.layout.iv_picview, null);

            iv.setImageUrl(mData.get(position).imgUrl);

            iv.loadImage();

            container.addView(iv, 0);

            return iv;

        }

 

        @Override

        public void destroyItem(ViewGroup container, int position, Object object) {

            container.removeView((RemoteImageView) object);

        }

 

        @Override

        public boolean isViewFromObject(View view, Object o) {

            return view == o;

        }

}

适配器设置代码:

ViewPager viewPager = (ViewPager) v.findViewById(R.id.vp_imgs);

PicViewImgsAdapter picViewImgsAdapter = new PicViewImgsAdapter(getActivity());

picViewImgsAdapter.addItem(new PicViewItem(url));

viewPager.setAdapter(picViewImgsAdapter);

8.3     对话框

8.3.1    SimpleLoadingDialog

简单Loading提示对话框

n 使用方法

SimpleLoadingDialog simpleLoadingDialog =

          SimpleLoadingDialog(context, loadingHintString);

simpleLoadingDialog.setHintProgress(hintProgressString);

simpleLoadingDialog.show();

simpleLoadingDialog.dismiss();

 

 

 

9       工具类库

9.1     AndroidUtil

n public static int dp2px(Context context, float dpValue);

dp to px像素转换

n public static int getVerCode(Context context);

得到VersionCode

n public static String getVerName(Context context);

得到VersionName

n public static String getAppName(Context context);

得到AppName

n public static String getDeviceId(Context context);

得到DeviceID,得不到返回null

n public static void hideIME(Activity context, boolean bHide);

隐藏IME软键盘

n public static void showIMEonDialog(AlertDialog dialog);

设置Dialog显示时跳出软键盘

n public static boolean isPkgInstalled(Context ctx, String packageName);

判断一个Package是否已经安装

n public static boolean isGooglePlayInstalled(Context ctx);

判断GooglePlay是否已经安装

n public static boolean networkStatusOK(final Context context);

网络是否可用,包含了Wifi2/3G

n public static void showNetworkFailureDlg(final Activity context);

弹出网络不可用对话框

9.2     DateUtil

n public static String getSimpleDatetime(long milliseconds);

简单的默认格式日期时间

n public static String getDefaultDatetime(long milliseconds);

"yyyy-MM-dd HH:mm:ss Z"

n public static String getTimeString(String strTime);

"20100315072741000-0700" =>  "yyyy-MM-dd HH:mm:ss Z"

10  单元测试支持

10.1 @AkTest

对于单元测试的支持,一个测试Case

n 示例代码

  @AkTest("You May Like it 推荐接口")

    public void testYouMayLikeIt() {

        try {

            ArrayList<String> pids = new ArrayList<String>();

            pids.add("550457925");

            pids.add("536849918");

            pids.add("505580543");

            YouMayAlsoLikeResult youMayAlsoLikeResult = aeClient.youMayAlsoLike(pids, 12);

            assertNotNull(youMayAlsoLikeResult);

            assertNotNull(youMayAlsoLikeResult.productlist.list2015);

            assertTrue(youMayAlsoLikeResult.productlist.list2015.size()==12);

        } catch (AkException e) {

            DefaultExceptionHandler.handle(e);

        }

    }

 

    @AkTest("[收货地址]列出、添加、编辑、删除(需该用户已经存在的地址不超过4)")

    public void testMailingAddressListMailingAddresses() {

        try {

            aeClient.memberLogin(TestData.emailUser, TestData.password);

            MailingAddressResult result = aeClient.mailingAddressListMailingAddresses();

            assertNotNull(result.addressList);

            result = aeClient.mailingAddressAddMailingAddress(

                    "3945 Freedom", "",

                    "US", "California", "Santa Clara",

                    "", "", "", "95054", "408", "1",

                    "43231551", "Pancras Chow", "95054"

            );

            assertNotNull(result);

            int newId = result.newMailingAddressId;

            result = aeClient.mailingAddressEditMailingAddress(

                    newId, "3945 Freedom modified.", "",

                    "US", "California", "Santa Clara",

                    "", "", "", "95054", "408", "1",

                    "43231551", "Pancras Chow", "95054"

            );

            assertNotNull(result);

            result = aeClient.mailingAddressDeleteMailingAddress(newId);

            assertNotNull(result);

        } catch (AkException e) {

            DefaultExceptionHandler.handle(e);

        }

    }

 

    @AkTest("[其他]得到Promotion具体的商品列表")

    public void testBasicPromotionCategoryProductAjax() {

        try {

            ArrayList<String> catIds = new ArrayList<String>();

            ArrayList<String> nums = new ArrayList<String>();

            catIds.add("4421");

            catIds.add("4431");

            catIds.add("4441");

            catIds.add("4451");

            catIds.add("4461");

            catIds.add("4471");

            nums.add("50");

            nums.add("50");

            nums.add("50");

            nums.add("50");

            nums.add("50");

            nums.add("50");

            PromotionCategoryProduct products =

                    aeClient.promotionCategoryProductAjax("100007809", catIds, nums);

            assertNotNull("[失败]products is null", products);

            assertNotNull("[失败]products' productlist is null", products.productlist);

        } catch (AkException e) {

            DefaultExceptionHandler.handle(e);

        }

    }