├── .gitignore ├── README.md ├── build.gradle ├── docs ├── _decorators │ ├── basic.html │ └── post.html ├── _pages │ ├── filedownloader.md │ ├── imageloader.md │ ├── index.md │ ├── request.md │ ├── startup.md │ ├── understanding.md │ └── usecase.md ├── _raw │ └── css │ │ └── site.css └── netroid ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── vincestyling │ └── netroid │ ├── AuthFailureError.java │ ├── CacheDispatcher.java │ ├── ClientError.java │ ├── DefaultRetryPolicy.java │ ├── Delivery.java │ ├── ExecutorDelivery.java │ ├── HttpUtils.java │ ├── IListener.java │ ├── Listener.java │ ├── NetroidError.java │ ├── NetroidLog.java │ ├── Network.java │ ├── NetworkDispatcher.java │ ├── NetworkError.java │ ├── NetworkResponse.java │ ├── NoConnectionError.java │ ├── ParseError.java │ ├── Request.java │ ├── RequestPerformer.java │ ├── RequestQueue.java │ ├── Response.java │ ├── RetryPolicy.java │ ├── ServerError.java │ ├── TimeoutError.java │ ├── cache │ ├── BitmapImageCache.java │ ├── DiskCache.java │ └── LruCache.java │ ├── request │ ├── FileDownloadRequest.java │ ├── ImageRequest.java │ ├── JsonArrayRequest.java │ ├── JsonObjectRequest.java │ ├── JsonRequest.java │ └── StringRequest.java │ ├── stack │ ├── HttpClientStack.java │ ├── HttpStack.java │ └── HurlStack.java │ ├── toolbox │ ├── AndroidAuthenticator.java │ ├── Authenticator.java │ ├── BasicNetwork.java │ ├── ByteArrayPool.java │ ├── FileDownloader.java │ ├── ImageLoader.java │ └── PoolingByteArrayOutputStream.java │ └── widget │ └── NetworkImageView.java ├── sample ├── .gitignore ├── build.gradle ├── debug.keystore ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ └── cover_16539.jpg │ ├── java │ └── com │ │ └── vincestyling │ │ └── netroid │ │ └── sample │ │ ├── AppLog.java │ │ ├── BaseActivity.java │ │ ├── BaseListActivity.java │ │ ├── BatchImageRequestDiskActivity.java │ │ ├── BatchImageRequestMemActivity.java │ │ ├── BatchImageRequestMultCacheActivity.java │ │ ├── CommonHttpRequestActivity.java │ │ ├── FileDownloadActivity.java │ │ ├── GridViewActivity.java │ │ ├── ImageRequestActivity.java │ │ ├── MainActivity.java │ │ ├── OffWithMainThreadPerformingActivity.java │ │ ├── mock │ │ ├── Book.java │ │ └── BookDataMock.java │ │ └── netroid │ │ ├── Netroid.java │ │ ├── SelfImageLoader.java │ │ └── TransactionalRequest.java │ └── res │ ├── drawable-xhdpi │ ├── click_to_add_more.png │ └── default190x338.jpg │ ├── layout │ ├── activity_file_downloader.xml │ ├── activity_gridview_p0.xml │ ├── activity_image_request.xml │ ├── activity_main.xml │ ├── activity_off_with_main_thread.xml │ ├── common_http_request.xml │ ├── download_task_item.xml │ ├── grid_item.xml │ └── list_item.xml │ ├── mipmap │ └── ic_launcher.png │ └── values │ └── strings.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | #Android generated 2 | bin 3 | gen 4 | 5 | #Eclipse 6 | .project 7 | .classpath 8 | .settings 9 | 10 | #IntelliJ IDEA 11 | .idea 12 | *.iml 13 | *.ipr 14 | *.iws 15 | out 16 | 17 | #Maven 18 | target 19 | release.properties 20 | pom.xml.* 21 | 22 | #Ant 23 | build.xml 24 | local.properties 25 | proguard.cfg 26 | 27 | #OSX 28 | .DS_Store 29 | 30 | # Site Dir 31 | site 32 | 33 | # Gradle 34 | .gradle 35 | build 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Netroid library for Android 3 | =========================== 4 | 5 | Netroid is a http library for Android that based on [Volley](http://developer.android.com/training/volley/index.html), 6 | That purpose is make your android development easier than before, provide fast, handly, useful way to do async http operation by background thread. 7 | 8 | Feature 9 | ========= 10 | 11 | #### 1. Base async http interaction. 12 | 13 | As most android apps done, Netroid allow you to retrive data over http with background thread, exchange invoke result to main thread. 14 | 15 | #### 2. Response cache base disk. 16 | 17 | Netroid can cache your http response to disk and the cache expire time was configurable. 18 | 19 | #### 3. Image loading solution. 20 | 21 | Provide a powerful solution of image load, used LruImageCache as bitmap memory cache. 22 | 23 | #### 4. Big file download solution. 24 | 25 | Provide file download management, allows create, pause, continue, discard operation with download task, also download progress callback. 26 | 27 | principle 28 | ========= 29 | 30 | When Netroid startup, it create lots of thread that amount specify by developer, each thread will block with **Request Queue**, 31 | after a new request comes, whether thread will be awake then perform http operation, finally it back to block status if that perform is done. 32 | 33 | Basic usage 34 | =========== 35 | 36 | The main entry of Netroid is `RequestQueue`, we highly recommnd you init it with `Application` and always let it stand with singleton : 37 | 38 | ```java 39 | public class YourApplication extends Application { 40 | 41 | public void onCreate() { 42 | super.onCreate(); 43 | 44 | // you can choose HttpURLConnection or HttpClient to execute request. 45 | Network network = new BasicNetwork(new HurlStack(Const.USER_AGENT, null), HTTP.UTF_8); 46 | 47 | // you can specify parallel thread amount, here is 4. 48 | // also instance the DiskBaseCache by your settings. 49 | RequestQueue mQueue = new RequestQueue(network, 4, 50 | new DiskCache(new File(ctx.getCacheDir(), Const.HTTP_DISK_CACHE_DIR_NAME), Const.HTTP_DISK_CACHE_SIZE)); 51 | 52 | // start and waiting requests. 53 | mQueue.start(); 54 | } 55 | 56 | } 57 | ``` 58 | 59 | In anywhere, the only one you should do just take the `RequestQueue` instance, then simply add your request instance into RequestQueue : 60 | 61 | ```java 62 | StringRequest request = new StringRequest(url, new Listener() { 63 | ProgressDialog mPrgsDialog; 64 | 65 | @Override 66 | public void onPreExecute() { 67 | mPrgsDialog = ProgressDialog.show(Activity.this, null, "loading...", true, true); 68 | } 69 | 70 | // cancel the dialog with onFinish() callback 71 | @Override 72 | public void onFinish() { 73 | mPrgsDialog.cancel(); 74 | } 75 | 76 | @Override 77 | public void onSuccess(String response) { 78 | Toast.makeText(Activity.this, "response is : " + response, 2000).show(); 79 | } 80 | 81 | @Override 82 | public void onError(NetroidError error) { 83 | Toast.makeText(Activity.this, error.getMessage(), 2000).show(); 84 | } 85 | 86 | @Override 87 | public void onCancel() { 88 | Toast.makeText(Activity.this, "request was cancel", 2000).show(); 89 | } 90 | }); 91 | 92 | // add the request to RequestQueue, will execute quickly if has idle thread 93 | mQueue.add(request); 94 | ``` 95 | 96 | Do not forget add internet permission to the `AndroidManifest.xml` file : 97 | 98 | ```xml 99 | 100 | 101 | ``` 102 | 103 | ### ImageLoader 104 | 105 | Similarly, **ImageLoader** should be singleton and also init with Application : 106 | 107 | ```java 108 | public class YourApplication extends Application { 109 | 110 | public void onCreate() { 111 | super.onCreate(); 112 | 113 | RequestQueue mQueue = ...; 114 | 115 | // SelfImageLoader is your implementation that extends by ImageLoader. 116 | // you can create a memory cache policy within ImageLoader. 117 | ImageLoader mImageLoader = new SelfImageLoader( 118 | mQueue, new BitmapImageCache(Const.HTTP_MEMORY_CACHE_SIZE)); 119 | } 120 | 121 | // the method you'll perform when you want to fill a single ImageView with network image. 122 | public void displayImage(String url, ImageView imageView) { 123 | ImageLoader.ImageListener listener = ImageLoader.getImageListener(imageView, 0, 0); 124 | mImageLoader.get(url, listener, 0, 0); 125 | } 126 | 127 | // we bring you the NetworkImageView to load network images when it's inside of ListView or GridView. 128 | public void displayImage(String url, NetworkImageView imageView) { 129 | imageView.setImageUrl(url, mImageLoader); 130 | } 131 | 132 | } 133 | ``` 134 | 135 | ### FileDownloader 136 | 137 | As always singleton, **FileDownloader** would be the same : 138 | 139 | ```java 140 | public class YourApplication extends Application { 141 | 142 | public void onCreate() { 143 | super.onCreate(); 144 | 145 | RequestQueue mQueue = ...; 146 | 147 | // specify the parallel download task count, less than 3 is recommended. 148 | FileDownloader mFileDownloader = new FileDownloader(mQueue, 1); 149 | } 150 | 151 | // add a new download task, take the DownloadController instance. 152 | public FileDownloader.DownloadController addFileDownload(String storeFilePath, String url, Listener listener) { 153 | return mFileDownloader.add(storeFilePath, url, listener); 154 | } 155 | 156 | } 157 | ``` 158 | 159 | With **DownloadController**, you can get the task status such as waiting downloading, finish, also you can trun the task to 160 | pause, continue, discard status. the **Listener.onProgressChange()** will inform you the download progress. 161 | 162 | Integration 163 | =========== 164 | 165 | Download the [latest JAR](http://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=com.duowan.android.netroid&a=netroid&v=LATEST) 166 | or grab via **Maven** : 167 | 168 | ```xml 169 | 170 | com.duowan.android.netroid 171 | netroid 172 | (insert latest version) 173 | 174 | ``` 175 | 176 | For **Gradle** projects use : 177 | 178 | ```groovy 179 | compile 'com.duowan.android.netroid:netroid:(insert latest version)' 180 | ``` 181 | 182 | At this point latest version is `1.2.1`. 183 | 184 | Documentation 185 | =========== 186 | 187 | For more detail about what Netroid can do, pay attention to the [docs](http://netroid.cn/), that written by chinese. 188 | 189 | Sample Application 190 | ================== 191 | 192 | To be straightforward, we build the [sample apk](http://netroid.cn/attach/netroid-sample-1.2.1.apk), you can try all features without any source code. 193 | 194 | License 195 | ======= 196 | 197 | ```text 198 | Copyright (C) 2015 Vince Styling 199 | 200 | Licensed under the Apache License, Version 2.0 (the "License"); 201 | you may not use this file except in compliance with the License. 202 | You may obtain a copy of the License at 203 | 204 | http://www.apache.org/licenses/LICENSE-2.0 205 | 206 | Unless required by applicable law or agreed to in writing, software 207 | distributed under the License is distributed on an "AS IS" BASIS, 208 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 209 | See the License for the specific language governing permissions and 210 | limitations under the License. 211 | ``` 212 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:1.2.3' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } -------------------------------------------------------------------------------- /docs/_decorators/basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ${variable:title} 7 | 8 | 9 |
10 |
11 | ${decorator:content} 12 |
13 |
14 | 18 | 19 | -------------------------------------------------------------------------------- /docs/_decorators/post.html: -------------------------------------------------------------------------------- 1 | decorator: basic 2 | ‡‡‡‡‡‡‡‡‡‡‡‡‡‡ 3 |
4 |

${variable:title}

5 |

last modified : ${variable:modifytime}

6 |
7 |
8 |
9 | ${decorator:content} 10 |
11 |
-------------------------------------------------------------------------------- /docs/_pages/filedownloader.md: -------------------------------------------------------------------------------- 1 | title: Netroid FileDownloader 2 | decorator: post 3 | slug: filedownloader.html 4 | ‡‡‡‡‡‡‡‡‡‡‡‡‡‡ 5 | 6 | # 大文件下载 7 | 8 | Netroid实现的 `FileDownloader` 对断点续传方式的大文件下载提供了支持,其内部维护一个下载队列,所以在创建时需要指定最大并行任务数,超出限制的任务将自动进入等待队列。在设置最大并行任务数后,开发者只需要往队列中不断添加任务,其它的事情均由 **FileDownloader** 完成。 9 | 10 | FileDownloader将在任务添加成功时返回 `DownloadController` 实例对象,这个对象提供了查看任务执行状态、暂停、继续、取消四项必需的操作功能,开发者只需要持有这个对象,即可随时掌控任务的所有情况。 11 | 12 | FileDownloader的用法类似于 **ImageLoader**,用单例模式创建一个全局的实例,在初始化 **RequestQueue** 时构造: 13 | 14 | ```java 15 | int poolSize = RequestQueue.DEFAULT_NETWORK_THREAD_POOL_SIZE; // 默认为4 16 | RequestQueue mQueue = new RequestQueue(Network, poolSize); 17 | // 建议并行任务数上限不超过3,在手机带宽有限的条件下,并行任务数的扩大无法加快下载速度。 18 | // 注:如果并行任务数上限大于或等于RequestQueue中的总线程数,将被视为不合法而抛出异常。 19 | FileDownloader mDownloader = new FileDownloader(mQueue, 1); 20 | ``` 21 | 22 | 调用 **FileDownloader.add()** 方法即可创建新任务: 23 | 24 | ```java 25 | // down.file是保存的文件名,这个文件只在下载成功后才存在,在下载过程中, 26 | // Netroid会在文件路径下创建一个临时文件,命名为:down.file.tmp,下载成功后更名为down.file。 27 | FileDownloader.DownloadController controller = FileDownloader.add( 28 | "/sdcard/netroid/down.file", "http://server.com/res/down.file", 29 | new Listener() { 30 | // 注:如果暂停或放弃了该任务,onFinish()不会回调 31 | @Override 32 | public void onFinish() { 33 | Toast.makeText("下载完成").show(); 34 | } 35 | 36 | // 注:如果暂停或放弃了该任务,onSuccess()不会回调 37 | @Override 38 | public void onSuccess(Void response) { 39 | Toast.makeText("下载成功").show(); 40 | } 41 | 42 | // 注:如果暂停或放弃了该任务,onError()不会回调 43 | @Override 44 | public void onError(NetroidError error) { 45 | Toast.makeText("下载失败").show(); 46 | } 47 | 48 | // Listener添加了这个回调方法专门用于获取进度 49 | @Override 50 | public void onProgressChange(long fileSize, long downloadedSize) { 51 | // 注:downloadedSize 有可能大于 fileSize,具体原因见下面的描述 52 | Toast.makeText("下载进度:" + (downloadedSize * 1.0f / fileSize * 100) + "%").show(); 53 | } 54 | }); 55 | 56 | // 查看该任务的状态 57 | controller.getState(); 58 | // 任务的状态分别是: 59 | STATUS_WAITING: // 等待中 60 | STATUS_DOWNLOADING: // 下载中 61 | STATUS_PAUSE: // 已暂停 62 | STATUS_SUCCESS: // 已成功(标识下载已经正常完成并成功) 63 | STATUS_DISCARD: // 已取消(放弃) 64 | 65 | // 暂停该任务 66 | controller.pause(); 67 | 68 | // 继续该任务 69 | controller.resume(); 70 | 71 | // 放弃(删除)该任务 72 | controller.discard(); 73 | ``` 74 | 75 | ## 任务优先级: 76 | 77 | 任务的优先级由添加的先后顺序来确定,当某项任务执行结束或暂停时,**FileDownloader** 将从头开始扫描整个队列,重新执行处于等待状态的任务: 78 | 79 | ``` 80 | 假设队列中有如下四个任务,并行任务上限为 1: 81 | A waiting 82 | B waiting 83 | C downloading 84 | D waiting 85 | 当 C 执行完成后,A 将部署并执行,而 D 要等待 A、B 执行完成后才可以执行。 86 | ``` 87 | 88 | ## 实现方式: 89 | 90 | Netroid添加了 `FileDownloadRequest` 来实现断点下载功能,核心的实现逻辑都包括在这个请求内。由于文件下载操作将会相对较长时间地占用线程资源,为了避免所有线程均处于繁忙状态而导致无法执行其它高优先级的Http操作,建议不要使用这个类单独发起下载请求,应当与 **FileDownloader** 一起使用。 91 | 92 | 由于文件下载操作的特殊性,不适宜进行缓存处理,为了避免错误地设置,**FileDownloadRequest** 内部直接禁用了缓存,所以调用 **FileDownloadRequest.setCacheExpireTime()** 来指定缓存过期时间将不生效。 93 | 94 | 注:在测试中发现大文件下载可能出现连接超时的问题,所以 **FileDownloadRequest** 的重试次数设置了一个比较大的值(200),以避免下载失败。 95 | 96 | 注:**FileDownloadRequest** 的优先级为最低,在等待队列中,优先级更高的操作将更快执行。 97 | 98 | ## 疑难解决: 99 | 100 | 进度计算可能会出现以下两种异常情况: 101 | 102 | > 1、文件总大小为0,但已下载大小大于0,导致进度计算出错或一直为0%。 103 | 104 | 这种情况是因为服务端使用了Chunked Encoding返回数据,Netroid无法从响应头中获取到Content-Length,所以在进度回调时下载文件的总大小一直为零。有关Transfer-Encoding:chunked的原理,可参考[HttpWatch](http://www.httpwatch.com/httpgallery/chunked/)的详细介绍。 105 | 106 | > 2、文件已下载大小大于总大小,导致进度计算超出100%。 107 | 108 | 这种情况是因为服务端返回了gzip格式的数据,但Netroid在接收到gzip数据时使用了GzipInputStream直接解压缩存放,导致计算出来的已下载大小是解压后的大小,但总大小因为是从Content-Length中取得的压缩大小,所以导致计算误差。 109 | 110 | ### 问题原因: 111 | 112 | 无论Netroid使用的 **HurlStack** 或 **HttpClientStack** 均在每次发送请求时添加了接收gzip编码的响应结果: 113 | 114 | ```java 115 | HttpRequest.addHeader("Accept-Encoding", "gzip"); 116 | ``` 117 | 118 | 这个Header将通知服务端可返回通过gzip后的响应内容,客户端再进行解压存放,设置可接收gzip编码对于普通的请求操作来讲能够有效地节省流量,但对于文件下载组件来讲直接导致了上述第二个问题的发生,第一个问题也有可能是因为这个设置而导致服务端认为客户端可接收Chunked Encoding而引发的。 119 | 120 | 如果你将要下载的文件属于gzip作用不大的文件,例如:jpg、apk、rar、dmg等经过压缩的二进制文件格式,你可以禁用接收gzip编码文件的操作,以解决上述两个问题。但如果你去下载一个纯文本文件,gzip压缩可显著地节省流量,是否该允许进度计算出错的问题存在,这其中的利弊需要开发者自己取舍。 121 | 122 | ### 解决方案: 123 | 124 | Netroid允许开发者实现自己的文件下载逻辑,只需要重写 **FileDownloader.buildRequest()** 方法,返回继承自 `FileDownloadRequest` 的实例即可: 125 | 126 | ```java 127 | FileDownloader mDownloder = new FileDownloader(mQueue, 1) { 128 | @Override 129 | public FileDownloadRequest buildRequest(String storeFilePath, String url) { 130 | return new FileDownloadRequest(storeFilePath, url) { 131 | @Override 132 | public void prepare() { 133 | addHeader("Accept-Encoding", "identity"); 134 | // 父类的prepare()方法做了Range计算,不要忘记调用 135 | super.prepare(); 136 | } 137 | }; 138 | } 139 | }; 140 | ``` 141 | 142 | 示例中返回一个重写了 **prepare()** 方法的 FileDownloadRequest 对象,在prepare()方法中设置Accept-Encoding为identity以代替Netroid默认的gzip设置。这个定制方式允许开发者选择是否启用gzip编码,从而解决进度计算的问题。 143 | -------------------------------------------------------------------------------- /docs/_pages/imageloader.md: -------------------------------------------------------------------------------- 1 | title: Netroid ImageLoader 2 | decorator: post 3 | slug: imageloader.html 4 | ‡‡‡‡‡‡‡‡‡‡‡‡‡‡ 5 | 6 | # 图片加载 7 | 8 | 使用Netroid提供的`ImageLoader`可以非常方便地实现图片加载功能,ImageLoader是一个抽象类,需要由开发者实现其中的创建请求方法: 9 | 10 | ```java 11 | public abstract class ImageLoader { 12 | public abstract ImageRequest buildRequest(String requestUrl, int maxWidth, int maxHeight); 13 | } 14 | ``` 15 | 16 | 这样做的原因是在创建请求时需要指定硬盘缓存的过期时间,这些必须由开发者根据需要自行设置: 17 | 18 | ```java 19 | public class SelfImageLoader extends ImageLoader { 20 | @Override 21 | public ImageRequest buildRequest(String requestUrl, int maxWidth, int maxHeight) { 22 | ImageRequest request = new ImageRequest(requestUrl, maxWidth, maxHeight); 23 | request.setCacheExpireTime(TimeUnit.MINUTES, 20); 24 | return request; 25 | } 26 | } 27 | ``` 28 | 29 | ## 单张图片 30 | 31 | 单张图片的加载可以通过发起`ImageRequest`请求来实现,但为了应用内存缓存,推荐使用 **ImageLoader** 来完成: 32 | 33 | ```java 34 | String url = "http://server.domain/sample.jpg"; 35 | ImageLoader.ImageListener listener = ImageLoader.getImageListener( 36 | ImageView, R.drawable.defaultImageResId, R.drawable.errorImageResId); 37 | 38 | // 允许设置最终显示位置的尺寸,ImageLoader将根据比例缩放图片,不设置或设置为0代表使用图片原始尺寸 39 | imageLoader.get(url, listener, ImageView.getWidth(), ImageView.getHeight()); 40 | ``` 41 | 42 | ## 批量图片 43 | 44 | Netroid提供了`NetworkImageView`专门用于批量图片加载的场景: 45 | 46 | ```java 47 | public class NetworkImageView extends ImageView { 48 | private String mUrl; 49 | 50 | // 默认显示的图片 51 | private int mDefaultImageId; 52 | 53 | // 加载失败时显示的图片 54 | private int mErrorImageId; 55 | 56 | // 主方法入口 57 | public void setImageUrl(String url, ImageLoader imageLoader) { 58 | mUrl = url; 59 | mImageLoader = imageLoader; 60 | // 这个方法将会对ImageView的尺寸是否有效、是否为同一张图片进行判断 61 | // 在执行新请求前,也会取消上一次在这个View里启动的另一个已经失效的请求 62 | // 由于篇幅的限制以及代码行数太多,这里不贴出具体实现的代码 63 | loadImageIfNecessary(false); 64 | } 65 | 66 | // 如果图片已经滑离屏幕,变为不可见,将执行取消请求的操作 67 | @Override 68 | protected void onDetachedFromWindow() { 69 | if (mImageContainer != null) mImageContainer.cancelRequest(); 70 | super.onDetachedFromWindow(); 71 | } 72 | } 73 | ``` 74 | 75 | 在 **Adapter.getView()** 中只需要简单的调用 **NetworkImageView.setImageUrl()** 即可完成所有事情: 76 | 77 | ```java 78 | public View getView(int position, View convertView, ViewGroup parent) { 79 | if (convertView == null) { 80 | convertView = getLayoutInflater().inflate(R.layout.list_item, null); 81 | } 82 | 83 | NetworkImageView imvCover = (NetworkImageView) convertView.findViewById(R.id.imvCover); 84 | imvCover.setImageUrl(Book.getImageUrl(), mImageLoader); 85 | 86 | return convertView; 87 | } 88 | ``` 89 | 90 | ImageLoader内部对于批量加载做了很好的处理,开发者可以设定每个任务的延时执行时间,在列表快速滑动时,可最大限度避免已离开屏幕的失效请求占用线程资源。在请求执行前,会根据url对请求进行重复性判断,避免相同的url执行多次,在请求失效时会立即调用 **Request.cancel()** 方法尝试终止操作。 91 | 92 | 注:在图片一次加载成功后不再改变的情况下,可以使用普通的ImageView,但在ListView、GridView这种场景时建议使用 **NetworkImageView**,特别是遇到GridView position 0多次getView的bug时,普通的ImageView将会导致图片错位的异常问题,这种情况在演示程序中有专门的解决方案。 93 | 94 | ## 非Http图片 95 | 96 | 默认情况下,ImageLoader只能加载网络图片并使用缓存方案,但在实际开发中你除此之外可能也需要读取assets、raw、drawable、sdcard目录中的图片资源,这种非Http的请求在原来的Volley架构中是无法实现的。 97 | 98 | 为了避免重复开发,Netroid对此做了扩展,只需要重写 **Request.perform()** 方法,手动构造 **NetworkResponse** 对象,就可以模拟Http请求完成整个流程。 99 | 100 | ```java 101 | ImageRequest request = new ImageRequest("image_file_name_in_assert_folder.jpg", ...) { 102 | // 核心代码在于重写perform方法,返回NetworkResponse实例 103 | @Override 104 | public NetworkResponse perform() { 105 | try { 106 | return new NetworkResponse(InputStreamToBytes(getAssets().open(getUrl())), HTTP.UTF_8); 107 | } catch (IOException e) { 108 | return new NetworkResponse(new byte[1], HTTP.UTF_8); 109 | } 110 | } 111 | }; 112 | ``` 113 | 114 | 在 **perform()** 方法中,只需要加载assets资源为字节数组返回即可,InputStreamToBytes()方法的实现在此不详细列出。这个扩展方案解决了无法加载本地资源的问题,得以继续应用Cache、ImageLoader的强大功能。 115 | 116 | ImageLoader通过重写 **buildRequest()** 方法来实现同时兼容sdcard、http、assets等不同的图片来源: 117 | 118 | ```java 119 | public class SelfImageLoader extends ImageLoader { 120 | 121 | public static final String RES_ASSETS = "assets://"; 122 | public static final String RES_SDCARD = "sdcard://"; 123 | public static final String RES_HTTP = "http://"; 124 | 125 | private AssetManager mAssetManager; 126 | 127 | public SelfImageLoader(RequestQueue queue, AssetManager assetManager) { 128 | super(queue); 129 | mAssetManager = assetManager; 130 | } 131 | 132 | @Override 133 | public ImageRequest buildRequest(String requestUrl, int maxWidth, int maxHeight) { 134 | ImageRequest request; 135 | if (requestUrl.startsWith(RES_ASSETS)) { 136 | request = new ImageRequest(requestUrl.substring(RES_ASSETS.length()), maxWidth, maxHeight) { 137 | @Override 138 | public NetworkResponse perform() { 139 | try { 140 | return new NetworkResponse(toBytes(mAssetManager.open(getUrl())), HTTP.UTF_8); 141 | } catch (IOException e) { 142 | return new NetworkResponse(new byte[1], HTTP.UTF_8); 143 | } 144 | } 145 | }; 146 | } 147 | else if (requestUrl.startsWith(RES_SDCARD)) { 148 | request = new ImageRequest(requestUrl.substring(RES_SDCARD.length()), maxWidth, maxHeight) { 149 | @Override 150 | public NetworkResponse perform() { 151 | try { 152 | return new NetworkResponse(toBytes(new FileInputStream(getUrl())), HTTP.UTF_8); 153 | } catch (IOException e) { 154 | return new NetworkResponse(new byte[1], HTTP.UTF_8); 155 | } 156 | } 157 | }; 158 | } 159 | else if (requestUrl.startsWith(RES_HTTP)) { 160 | request = new ImageRequest(requestUrl, maxWidth, maxHeight); 161 | } 162 | else { 163 | return null; 164 | } 165 | 166 | makeRequest(request); 167 | return request; 168 | } 169 | 170 | public void makeRequest(ImageRequest request) { 171 | request.setCacheExpireTime(TimeUnit.MINUTES, 30); 172 | } 173 | 174 | } 175 | 176 | // 示例:读取http资源的调用方法 177 | String url = "http://server.domain/sample.jpg"; 178 | ImageLoader.get(url, listener); 179 | 180 | // 示例:读取assets资源的调用方法 181 | String url = SelfImageLoader.RES_ASSETS + "sample.jpg"; 182 | ImageLoader.get(url, listener); 183 | 184 | // 示例:读取sdcard资源的调用方法 185 | String url = SelfImageLoader.RES_SDCARD + "/sdcard/sample.jpg"; 186 | ImageLoader.get(url, listener); 187 | ``` 188 | 189 | 这是一个实现兼容不同图片来源的解决方案,可在此基础上添加读取raw、drawable等不同目录下的图片,由于篇幅的限制不作举例,具体可查看演示程序中的代码。 190 | 191 | -------------------------------------------------------------------------------- /docs/_pages/index.md: -------------------------------------------------------------------------------- 1 | title: Netroid Introduction 2 | decorator: post 3 | slug: index.html 4 | ‡‡‡‡‡‡‡‡‡‡‡‡‡‡ 5 | 6 | # 简介: 7 | 8 | Netroid是一个基于[Volley](http://developer.android.com/training/volley/index.html)实现的Android Http库。提供执行网络请求、缓存返回结果、批量图片加载、大文件断点下载的常见Http交互功能。致力于避免每个项目重复开发基础Http功能,实现显著地缩短开发周期的愿景。 9 | 10 | # 实现原理: 11 | 12 | Netroid自启动后创建由开发者指定的线程数目,每个线程由 **BlockingQueue** 进行阻塞。当有新的请求进入队列时,其中一个线程将被唤醒并获得请求对象,然后开始执行,执行完成后线程重新回到阻塞状态,等待下一次唤醒。Netroid实现了强大的状态回调接口在请求执行过程中进行通知,包括开始、完成、成功、重试、失败、取消、执行网络操作、应用缓存、下载进度九种状态回调,开发者可方便地获取请求的执行情况,对用户进行友好提醒。 13 | 14 | #### 注:Netroid的线程池不具备伸缩功能,创建后所有线程均处于启用状态,不支持动态调整。 15 | 16 | # 文档组成: 17 | 18 | * [开始使用](/startup.html) 19 | 20 | 快速实现将Netroid集成到应用中。 21 | 22 | * [使用示例](/usecase.html) 23 | 24 | 了解Netroid在应用中的典型代码写法。 25 | 26 | * [组件详解](/understanding.html) 27 | 28 | Netroid中的各种内部组件及其使用方法的详细解释。 29 | 30 | * [请求处理](/request.html) 31 | 32 | 了解请求执行类的细节,定制请求处理方式,各种请求场景的实现方法。 33 | 34 | * [图片加载](/imageloader.html) 35 | 36 | 了解图片加载器的功能点及其使用方法。 37 | 38 | * [大文件下载](/filedownloader.html) 39 | 40 | 了解Netroid提供的文件断点下载组件的功能。 41 | 42 | * [Javadoc文档](/javadoc/index.html) 43 | 44 | 了解Netroid所有接口的细节。 45 | 46 | 也可直接点击下载[演示](/attach/netroid-sample-1.2.1.apk)程序查看运行效果。 47 | 48 | 49 | # 修改日志 50 | 51 | | 版本 | 说明 | 52 | | :-------------: | :------------- | 53 | | `1.2.1`
2014-05-05 | 1、下载组件允许定制FileDownloadRequest,以解决进度计算由于gzip引发的误差
2、执行下载任务时允许Content-Length不可用的问题
3、HttpClientStack默认开启接收gzip编码的响应结果设置 | 54 | | `1.2.0`
2014-04-13 | 1、重构缓存模块,普通Http请求不再允许使用内存缓存,不兼容之前的API
2、ImageLoader回退到Volley的实现方式,提供专属的内存缓存对象,纠正内存图片加载的不必要延时错误 | 55 | | `1.1.1`
2014-04-09 | 1、添加Request.prepare()方法实现在连接重试时更新Header
2、解决大文件下载由于连接重试导致的断点位置不正确的问题 | 56 | | `1.1.0`
2014-03-07 | 1、添加大文件下载管理器
2、修正HttpClient无法执行请求的问题 | 57 | | `1.0.3`
2013-01-14 | 1、ImageLoader支持执行非http请求
2、请求Listener实现多种情况的状态回调 | 58 | 59 | -------------------------------------------------------------------------------- /docs/_pages/startup.md: -------------------------------------------------------------------------------- 1 | title: Netroid Startup 2 | decorator: post 3 | slug: startup.html 4 | ‡‡‡‡‡‡‡‡‡‡‡‡‡‡ 5 | 6 | # 开始使用 7 | 8 | 本文档帮助你快速并正确地集成Netroid到应用中。 9 | 10 | 11 | ### 开发集成 12 | 13 | 方式一:下载[最新版本](http://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=com.duowan.android.netroid&a=netroid&v=LATEST)的jar,在工程中直接添加依赖。 14 | 15 | 方式二:Netroid已经发布于 **Maven** 库,你可以通过添加以下的依赖来引用: 16 | 17 | ```xml 18 | 19 | com.duowan.android.netroid 20 | netroid 21 | (insert latest version) 22 | 23 | ``` 24 | 25 | 方式三:同样地,使用 **Gradle** 的开发者可以添加以下描述来引用: 26 | 27 | ```groovy 28 | compile 'com.duowan.android.netroid:netroid:(insert latest version)' 29 | ``` 30 | 31 | #### 注:当前的最新版本是 `1.2.1`。 32 | 33 | 34 | ### 添加权限 35 | 36 | 配置 `AndroidManifest.xml`,添加Netroid SDK需要的权限到 `` 标签下: 37 | 38 | ```xml 39 | 40 | 41 | ``` 42 | 43 | ### 初始化 44 | 45 | 由于Http在Android中属于基础服务组件,为了达到随处可用的目标,通常情况都采用单例模式进行初始化并集中管理: 46 | 47 | ```java 48 | // 在 Android Application 这个程序入口处进行Netroid的初始化 49 | public class YourApplication extends Application { 50 | @Override 51 | public void onCreate() { 52 | super.onCreate(); 53 | Netroid.init(this); 54 | } 55 | } 56 | ``` 57 | 58 | `Netroid` 是开发者实现的用于管理所有包括 **RequestQueue**、**ImageLoader**、**FileDownloader** 实例的核心类: 59 | 60 | ```java 61 | public class Netroid { 62 | // Netroid入口,私有该实例,提供方法对外服务。 63 | private static RequestQueue mRequestQueue; 64 | 65 | // 图片加载管理器,私有该实例,提供方法对外服务。 66 | private static ImageLoader mImageLoader; 67 | 68 | // 文件下载管理器,私有该实例,提供方法对外服务。 69 | private static FileDownloader mFileDownloader; 70 | 71 | private Netroid() {} 72 | 73 | public static void init(Context ctx) { 74 | if (mRequestQueue != null) throw new IllegalStateException("initialized"); 75 | 76 | // 创建Netroid主类,指定硬盘缓存方案 77 | Network network = new BasicNetwork(new HurlStack(Const.USER_AGENT, null), HTTP.UTF_8); 78 | mRequestQueue = new RequestQueue(network, 4, new DiskCache( 79 | new File(ctx.getCacheDir(), Const.HTTP_DISK_CACHE_DIR_NAME), Const.HTTP_DISK_CACHE_SIZE)); 80 | 81 | // 创建ImageLoader实例,指定内存缓存方案 82 | // 注:SelfImageLoader的实现示例请查看图片加载的相关文档 83 | // 注:ImageLoader及FileDownloader不是必须初始化的组件,如果没有用处,不需要创建实例 84 | mImageLoader = new SelfImageLoader( 85 | mRequestQueue, new BitmapImageCache(Const.HTTP_MEMORY_CACHE_SIZE)); 86 | 87 | mFileDownloader = new FileDownloader(mRequestQueue, 1); 88 | 89 | mRequestQueue.start(); 90 | } 91 | 92 | // 示例做法:执行自定义请求以获得书籍列表 93 | public static void getBookList(int pageNo, Listener> listener) { 94 | mRequestQueue.add(new BookListRequest("http://server.com/book_list/" + pageNo, listener)); 95 | } 96 | 97 | // 加载单张图片 98 | public static void displayImage(String url, ImageView imageView) { 99 | ImageLoader.ImageListener listener = ImageLoader.getImageListener(imageView, 0, 0); 100 | mImageLoader.get(url, listener, 0, 0); 101 | } 102 | 103 | // 批量加载图片 104 | public static void displayImage(String url, NetworkImageView imageView) { 105 | imageView.setImageUrl(url, mImageLoader); 106 | } 107 | 108 | // 执行文件下载请求 109 | public static FileDownloader.DownloadController addFileDownload(String storeFilePath, String url, Listener listener) { 110 | return mFileDownloader.add(storeFilePath, url, listener); 111 | } 112 | } 113 | 114 | public class Const { 115 | // http parameters 116 | public static final int HTTP_MEMORY_CACHE_SIZE = 2 * 1024 * 1024; // 2MB 117 | public static final int HTTP_DISK_CACHE_SIZE = 50 * 1024 * 1024; // 50MB 118 | public static final String HTTP_DISK_CACHE_DIR_NAME = "netroid"; 119 | public static final String USER_AGENT = "netroid.cn"; 120 | } 121 | ``` 122 | 123 | 至此,Netroid的所有初始化工作已经完成,你可以添加各种接口来执行请求,使用Netroid提供的强大服务!! 124 | 125 | 开发者可查看[使用示例](/usecase.html)来了解典型的代码写法,更多细节请查看[请求处理](/request.html)的全面描述。 126 | 127 | -------------------------------------------------------------------------------- /docs/_pages/understanding.md: -------------------------------------------------------------------------------- 1 | title: Netroid Understanding 2 | decorator: post 3 | slug: understanding.html 4 | ‡‡‡‡‡‡‡‡‡‡‡‡‡‡ 5 | 6 | # 运行原理 7 | 8 | Netroid在启动时创建以下几个实例用于提供网络服务: 9 | 10 | | | 11 | | :------------- | 12 | | `NetWork`用于执行Http请求的对象,有HttpUrlConnection、HttpClient两种执行方式,可以自行指定使用哪种方式。 | 13 | | `Network Thread Pool`调用Http请求执行对象(**NetWork**)的线程池,默认为四个线程(NetworkDispatcher)的容量,在初始化时可进行指定。每个线程由BlockingQueue.take()方法进行阻塞,然后一直处于等待状态,在新请求进入队列后马上开始执行。 | 14 | | `Cache Dispatcher`每个需要进行缓存操作的请求都将首先由这个线程执行,在缓存未过期时直接返回缓存数据,否则将请求放进 **Network Queue** 队列。 | 15 | | `Cache Queue`需要执行缓存操作的Http请求对象队列,**Cache Dispatcher**通过获取到达这个队列的请求来执行缓存检测。 | 16 | | `Network Queue`需要执行网络操作的Http请求对象队列,**Network Thread Pool**通过获取到达这个队列的请求来执行实际的网络操作。 | 17 | | `Response Delivery`执行返回请求结果的功能,内部通过Handler.post(Runnable)来实现在UI线程上处理响应结果,区分请求成功和请求失败两种情况。 | 18 | 19 | 在请求到达时,每个组件中将会依次对请求执行处理,流程如下: 20 | 21 | ![Netroid Request Handling Flowchart](/netroid_request_handling_flowchart.png "Netroid Request Handling Flowchart") 22 | 23 | 24 | # 组件介绍 25 | 26 | 程序入口为`RequestQueue`类,提供以下几个主要的接口进行交互执行相关操作。 27 | 28 | ```java 29 | public interface RequestQueue { 30 | // 启动Netroid服务的入口 31 | public void start(); 32 | 33 | // 停止Netroid服务 34 | public void stop(); 35 | 36 | // 根据Request的Tag标记来停止某一同类型请求 37 | // 注:Netroid只是做了退出的标记,请求不会马上被终止 38 | public void cancelAll(Object tag); 39 | 40 | // 将一个创建好的请求实例添加到队列,由队列执行 41 | public void add(Request request); 42 | } 43 | ``` 44 | 45 | 大多数情况下,**RequestQueue** 类需要接收三个简单的参数来进行初始化,分别是Http请求执行对象(Network)、Netroid线程池容量、请求的硬盘缓存方案: 46 | 47 | ```java 48 | public RequestQueue(Network network, int threadPoolSize, DiskCache cache); 49 | ``` 50 | 51 | ## 构造Network: 52 | 53 | **BasicNetwork** 是Netroid提供的 **Network** 接口实现类: 54 | 55 | ```java 56 | // 请求响应的编码由"Content-Type" Header决定,defaultCharset用于当服务端未返回编码方式时的默认选择 57 | public BasicNetwork(HttpStack httpStack, String defaultCharset); 58 | ``` 59 | 60 | `HttpStack`接口实例用于指示网络请求的执行方式,Netroid提供了**HurlStack**、**HttpClientStack**两种默认实现,对于这两种执行方式的选择,Volley建议GingerBread(API9)或以上的Android系统选择用HurlStack,参考写法如下: 61 | 62 | ```java 63 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { 64 | httpStack = new HurlStack(userAgent); 65 | } else { 66 | // Prior to Gingerbread, HttpUrlConnection was unreliable. 67 | // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html 68 | httpStack = new HttpClientStack(userAgent); 69 | } 70 | ``` 71 | 72 | #### 注:由于Android中的HttpClient在API9之后的版本中不具备优势,HttpClientStack仍有相当多的Bug未解决,鉴于Android 4.0或以上的版本已经相当普及,所以没有投入精力去解决这些问题,建议开发者使用 **HurlStack**。 73 | 74 | User-Agent是HttpStack接口的两个实现类中默认提供的 **Header** 设置,你可以根据自己的需要设置UA,也可以直接使用android package name作为UA: 75 | 76 | ```java 77 | String userAgent = "netroid/0"; 78 | try { 79 | String packageName = context.getPackageName(); 80 | PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0); 81 | userAgent = packageName + "/" + info.versionCode; 82 | } catch (NameNotFoundException e) { 83 | } 84 | ``` 85 | 86 | ## 缓存方案的用法: 87 | 88 | Netroid默认提供两种类型的Cache,均使用了Lru算法: 89 | 90 | | | 91 | | :------------- | 92 | | `DiskCache`基于硬盘的持久化缓存,所有请求均可使用。 | 93 | | `BitmapImageCache`基于内存的图片缓存,专属于 **ImageLoader** 的缓存方案。 | 94 | 95 | Netroid在执行请求时默认不使用任何缓存,所以在初始化`RequestQueue`对象时,开发者必须手动构造硬盘缓存方案。同样地,**ImageLoader** 也需要做相同的操作来指定内存缓存。 96 | 97 | 这两种缓存方案需要开发者构建,所以你可以继承它们并加以扩展。但实际上Netroid提供的默认实现已经能够满足大部分的应用场景,典型的写法如下: 98 | 99 | ```java 100 | // 创建Netroid主类,指定硬盘缓存方案 101 | mRequestQueue = new RequestQueue(..., new DiskCache(new File("/sdcard/netroid/"), 50 * 1024 * 1024)); 102 | 103 | // 创建ImageLoader实例,指定内存缓存方案 104 | mImageLoader = new SelfImageLoader(mRequestQueue, new BitmapImageCache(2 * 1024 * 1024)); 105 | ``` 106 | 107 | 所有的Http请求均可使用硬盘缓存方案,开发者必须在发起一个新的Http请求时通过 **setCacheExpireTime(...)** 指定过期时间,才能使缓存生效。 108 | 109 | ```java 110 | String url = "http://server.domain/json_array.json"; 111 | JsonArrayRequest request = new JsonArrayRequest(url, new Listener() { 112 | @Override 113 | public void onSuccess(JSONArray response) { 114 | showResult(response.toString()); 115 | } 116 | 117 | @Override 118 | public void onError(NetroidError error) { 119 | showError(error); 120 | } 121 | }); 122 | 123 | // 设置为十天之后过期 124 | request.setCacheExpireTime(TimeUnit.DAYS, 10); 125 | mQueue.add(request); 126 | ``` 127 | 128 | 特殊情况下,你需要不读取缓存而是直接执行网络请求,然后再将返回结果保存进缓存,做强制刷新操作, 129 | Netroid提供了 **setForceUpdate(true)** 设置允许你实现这种需求: 130 | 131 | ```java 132 | request.setForceUpdate(true); 133 | request.setCacheExpireTime(TimeUnit.DAYS, 10); 134 | ``` 135 | 136 | 以上的写法指定Netroid将不检索缓存情况,马上执行网络请求操作,获得响应结果后放入硬盘缓存中,缓存的过期时间设置为十天。 137 | 138 | -------------------------------------------------------------------------------- /docs/_pages/usecase.md: -------------------------------------------------------------------------------- 1 | title: Netroid Usecase 2 | decorator: post 3 | slug: usecase.html 4 | ‡‡‡‡‡‡‡‡‡‡‡‡‡‡ 5 | 6 | # 使用示例 7 | 8 | 使用`JsonObjectRequest`获取一个json对象: 9 | 10 | ```java 11 | String url = "http://server.domain/json_object.do"; 12 | JsonObjectRequest request = new JsonObjectRequest(url, null, new Listener() { 13 | @Override 14 | public void onSuccess(JSONObject response) { 15 | Toast.makeText(Activity.this, response.getString("result"), 2000).show(); 16 | } 17 | 18 | @Override 19 | public void onError(NetroidError error) { 20 | Toast.makeText(Activity.this, "error occurred : " + error.getMessage(), 2000).show(); 21 | } 22 | }); 23 | 24 | // 设置请求标识,这个标识可用于终止该请求时传入的Key 25 | request.setTag("json-request"); 26 | RequestQueue.add(request); 27 | ``` 28 | 29 | 使用`JsonArrayRequest`获取一个json对象列表: 30 | 31 | ```java 32 | String url = "http://server.domain/json_array.do"; 33 | JsonArrayRequest request = new JsonArrayRequest(url, new Listener() { 34 | @Override 35 | public void onSuccess(JSONArray response) { 36 | Toast.makeText(Activity.this, "JSONArray length : " + response.length(), 2000).show(); 37 | } 38 | 39 | @Override 40 | public void onError(NetroidError error) { 41 | Toast.makeText(Activity.this, "error occurred : " + error.getMessage(), 2000).show(); 42 | } 43 | }); 44 | 45 | // 忽略请求的硬盘缓存,直接执行Http操作 46 | request.setForceUpdate(true); 47 | // Http操作成功后保存进缓存的过期时间 48 | request.setCacheExpireTime(TimeUnit.MINUTES, 10); 49 | RequestQueue.add(request); 50 | ``` 51 | 52 | 使用`StringRequest`获取一个字符串结果: 53 | 54 | ```java 55 | String url = "http://server.domain/string.do"; 56 | StringRequest request = new StringRequest(Request.Method.GET, url, new Listener() { 57 | @Override 58 | public void onSuccess(String response) { 59 | Toast.makeText(Activity.this, "response : " + response, 2000).show(); 60 | } 61 | 62 | @Override 63 | public void onError(NetroidError error) { 64 | Toast.makeText(Activity.this, "error occurred : " + error.getMessage(), 2000).show(); 65 | } 66 | }); 67 | 68 | // 设置请求Header 69 | request.addHeader("Accept-Encoding", "gzip, deflate"); 70 | RequestQueue.add(request); 71 | ``` 72 | 73 | 使用`ImageRequest`获取一张图片: 74 | 75 | ```java 76 | String url = "http://server.domain/sample.jpg"; 77 | ImageRequest request = new ImageRequest(url, new Listener() { 78 | @Override 79 | public void onSuccess(Bitmap response) { 80 | ImageView.setImageBitmap(response); 81 | } 82 | 83 | @Override 84 | public void onError(NetroidError error) { 85 | Toast.makeText(Activity.this, "error occurred : " + error.getMessage(), 2000).show(); 86 | } 87 | }, ImageView.getWidth(), ImageView.getHeight(), Bitmap.Config.RGB_565); 88 | 89 | // 设置返回结果在硬盘缓存中的过期时间为10天 90 | request.setCacheExpireTime(TimeUnit.DAYS, 10); 91 | RequestQueue.add(request); 92 | ``` 93 | 94 | #### 注:不建议直接使用ImageRequest,推荐使用 **ImageLoader** 来加载图片。 95 | 96 | 使用Netroid,你的所有Http操作代码都将类似于这样的书写方式。 97 | 98 | -------------------------------------------------------------------------------- /docs/netroid: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This shell was write for netroid documentation generate & build, 4 | # use khaleesi(https://github.com/vince-styling/khaleesi) as generator. 5 | # 6 | 7 | src_dir=~/dev/netroid/docs 8 | dest_dir=~/dev/netroid/site 9 | line_numbers="true" 10 | toc_selection="h1,h2[unique]" 11 | 12 | if [[ "$1" == "generate" ]]; then 13 | diff=$([ "$2" == 'diff' ] && echo "true" || echo "false") 14 | khaleesi generate --src-dir "$src_dir" --dest-dir "$dest_dir" \ 15 | --line-numbers $line_numbers --diff-plus "$diff" --toc-selection "$toc_selection" 16 | 17 | elif [[ "$1" == "serve" ]]; then 18 | #nohup ruby -run -e httpd $dest_dir -p 9090 & 19 | ruby -run -e httpd $dest_dir -p 9090 20 | 21 | elif [[ "$1" == "build" ]]; then 22 | temperary_dest_dir=~/tmp_site 23 | 24 | rm -fr $temperary_dest_dir 25 | mkdir $temperary_dest_dir 26 | 27 | cd $src_dir 28 | cd .. 29 | 30 | git checkout master 31 | 32 | khaleesi build --src-dir "$src_dir" --dest-dir "$temperary_dest_dir" \ 33 | --line-numbers $line_numbers --highlighter pygments --toc-selection "$toc_selection" 34 | 35 | git checkout gh-pages 36 | 37 | rsync -acv $temperary_dest_dir/ . 38 | rm -fr $temperary_dest_dir 39 | 40 | fi -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Settings specified in this file will override any Gradle settings 5 | # configured through the IDE. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vince-styling/Netroid/7e67a08db8fecfee9d8c9ac4be625da108c4012c/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Jun 23 13:01:43 HKT 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 22 5 | buildToolsVersion "22.0.1" 6 | 7 | defaultConfig { 8 | minSdkVersion 9 9 | targetSdkVersion 22 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/AuthFailureError.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid; 17 | 18 | import android.content.Intent; 19 | 20 | /** 21 | * Error indicating that there was an authentication failure when performing a Request. 22 | */ 23 | public class AuthFailureError extends NetroidError { 24 | /** 25 | * An intent that can be used to resolve this exception. (Brings up the password dialog.) 26 | */ 27 | private Intent mResolutionIntent; 28 | 29 | public AuthFailureError() { 30 | } 31 | 32 | public AuthFailureError(Intent intent) { 33 | mResolutionIntent = intent; 34 | } 35 | 36 | public AuthFailureError(NetworkResponse response) { 37 | super(response); 38 | } 39 | 40 | public AuthFailureError(String message) { 41 | super(message); 42 | } 43 | 44 | public AuthFailureError(String message, Exception reason) { 45 | super(message, reason); 46 | } 47 | 48 | public Intent getResolutionIntent() { 49 | return mResolutionIntent; 50 | } 51 | 52 | @Override 53 | public String getMessage() { 54 | if (mResolutionIntent != null) { 55 | return "User needs to (re)enter credentials."; 56 | } 57 | return super.getMessage(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/CacheDispatcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid; 17 | 18 | import android.os.Process; 19 | import com.vincestyling.netroid.cache.DiskCache; 20 | 21 | import java.util.concurrent.BlockingQueue; 22 | 23 | /** 24 | * Provides a thread for performing cache triage on a queue of requests. 25 | *

26 | * Requests added to the specified cache queue are resolved from cache. 27 | * Any deliverable response is posted back to the caller via a 28 | * {@link Delivery}. Cache misses and responses that require 29 | * refresh are enqueued on the specified network queue for processing 30 | * by a {@link NetworkDispatcher}. 31 | */ 32 | public class CacheDispatcher extends Thread { 33 | 34 | private static final boolean DEBUG = NetroidLog.DEBUG; 35 | 36 | /** 37 | * The queue of requests coming in for triage. 38 | */ 39 | private final BlockingQueue mCacheQueue; 40 | 41 | /** 42 | * The queue of requests going out to the network. 43 | */ 44 | private final BlockingQueue mNetworkQueue; 45 | 46 | /** 47 | * The cache to read from. 48 | */ 49 | private final DiskCache mCache; 50 | 51 | /** 52 | * For posting responses. 53 | */ 54 | private final Delivery mDelivery; 55 | 56 | /** 57 | * Used for telling us to die. 58 | */ 59 | private volatile boolean mQuit = false; 60 | 61 | /** 62 | * Creates a new cache triage dispatcher thread. You must call {@link #start()} 63 | * in order to begin processing. 64 | * 65 | * @param cacheQueue Queue of incoming requests for triage 66 | * @param networkQueue Queue to post requests that require network to 67 | * @param cache Cache interface to use for resolution 68 | * @param delivery Delivery interface to use for posting responses 69 | */ 70 | public CacheDispatcher(BlockingQueue cacheQueue, BlockingQueue networkQueue, 71 | DiskCache cache, Delivery delivery) { 72 | mCache = cache; 73 | mDelivery = delivery; 74 | mCacheQueue = cacheQueue; 75 | mNetworkQueue = networkQueue; 76 | } 77 | 78 | /** 79 | * Forces this dispatcher to quit immediately. If any requests are still in 80 | * the queue, they are not guaranteed to be processed. 81 | */ 82 | public void quit() { 83 | mQuit = true; 84 | interrupt(); 85 | } 86 | 87 | @Override 88 | public void run() { 89 | if (DEBUG) NetroidLog.v("start new dispatcher"); 90 | Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 91 | 92 | // Make a blocking call to initialize the cache. 93 | if (mCache != null) mCache.initialize(); 94 | 95 | while (true) { 96 | try { 97 | // Get a request from the cache triage queue, blocking until 98 | // at least one is available. 99 | final Request request = mCacheQueue.take(); 100 | request.addMarker("cache-queue-take"); 101 | mDelivery.postPreExecute(request); 102 | 103 | // If the request has been canceled, don't bother dispatching it. 104 | if (request.isCanceled()) { 105 | request.finish("cache-discard-canceled"); 106 | mDelivery.postCancel(request); 107 | mDelivery.postFinish(request); 108 | continue; 109 | } 110 | 111 | // Attempt to retrieve this item from cache. 112 | DiskCache.Entry entry = mCache != null ? mCache.getEntry(request.getCacheKey()) : null; 113 | if (entry == null) { 114 | request.addMarker("cache-miss"); 115 | // Cache miss; send off to the network dispatcher. 116 | mNetworkQueue.put(request); 117 | mDelivery.postNetworking(request); 118 | continue; 119 | } 120 | 121 | // If it is completely expired, just send it to the network. 122 | if (entry.isExpired()) { 123 | request.addMarker("cache-hit-expired"); 124 | mNetworkQueue.put(request); 125 | mDelivery.postNetworking(request); 126 | continue; 127 | } 128 | 129 | // We have a cache hit; parse its data for delivery back to the request. 130 | request.addMarker("cache-hit"); 131 | Response response = request.parseNetworkResponse(new NetworkResponse(entry.getData(), entry.getCharset())); 132 | request.addMarker("cache-hit-parsed"); 133 | mDelivery.postUsedCache(request); 134 | 135 | if (!entry.refreshNeeded()) { 136 | // Completely unexpired cache hit. Just deliver the response. 137 | mDelivery.postResponse(request, response); 138 | } else { 139 | // Soft-expired cache hit. We can deliver the cached response, 140 | // but we need to also send the request to the network for 141 | // refreshing. 142 | request.addMarker("cache-hit-refresh-needed"); 143 | 144 | // Mark the response as intermediate. 145 | response.intermediate = true; 146 | 147 | // Post the intermediate response back to the user and have 148 | // the delivery then forward the request along to the network. 149 | mDelivery.postResponse(request, response, new Runnable() { 150 | @Override 151 | public void run() { 152 | try { 153 | mNetworkQueue.put(request); 154 | } catch (InterruptedException e) { 155 | // Not much we can do about this. 156 | } 157 | } 158 | }); 159 | } 160 | } catch (InterruptedException e) { 161 | // We may have been interrupted because it was time to quit. 162 | if (mQuit) return; 163 | } 164 | } 165 | } 166 | 167 | } 168 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/ClientError.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid; 17 | 18 | /** 19 | * Indicates that the server responded with an error response indicating that the client has erred. 20 | * 21 | * For backwards compatibility, extends ServerError which used to be thrown for all server errors, 22 | * including 4xx error codes indicating a client error. 23 | */ 24 | public class ClientError extends ServerError { 25 | public ClientError(NetworkResponse networkResponse) { 26 | super(networkResponse); 27 | } 28 | 29 | public ClientError() { 30 | super(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/DefaultRetryPolicy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid; 17 | 18 | /** 19 | * Default retry policy for requests. 20 | */ 21 | public class DefaultRetryPolicy implements RetryPolicy { 22 | /** 23 | * The current timeout in milliseconds. 24 | */ 25 | private int mCurrentTimeoutMs; 26 | 27 | /** 28 | * The current retry count. 29 | */ 30 | private int mCurrentRetryCount; 31 | 32 | /** 33 | * The maximum number of attempts. 34 | */ 35 | private final int mMaxNumRetries; 36 | 37 | /** 38 | * The backoff multiplier for for the policy. 39 | */ 40 | private final float mBackoffMultiplier; 41 | 42 | /** 43 | * The default socket timeout in milliseconds 44 | */ 45 | public static final int DEFAULT_TIMEOUT_MS = 2500; 46 | 47 | /** 48 | * The default number of retries 49 | */ 50 | public static final int DEFAULT_MAX_RETRIES = 1; 51 | 52 | /** 53 | * The default backoff multiplier 54 | */ 55 | public static final float DEFAULT_BACKOFF_MULT = 1f; 56 | 57 | /** 58 | * Constructs a new retry policy using the default timeouts. 59 | */ 60 | public DefaultRetryPolicy() { 61 | this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT); 62 | } 63 | 64 | /** 65 | * Constructs a new retry policy. 66 | * 67 | * @param initialTimeoutMs The initial timeout for the policy. 68 | * @param maxNumRetries The maximum number of retries. 69 | * @param backoffMultiplier Backoff multiplier for the policy. 70 | */ 71 | public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) { 72 | mCurrentTimeoutMs = initialTimeoutMs; 73 | mMaxNumRetries = maxNumRetries; 74 | mBackoffMultiplier = backoffMultiplier; 75 | } 76 | 77 | /** 78 | * Returns the current timeout. 79 | */ 80 | @Override 81 | public int getCurrentTimeout() { 82 | return mCurrentTimeoutMs; 83 | } 84 | 85 | /** 86 | * Returns the current retry count. 87 | */ 88 | @Override 89 | public int getCurrentRetryCount() { 90 | return mCurrentRetryCount; 91 | } 92 | 93 | /** 94 | * Prepares for the next retry by applying a backoff to the timeout. 95 | * 96 | * @param error The error code of the last attempt. 97 | */ 98 | @Override 99 | public void retry(NetroidError error) throws NetroidError { 100 | mCurrentRetryCount++; 101 | mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier); 102 | if (!hasAttemptRemaining()) { 103 | throw error; 104 | } 105 | } 106 | 107 | /** 108 | * Returns true if this policy has attempts remaining, false otherwise. 109 | */ 110 | protected boolean hasAttemptRemaining() { 111 | return mCurrentRetryCount <= mMaxNumRetries; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/Delivery.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid; 17 | 18 | public interface Delivery { 19 | 20 | /** 21 | * Posts request finished callback for the given request. 22 | */ 23 | void postFinish(Request request); 24 | 25 | /** 26 | * Parses a response from the network or cache and delivers it. 27 | */ 28 | void postResponse(Request request, Response response); 29 | 30 | /** 31 | * Parses a response from the network or cache and delivers it. The provided 32 | * Runnable will be executed after delivery. 33 | */ 34 | void postResponse(Request request, Response response, Runnable runnable); 35 | 36 | /** 37 | * Posts an error for the given request. 38 | */ 39 | void postError(Request request, NetroidError error); 40 | 41 | /** 42 | * Posts a cancel callback for the given request. 43 | */ 44 | void postCancel(Request request); 45 | 46 | /** 47 | * Posts starting execute callback for the given request. 48 | */ 49 | void postPreExecute(Request request); 50 | 51 | /** 52 | * Posts cache used callback for the given request. 53 | */ 54 | void postUsedCache(Request request); 55 | 56 | /** 57 | * Posts networking callback for the given request. 58 | */ 59 | void postNetworking(Request request); 60 | 61 | /** 62 | * Posts request retry callback for the given request. 63 | */ 64 | void postRetry(Request request); 65 | 66 | /** 67 | * Posts file download progress stat. 68 | */ 69 | void postDownloadProgress(Request request, long fileSize, long downloadedSize); 70 | } 71 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/ExecutorDelivery.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid; 17 | 18 | import android.os.Handler; 19 | 20 | import java.util.concurrent.Executor; 21 | 22 | /** 23 | * Delivers responses and errors. This class supposed to perform the delivers on the main thread. 24 | * Whereas you can still take off with it via {@link #ExecutorDelivery(Executor)} 25 | * by pass a simple Executor like following : 26 | *

 {@code
 27 |  * new ExecutorDelivery(new Executor() {
 28 |  *     public void execute(Runnable command) {
 29 |  *       // invoke run() directly.
 30 |  *       command.run();
 31 |  *     }
 32 |  * });}
33 | * Take advantage of it, we're able to perform a Http Request on 34 | * blocking purpose while we on background thread explicitly. 35 | */ 36 | public class ExecutorDelivery implements Delivery { 37 | /** 38 | * Used for posting responses, typically to the main thread. 39 | */ 40 | private final Executor mResponsePoster; 41 | 42 | /** 43 | * Creates a new response delivery interface. 44 | * 45 | * @param handler {@link Handler} to post responses on 46 | */ 47 | public ExecutorDelivery(final Handler handler) { 48 | // Make an Executor that just wraps the handler. 49 | mResponsePoster = new Executor() { 50 | @Override 51 | public void execute(Runnable command) { 52 | handler.post(command); 53 | } 54 | }; 55 | } 56 | 57 | /** 58 | * Creates a new response delivery interface, mockable version 59 | * for testing. 60 | * 61 | * @param executor For running delivery tasks 62 | */ 63 | public ExecutorDelivery(Executor executor) { 64 | mResponsePoster = executor; 65 | } 66 | 67 | @Override 68 | public void postFinish(final Request request) { 69 | request.addMarker("post-finish"); 70 | mResponsePoster.execute(new Runnable() { 71 | @Override 72 | public void run() { 73 | request.deliverFinish(); 74 | } 75 | }); 76 | } 77 | 78 | @Override 79 | public void postResponse(Request request, Response response) { 80 | postResponse(request, response, null); 81 | } 82 | 83 | @Override 84 | public void postResponse(Request request, Response response, Runnable runnable) { 85 | request.markDelivered(); 86 | request.addMarker("post-response"); 87 | mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable)); 88 | } 89 | 90 | @Override 91 | public void postError(Request request, NetroidError error) { 92 | request.addMarker("post-error"); 93 | Response response = Response.error(error); 94 | mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null)); 95 | } 96 | 97 | @Override 98 | public void postCancel(final Request request) { 99 | request.addMarker("post-cancel"); 100 | mResponsePoster.execute(new Runnable() { 101 | @Override 102 | public void run() { 103 | request.deliverCancel(); 104 | } 105 | }); 106 | } 107 | 108 | @Override 109 | public void postPreExecute(final Request request) { 110 | request.addMarker("post-preexecute"); 111 | mResponsePoster.execute(new Runnable() { 112 | @Override 113 | public void run() { 114 | request.deliverPreExecute(); 115 | } 116 | }); 117 | } 118 | 119 | @Override 120 | public void postUsedCache(final Request request) { 121 | request.addMarker("post-usedcache"); 122 | mResponsePoster.execute(new Runnable() { 123 | @Override 124 | public void run() { 125 | request.deliverUsedCache(); 126 | } 127 | }); 128 | } 129 | 130 | @Override 131 | public void postNetworking(final Request request) { 132 | request.addMarker("post-networking"); 133 | mResponsePoster.execute(new Runnable() { 134 | @Override 135 | public void run() { 136 | request.deliverNetworking(); 137 | } 138 | }); 139 | } 140 | 141 | @Override 142 | public void postRetry(final Request request) { 143 | request.addMarker("post-retry"); 144 | mResponsePoster.execute(new Runnable() { 145 | @Override 146 | public void run() { 147 | request.deliverRetry(); 148 | } 149 | }); 150 | } 151 | 152 | @Override 153 | public void postDownloadProgress(final Request request, final long fileSize, final long downloadedSize) { 154 | request.addMarker("post-downloadprogress"); 155 | mResponsePoster.execute(new Runnable() { 156 | @Override 157 | public void run() { 158 | request.deliverDownloadProgress(fileSize, downloadedSize); 159 | } 160 | }); 161 | } 162 | 163 | /** 164 | * A Runnable used for delivering network responses to a listener on the 165 | * main thread. 166 | */ 167 | private class ResponseDeliveryRunnable implements Runnable { 168 | private final Request mRequest; 169 | private final Response mResponse; 170 | private final Runnable mRunnable; 171 | 172 | public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) { 173 | mRequest = request; 174 | mResponse = response; 175 | mRunnable = runnable; 176 | } 177 | 178 | @SuppressWarnings("unchecked") 179 | @Override 180 | public void run() { 181 | // If this request has canceled, finish it and don't deliver. 182 | if (mRequest.isCanceled()) { 183 | mRequest.finish("canceled-at-delivery"); 184 | mRequest.deliverFinish(); 185 | return; 186 | } 187 | 188 | // Deliver a normal response or error, depending. 189 | if (mResponse.isSuccess()) { 190 | mRequest.deliverSuccess(mResponse.result); 191 | } else { 192 | mRequest.deliverError(mResponse.errorDetail); 193 | } 194 | 195 | // If this is an intermediate response, add a marker, otherwise we're done 196 | // and the request can be finished. 197 | if (mResponse.intermediate) { 198 | mRequest.addMarker("intermediate-response"); 199 | } else { 200 | mRequest.finish("done"); 201 | } 202 | 203 | // If we have been provided a post-delivery runnable, run it. 204 | if (mRunnable != null) { 205 | mRunnable.run(); 206 | } 207 | 208 | mRequest.deliverFinish(); 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/HttpUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid; 17 | 18 | import android.text.TextUtils; 19 | import com.vincestyling.netroid.toolbox.ByteArrayPool; 20 | import com.vincestyling.netroid.toolbox.PoolingByteArrayOutputStream; 21 | import org.apache.http.Header; 22 | import org.apache.http.HttpEntity; 23 | import org.apache.http.HttpResponse; 24 | import org.apache.http.protocol.HTTP; 25 | 26 | import java.io.IOException; 27 | import java.io.InputStream; 28 | import java.io.UnsupportedEncodingException; 29 | import java.util.zip.GZIPInputStream; 30 | 31 | public class HttpUtils { 32 | 33 | private HttpUtils() { 34 | /* cannot be instantiated */ 35 | } 36 | 37 | /** 38 | * Reads the contents of HttpEntity into a byte[]. 39 | */ 40 | public static byte[] responseToBytes(HttpResponse response) throws IOException, ServerError { 41 | HttpEntity entity = response.getEntity(); 42 | PoolingByteArrayOutputStream bytes = 43 | new PoolingByteArrayOutputStream(ByteArrayPool.get(), (int) entity.getContentLength()); 44 | byte[] buffer = null; 45 | try { 46 | InputStream in = entity.getContent(); 47 | if (isGzipContent(response) && !(in instanceof GZIPInputStream)) { 48 | in = new GZIPInputStream(in); 49 | } 50 | 51 | if (in == null) { 52 | throw new ServerError(); 53 | } 54 | 55 | buffer = ByteArrayPool.get().getBuf(1024); 56 | int count; 57 | while ((count = in.read(buffer)) != -1) { 58 | bytes.write(buffer, 0, count); 59 | } 60 | return bytes.toByteArray(); 61 | } finally { 62 | try { 63 | // Close the InputStream and release the resources by "consuming the content". 64 | entity.consumeContent(); 65 | } catch (IOException e) { 66 | // This can happen if there was an exception above that left the entity in 67 | // an invalid state. 68 | NetroidLog.v("Error occured when calling consumingContent"); 69 | } 70 | ByteArrayPool.get().returnBuf(buffer); 71 | bytes.close(); 72 | } 73 | } 74 | 75 | /** 76 | * Returns the charset specified in the Content-Type of this header. 77 | */ 78 | public static String getCharset(HttpResponse response) { 79 | Header header = response.getFirstHeader(HTTP.CONTENT_TYPE); 80 | if (header != null) { 81 | String contentType = header.getValue(); 82 | if (!TextUtils.isEmpty(contentType)) { 83 | String[] params = contentType.split(";"); 84 | for (int i = 1; i < params.length; i++) { 85 | String[] pair = params[i].trim().split("="); 86 | if (pair.length == 2 && pair[0].equals("charset")) { 87 | return pair[1]; 88 | } 89 | } 90 | } 91 | } 92 | return null; 93 | } 94 | 95 | public static String getHeader(HttpResponse response, String key) { 96 | Header header = response.getFirstHeader(key); 97 | return header == null ? null : header.getValue(); 98 | } 99 | 100 | public static boolean isSupportRange(HttpResponse response) { 101 | if (TextUtils.equals(getHeader(response, "Accept-Ranges"), "bytes")) { 102 | return true; 103 | } 104 | String value = getHeader(response, "Content-Range"); 105 | return value != null && value.startsWith("bytes"); 106 | } 107 | 108 | public static boolean isGzipContent(HttpResponse response) { 109 | return TextUtils.equals(getHeader(response, "Content-Encoding"), "gzip"); 110 | } 111 | 112 | public static String parseResponse(NetworkResponse response) { 113 | String parsed; 114 | try { 115 | parsed = new String(response.data, response.charset); 116 | } catch (UnsupportedEncodingException e) { 117 | parsed = new String(response.data); 118 | } 119 | return parsed; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/IListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid; 17 | 18 | /** 19 | * Callback interface for delivering request status or response result. 20 | * Note : all method are calls over UI thread. 21 | * 22 | * @param Parsed type of this response. 23 | */ 24 | public interface IListener { 25 | /** 26 | * Inform when start to handle this Request. 27 | */ 28 | void onPreExecute(); 29 | 30 | /** 31 | * Inform when {@link Request} execute is finish, 32 | * whatever success or error or cancel, this callback 33 | * method always invoke if request is done. 34 | */ 35 | void onFinish(); 36 | 37 | /** 38 | * Called when response success. 39 | */ 40 | void onSuccess(T response); 41 | 42 | /** 43 | * Callback method that an error has been occurred with the 44 | * provided error code and optional user-readable message. 45 | */ 46 | void onError(NetroidError error); 47 | 48 | /** 49 | * Inform when the {@link Request} is truly cancelled. 50 | */ 51 | void onCancel(); 52 | 53 | /** 54 | * Inform When the {@link Request} cache non-exist or expired, 55 | * this callback method is opposite by the onUsedCache(), 56 | * means the http retrieving will happen soon. 57 | */ 58 | void onNetworking(); 59 | 60 | /** 61 | * Inform when the cache already use, 62 | * it means http networking won't execute. 63 | */ 64 | void onUsedCache(); 65 | 66 | /** 67 | * Inform when {@link Request} execute is going to retry. 68 | */ 69 | void onRetry(); 70 | 71 | /** 72 | * Inform when download progress change, this callback method only available 73 | * when request was {@link com.vincestyling.netroid.request.FileDownloadRequest}. 74 | */ 75 | void onProgressChange(long fileSize, long downloadedSize); 76 | } 77 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/Listener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid; 17 | 18 | public class Listener implements IListener { 19 | @Override 20 | public void onPreExecute() { 21 | } 22 | 23 | @Override 24 | public void onFinish() { 25 | } 26 | 27 | @Override 28 | public void onSuccess(T response) { 29 | } 30 | 31 | @Override 32 | public void onError(NetroidError error) { 33 | } 34 | 35 | @Override 36 | public void onCancel() { 37 | } 38 | 39 | @Override 40 | public void onNetworking() { 41 | } 42 | 43 | @Override 44 | public void onUsedCache() { 45 | } 46 | 47 | @Override 48 | public void onRetry() { 49 | } 50 | 51 | @Override 52 | public void onProgressChange(long fileSize, long downloadedSize) { 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/NetroidError.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid; 17 | 18 | /** 19 | * Exception style class encapsulating Netroid errors 20 | */ 21 | public class NetroidError extends Exception { 22 | public final NetworkResponse networkResponse; 23 | 24 | public NetroidError() { 25 | networkResponse = null; 26 | } 27 | 28 | public NetroidError(NetworkResponse response) { 29 | networkResponse = response; 30 | } 31 | 32 | public NetroidError(String exceptionMessage) { 33 | super(exceptionMessage); 34 | networkResponse = null; 35 | } 36 | 37 | public NetroidError(String exceptionMessage, Throwable reason) { 38 | super(exceptionMessage, reason); 39 | networkResponse = null; 40 | } 41 | 42 | public NetroidError(Throwable cause) { 43 | super(cause); 44 | networkResponse = null; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/NetroidLog.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid; 17 | 18 | import android.os.SystemClock; 19 | import android.util.Log; 20 | 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | import java.util.Locale; 24 | 25 | /** 26 | * Logging helper class. 27 | */ 28 | public class NetroidLog { 29 | 30 | private NetroidLog() { 31 | /* cannot be instantiated */ 32 | } 33 | 34 | public static String TAG = "Netroid"; 35 | 36 | public static boolean DEBUG = Log.isLoggable(TAG, Log.VERBOSE); 37 | 38 | /** 39 | * Customize the log tag for your application, so that other apps 40 | * using Netroid don't mix their logs with yours. 41 | *
42 | * Enable the log property for your tag before starting your app: 43 | *
44 | * {@code adb shell setprop log.tag.<tag>} 45 | */ 46 | public static void setTag(String tag) { 47 | d("Changing log tag to %s", tag); 48 | TAG = tag; 49 | 50 | // Reinitialize the DEBUG "constant" 51 | DEBUG = Log.isLoggable(TAG, Log.VERBOSE); 52 | } 53 | 54 | public static void v(String format, Object... args) { 55 | if (DEBUG) { 56 | Log.v(TAG, buildMessage(format, args)); 57 | } 58 | } 59 | 60 | public static void d(String format, Object... args) { 61 | Log.d(TAG, buildMessage(format, args)); 62 | } 63 | 64 | public static void e(String format, Object... args) { 65 | Log.e(TAG, buildMessage(format, args)); 66 | } 67 | 68 | public static void e(Throwable tr, String format, Object... args) { 69 | Log.e(TAG, buildMessage(format, args), tr); 70 | } 71 | 72 | public static void wtf(String format, Object... args) { 73 | Log.wtf(TAG, buildMessage(format, args)); 74 | } 75 | 76 | public static void wtf(Throwable tr, String format, Object... args) { 77 | Log.wtf(TAG, buildMessage(format, args), tr); 78 | } 79 | 80 | /** 81 | * Formats the caller's provided message and prepends useful info like 82 | * calling thread ID and method name. 83 | */ 84 | private static String buildMessage(String format, Object... args) { 85 | String msg = (args == null || args.length == 0) ? format : String.format(Locale.US, format, args); 86 | StackTraceElement[] trace = new Throwable().fillInStackTrace().getStackTrace(); 87 | 88 | String caller = ""; 89 | // Walk up the stack looking for the first caller outside of NetroidLog. 90 | // It will be at least two frames up, so start there. 91 | for (int i = 2; i < trace.length; i++) { 92 | Class clazz = trace[i].getClass(); 93 | if (!clazz.equals(NetroidLog.class)) { 94 | String callingClass = trace[i].getClassName(); 95 | callingClass = callingClass.substring(callingClass.lastIndexOf('.') + 1); 96 | callingClass = callingClass.substring(callingClass.lastIndexOf('$') + 1); 97 | 98 | caller = callingClass + "." + trace[i].getMethodName(); 99 | break; 100 | } 101 | } 102 | return String.format(Locale.US, "[%d] %s: %s", 103 | Thread.currentThread().getId(), caller, msg); 104 | } 105 | 106 | /** 107 | * A simple event log with records containing a name, thread ID, and timestamp. 108 | */ 109 | static class MarkerLog { 110 | public static final boolean ENABLED = NetroidLog.DEBUG; 111 | 112 | /** 113 | * Minimum duration from first marker to last in an marker log to warrant logging. 114 | */ 115 | private static final long MIN_DURATION_FOR_LOGGING_MS = 0; 116 | 117 | private static class Marker { 118 | public final String name; 119 | public final long thread; 120 | public final long time; 121 | 122 | public Marker(String name, long thread, long time) { 123 | this.name = name; 124 | this.thread = thread; 125 | this.time = time; 126 | } 127 | } 128 | 129 | private final List mMarkers = new ArrayList(); 130 | private boolean mFinished = false; 131 | 132 | /** 133 | * Adds a marker to this log with the specified name. 134 | */ 135 | public synchronized void add(String name, long threadId) { 136 | if (mFinished) { 137 | throw new IllegalStateException("Marker added to finished log"); 138 | } 139 | 140 | mMarkers.add(new Marker(name, threadId, SystemClock.elapsedRealtime())); 141 | } 142 | 143 | /** 144 | * Closes the log, dumping it to logcat if the time difference between 145 | * the first and last markers is greater than {@link #MIN_DURATION_FOR_LOGGING_MS}. 146 | * 147 | * @param header Header string to print above the marker log. 148 | */ 149 | public synchronized void finish(String header) { 150 | mFinished = true; 151 | 152 | long duration = getTotalDuration(); 153 | if (duration <= MIN_DURATION_FOR_LOGGING_MS) { 154 | return; 155 | } 156 | 157 | long prevTime = mMarkers.get(0).time; 158 | d("(%-4d ms) %s", duration, header); 159 | for (Marker marker : mMarkers) { 160 | long thisTime = marker.time; 161 | d("(+%-4d) [%2d] %s", (thisTime - prevTime), marker.thread, marker.name); 162 | prevTime = thisTime; 163 | } 164 | } 165 | 166 | @Override 167 | protected void finalize() throws Throwable { 168 | // Catch requests that have been collected (and hence end-of-lifed) 169 | // but had no debugging output printed for them. 170 | if (!mFinished) { 171 | finish("Request on the loose"); 172 | e("Marker log finalized without finish() - uncaught exit point for request"); 173 | } 174 | } 175 | 176 | /** 177 | * Returns the time difference between the first and last events in this log. 178 | */ 179 | private long getTotalDuration() { 180 | if (mMarkers.isEmpty()) return 0; 181 | long first = mMarkers.get(0).time; 182 | long last = mMarkers.get(mMarkers.size() - 1).time; 183 | return last - first; 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/Network.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid; 17 | 18 | /** 19 | * An interface for performing requests. 20 | */ 21 | public interface Network { 22 | 23 | /** 24 | * Set the request delivery that use to post http networking callbacks. 25 | */ 26 | void setDelivery(Delivery delivery); 27 | 28 | /** 29 | * Performs the specified request. 30 | * 31 | * @param request Request to process 32 | * @return A {@link NetworkResponse} with data and caching metadata; will never be null 33 | * @throws NetroidError on errors 34 | */ 35 | NetworkResponse performRequest(Request request) throws NetroidError; 36 | } 37 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/NetworkDispatcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid; 17 | 18 | import android.os.Process; 19 | import com.vincestyling.netroid.cache.DiskCache; 20 | 21 | import java.util.concurrent.BlockingQueue; 22 | 23 | /** 24 | * Provides a thread for performing network dispatch from a queue of requests. 25 | *

26 | * Requests added to the specified queue are processed from the network via a 27 | * specified {@link Network} interface. Responses are committed to cache, if 28 | * eligible, using a specified {@link com.vincestyling.netroid.cache.DiskCache} interface. 29 | * Valid responses and errors are posted back to the caller via a {@link Delivery}. 30 | */ 31 | public class NetworkDispatcher extends Thread { 32 | /** 33 | * The queue of requests to service. 34 | */ 35 | private final BlockingQueue mQueue; 36 | 37 | /** 38 | * The network interface for processing requests. 39 | */ 40 | private final Network mNetwork; 41 | 42 | /** 43 | * The cache to write to. 44 | */ 45 | private final DiskCache mCache; 46 | 47 | /** 48 | * For posting responses and errors. 49 | */ 50 | private final Delivery mDelivery; 51 | 52 | /** 53 | * Used for telling us to die. 54 | */ 55 | private volatile boolean mQuit = false; 56 | 57 | /** 58 | * Creates a new network dispatcher thread. You must call {@link #start()} 59 | * in order to begin processing. 60 | * 61 | * @param queue Queue of incoming requests for triage 62 | * @param network Network interface to use for performing requests 63 | * @param cache Cache interface to use for writing responses to cache 64 | * @param delivery Delivery interface to use for posting responses 65 | */ 66 | public NetworkDispatcher(BlockingQueue queue, 67 | Network network, DiskCache cache, 68 | Delivery delivery) { 69 | mQueue = queue; 70 | mCache = cache; 71 | mNetwork = network; 72 | mDelivery = delivery; 73 | } 74 | 75 | /** 76 | * Forces this dispatcher to quit immediately. If any requests are still in 77 | * the queue, they are not guaranteed to be processed. 78 | */ 79 | public void quit() { 80 | mQuit = true; 81 | interrupt(); 82 | } 83 | 84 | @Override 85 | public void run() { 86 | Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 87 | Request request; 88 | while (true) { 89 | try { 90 | // Take a request from the queue. 91 | request = mQueue.take(); 92 | } catch (InterruptedException e) { 93 | // We may have been interrupted because it was time to quit. 94 | if (mQuit) return; 95 | continue; 96 | } 97 | 98 | RequestPerformer.perform(request, mNetwork, mCache, mDelivery); 99 | } 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/NetworkError.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid; 17 | 18 | /** 19 | * Indicates that there was a network error when performing a Netroid request. 20 | */ 21 | public class NetworkError extends NetroidError { 22 | public NetworkError() { 23 | super(); 24 | } 25 | 26 | public NetworkError(Throwable cause) { 27 | super(cause); 28 | } 29 | 30 | public NetworkError(NetworkResponse networkResponse) { 31 | super(networkResponse); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/NetworkResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid; 17 | 18 | import org.apache.http.HttpStatus; 19 | 20 | /** 21 | * Data and headers returned from {@link Network#performRequest(Request)}. 22 | */ 23 | public class NetworkResponse { 24 | /** 25 | * Creates a new network response. 26 | * 27 | * @param statusCode the HTTP status code 28 | * @param data Response body 29 | * @param charset The response body charset, parse by http header 30 | */ 31 | public NetworkResponse(int statusCode, byte[] data, String charset) { 32 | this.statusCode = statusCode; 33 | this.data = data; 34 | this.charset = charset; 35 | } 36 | 37 | // public NetworkResponse(byte[] data) { 38 | // this(HttpStatus.SC_OK, data, Collections.emptyMap()); 39 | // } 40 | 41 | public NetworkResponse(byte[] data, String charset) { 42 | this(HttpStatus.SC_OK, data, charset); 43 | } 44 | 45 | /** 46 | * The HTTP status code. 47 | */ 48 | public final int statusCode; 49 | 50 | /** 51 | * Raw data from this response. 52 | */ 53 | public final byte[] data; 54 | 55 | /** 56 | * Charset from this response. 57 | */ 58 | public final String charset; 59 | } -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/NoConnectionError.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid; 17 | 18 | /** 19 | * Error indicating that no connection could be established when performing a Netroid request. 20 | */ 21 | public class NoConnectionError extends NetworkError { 22 | public NoConnectionError() { 23 | super(); 24 | } 25 | 26 | public NoConnectionError(Throwable reason) { 27 | super(reason); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/ParseError.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid; 17 | 18 | /** 19 | * Indicates that the server's response could not be parsed. 20 | */ 21 | public class ParseError extends NetroidError { 22 | public ParseError() { 23 | } 24 | 25 | public ParseError(NetworkResponse networkResponse) { 26 | super(networkResponse); 27 | } 28 | 29 | public ParseError(Throwable cause) { 30 | super(cause); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/RequestPerformer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid; 17 | 18 | import com.vincestyling.netroid.cache.DiskCache; 19 | 20 | /** 21 | * The standalone request performer, the intention of put it standalone 22 | * are enable it to invoke either main thread or non. 23 | * @see NetworkDispatcher#run() 24 | */ 25 | public final class RequestPerformer { 26 | 27 | public static void perform(Request request, Network network, DiskCache cache, Delivery delivery) { 28 | try { 29 | request.addMarker("network-queue-take"); 30 | delivery.postPreExecute(request); 31 | 32 | // If the request was cancelled already, 33 | // do not perform the network request. 34 | if (request.isCanceled()) { 35 | request.finish("network-discard-cancelled"); 36 | delivery.postCancel(request); 37 | delivery.postFinish(request); 38 | return; 39 | } 40 | 41 | // Perform the network request. 42 | NetworkResponse networkResponse = network.performRequest(request); 43 | request.addMarker("network-http-complete"); 44 | 45 | // Parse the response here on the worker thread. 46 | Response response = request.parseNetworkResponse(networkResponse); 47 | request.addMarker("network-parse-complete"); 48 | 49 | // Write to cache if applicable. 50 | if (cache != null && request.shouldCache() && response.cacheEntry != null) { 51 | response.cacheEntry.setExpireTime(request.getCacheExpireTime()); 52 | cache.putEntry(request.getCacheKey(), response.cacheEntry); 53 | request.addMarker("network-cache-written"); 54 | } 55 | 56 | // Post the response back. 57 | request.markDelivered(); 58 | delivery.postResponse(request, response); 59 | } catch (NetroidError netroidError) { 60 | delivery.postError(request, request.parseNetworkError(netroidError)); 61 | } catch (Exception e) { 62 | NetroidLog.e(e, "Unhandled exception %s", e.toString()); 63 | delivery.postError(request, new NetroidError(e)); 64 | } 65 | } 66 | 67 | public static void perform(Request request, Network network, Delivery delivery) { 68 | perform(request, network, null, delivery); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/Response.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid; 17 | 18 | import com.vincestyling.netroid.cache.DiskCache; 19 | 20 | /** 21 | * Encapsulates a parsed response for delivery. 22 | * 23 | * @param Parsed type of this response 24 | */ 25 | public class Response { 26 | 27 | /** 28 | * Returns a successful response containing the parsed result. 29 | */ 30 | public static Response success(T result, NetworkResponse response) { 31 | return new Response<>(result, response); 32 | } 33 | 34 | /** 35 | * Returns a failed response containing the given error code and an optional 36 | * localized message displayed to the user. 37 | */ 38 | public static Response error(NetroidError error) { 39 | return new Response<>(error); 40 | } 41 | 42 | /** 43 | * Parsed response, or null in the case of error. 44 | */ 45 | public final T result; 46 | 47 | /** 48 | * Cache metadata for this response, or null in the case of error. 49 | */ 50 | public final DiskCache.Entry cacheEntry; 51 | 52 | /** 53 | * Detailed error information if errorCode != OK. 54 | */ 55 | public final NetroidError errorDetail; 56 | 57 | /** 58 | * True if this response was a soft-expired one and a second one MAY be coming. 59 | */ 60 | public boolean intermediate = false; 61 | 62 | /** 63 | * Returns whether this response is considered successful. 64 | */ 65 | public boolean isSuccess() { 66 | return errorDetail == null; 67 | } 68 | 69 | private Response(T result, NetworkResponse response) { 70 | this.result = result; 71 | cacheEntry = response != null ? new DiskCache.Entry(response.data, response.charset) : null; 72 | this.errorDetail = null; 73 | } 74 | 75 | private Response(NetroidError error) { 76 | this.result = null; 77 | this.cacheEntry = null; 78 | this.errorDetail = error; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/RetryPolicy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid; 17 | 18 | /** 19 | * Retry policy for a request. 20 | */ 21 | public interface RetryPolicy { 22 | 23 | /** 24 | * Returns the current timeout (used for logging). 25 | */ 26 | int getCurrentTimeout(); 27 | 28 | /** 29 | * Returns the current retry count (used for logging). 30 | */ 31 | int getCurrentRetryCount(); 32 | 33 | /** 34 | * Prepares for the next retry by applying a backoff to the timeout. 35 | * 36 | * @param error The error code of the last attempt. 37 | * @throws NetroidError In the event that the retry could not be performed (for example if we 38 | * ran out of attempts), the passed in error is thrown. 39 | */ 40 | void retry(NetroidError error) throws NetroidError; 41 | } 42 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/ServerError.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid; 17 | 18 | /** 19 | * Indicates that the error responded with an error response. 20 | */ 21 | public class ServerError extends NetroidError { 22 | public ServerError(NetworkResponse networkResponse) { 23 | super(networkResponse); 24 | } 25 | 26 | public ServerError() { 27 | super(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/TimeoutError.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid; 17 | 18 | /** 19 | * Indicates that the connection or the socket timed out. 20 | */ 21 | public class TimeoutError extends NetroidError { 22 | } 23 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/cache/BitmapImageCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid.cache; 17 | 18 | import android.graphics.Bitmap; 19 | import com.vincestyling.netroid.toolbox.ImageLoader; 20 | 21 | public class BitmapImageCache extends LruCache implements ImageLoader.ImageCache { 22 | public BitmapImageCache(int maxSize) { 23 | super(maxSize); 24 | } 25 | 26 | @Override 27 | protected int sizeOf(String key, Bitmap value) { 28 | return value.getRowBytes() * value.getHeight(); 29 | } 30 | 31 | @Override 32 | public Bitmap getBitmap(String url) { 33 | return get(url); 34 | } 35 | 36 | @Override 37 | public void putBitmap(String url, Bitmap bitmap) { 38 | put(url, bitmap); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/request/JsonArrayRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid.request; 17 | 18 | import com.vincestyling.netroid.IListener; 19 | import com.vincestyling.netroid.NetworkResponse; 20 | import com.vincestyling.netroid.ParseError; 21 | import com.vincestyling.netroid.Response; 22 | import org.json.JSONArray; 23 | import org.json.JSONException; 24 | 25 | import java.io.UnsupportedEncodingException; 26 | 27 | /** 28 | * A request for retrieving a {@link JSONArray} response body at a given URL. 29 | */ 30 | public class JsonArrayRequest extends JsonRequest { 31 | /** 32 | * Creates a new request. 33 | * 34 | * @param url URL to fetch the JSON from 35 | * @param listener Listener to receive the JSON response or error message 36 | */ 37 | public JsonArrayRequest(String url, IListener listener) { 38 | super(Method.GET, url, null, listener); 39 | } 40 | 41 | @Override 42 | protected Response parseNetworkResponse(NetworkResponse response) { 43 | try { 44 | String jsonString = new String(response.data, response.charset); 45 | return Response.success(new JSONArray(jsonString), response); 46 | } catch (UnsupportedEncodingException e) { 47 | return Response.error(new ParseError(e)); 48 | } catch (JSONException je) { 49 | return Response.error(new ParseError(je)); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/request/JsonObjectRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid.request; 17 | 18 | import com.vincestyling.netroid.IListener; 19 | import com.vincestyling.netroid.NetworkResponse; 20 | import com.vincestyling.netroid.ParseError; 21 | import com.vincestyling.netroid.Response; 22 | import org.json.JSONException; 23 | import org.json.JSONObject; 24 | 25 | import java.io.UnsupportedEncodingException; 26 | 27 | /** 28 | * A request for retrieving a {@link JSONObject} response body at a given URL, allowing for an 29 | * optional {@link JSONObject} to be passed in as part of the request body. 30 | */ 31 | public class JsonObjectRequest extends JsonRequest { 32 | /** 33 | * Creates a new request. 34 | * 35 | * @param method the HTTP method to use 36 | * @param url URL to fetch the JSON from 37 | * @param jsonRequest A {@link JSONObject} to post with the request. Null is allowed and 38 | * indicates no parameters will be posted along with request. 39 | * @param listener Listener to receive the JSON response or error message 40 | */ 41 | public JsonObjectRequest(int method, String url, JSONObject jsonRequest, IListener listener) { 42 | super(method, url, (jsonRequest == null) ? null : jsonRequest.toString(), listener); 43 | } 44 | 45 | /** 46 | * Constructor which defaults to GET if jsonRequest is 47 | * null, POST otherwise. 48 | * 49 | * @see #JsonObjectRequest(int, String, JSONObject, IListener) 50 | */ 51 | public JsonObjectRequest(String url, JSONObject jsonRequest, IListener listener) { 52 | this(jsonRequest == null ? Method.GET : Method.POST, url, jsonRequest, listener); 53 | } 54 | 55 | @Override 56 | protected Response parseNetworkResponse(NetworkResponse response) { 57 | try { 58 | String jsonString = new String(response.data, response.charset); 59 | return Response.success(new JSONObject(jsonString), response); 60 | } catch (UnsupportedEncodingException e) { 61 | return Response.error(new ParseError(e)); 62 | } catch (JSONException je) { 63 | return Response.error(new ParseError(je)); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/request/JsonRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid.request; 17 | 18 | import com.vincestyling.netroid.*; 19 | 20 | import java.io.UnsupportedEncodingException; 21 | 22 | /** 23 | * A request for retrieving a T type response body at a given URL that also 24 | * optionally sends along a JSON body in the request specified. 25 | * 26 | * @param JSON type of response expected 27 | */ 28 | public abstract class JsonRequest extends Request { 29 | /** 30 | * Charset for request. 31 | */ 32 | private static final String PROTOCOL_CHARSET = "utf-8"; 33 | 34 | /** 35 | * Content type for request. 36 | */ 37 | private static final String PROTOCOL_CONTENT_TYPE = 38 | String.format("application/json; charset=%s", PROTOCOL_CHARSET); 39 | 40 | private final String mRequestBody; 41 | 42 | public JsonRequest(int method, String url, String requestBody, IListener listener) { 43 | super(method, url, listener); 44 | mRequestBody = requestBody; 45 | } 46 | 47 | @Override 48 | abstract protected Response parseNetworkResponse(NetworkResponse response); 49 | 50 | @Override 51 | public String getBodyContentType() { 52 | return PROTOCOL_CONTENT_TYPE; 53 | } 54 | 55 | @Override 56 | public byte[] getBody() { 57 | try { 58 | return mRequestBody == null ? null : mRequestBody.getBytes(PROTOCOL_CHARSET); 59 | } catch (UnsupportedEncodingException uee) { 60 | NetroidLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s", 61 | mRequestBody, PROTOCOL_CHARSET); 62 | return null; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/request/StringRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid.request; 17 | 18 | import com.vincestyling.netroid.*; 19 | 20 | /** 21 | * A canned request for retrieving the response body at a given URL as a String. 22 | */ 23 | public class StringRequest extends Request { 24 | /** 25 | * Creates a new request with the given method. 26 | * 27 | * @param method the request {@link Method} to use 28 | * @param url URL to fetch the string at 29 | * @param listener Listener to receive the String response or error message 30 | */ 31 | public StringRequest(int method, String url, IListener listener) { 32 | super(method, url, listener); 33 | } 34 | 35 | /** 36 | * Creates a new GET request. 37 | * 38 | * @param url URL to fetch the string at 39 | * @param listener Listener to receive the String response 40 | */ 41 | public StringRequest(String url, IListener listener) { 42 | this(Method.GET, url, listener); 43 | } 44 | 45 | @Override 46 | protected Response parseNetworkResponse(NetworkResponse response) { 47 | String parsed = HttpUtils.parseResponse(response); 48 | return Response.success(parsed, response); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/stack/HttpClientStack.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid.stack; 17 | 18 | import android.net.http.AndroidHttpClient; 19 | import com.vincestyling.netroid.AuthFailureError; 20 | import com.vincestyling.netroid.Request; 21 | import com.vincestyling.netroid.Request.Method; 22 | import org.apache.http.HttpEntity; 23 | import org.apache.http.HttpResponse; 24 | import org.apache.http.client.HttpClient; 25 | import org.apache.http.client.methods.*; 26 | import org.apache.http.entity.ByteArrayEntity; 27 | import org.apache.http.params.HttpConnectionParams; 28 | import org.apache.http.params.HttpParams; 29 | import org.apache.http.protocol.HTTP; 30 | 31 | import java.io.IOException; 32 | import java.net.URI; 33 | import java.util.Map; 34 | import java.util.Map.Entry; 35 | 36 | /** 37 | * An HttpStack that performs request over an {@link HttpClient}. 38 | */ 39 | public class HttpClientStack implements HttpStack { 40 | protected final HttpClient mClient; 41 | 42 | public HttpClientStack(String userAgent) { 43 | mClient = AndroidHttpClient.newInstance(userAgent); 44 | } 45 | 46 | public HttpClientStack(HttpClient client) { 47 | mClient = client; 48 | } 49 | 50 | private static void addHeaders(HttpUriRequest httpRequest, Map headers) { 51 | for (Entry header : headers.entrySet()) { 52 | httpRequest.setHeader(header.getKey(), header.getValue()); 53 | } 54 | } 55 | 56 | // private static List getPostParameterPairs(Map postParams) { 57 | // List result = new ArrayList<>(postParams.size()); 58 | // for (Entry param : postParams.entrySet()) { 59 | // result.add(new BasicNameValuePair(param.getKey(), param.getValue())); 60 | // } 61 | // return result; 62 | // } 63 | 64 | @Override 65 | public HttpResponse performRequest(Request request) throws IOException, AuthFailureError { 66 | HttpUriRequest httpRequest = createHttpRequest(request); 67 | onPrepareRequest(httpRequest); 68 | addHeaders(httpRequest, request.getHeaders()); 69 | HttpParams httpParams = httpRequest.getParams(); 70 | int timeoutMs = request.getTimeoutMs(); 71 | // TODO: Reevaluate this connection timeout based on more wide-scale 72 | // data collection and possibly different for wifi vs. 3G. 73 | HttpConnectionParams.setConnectionTimeout(httpParams, 5000); 74 | HttpConnectionParams.setSoTimeout(httpParams, timeoutMs); 75 | return mClient.execute(httpRequest); 76 | } 77 | 78 | /** 79 | * Creates the appropriate subclass of HttpUriRequest for passed in request. 80 | */ 81 | private static HttpUriRequest createHttpRequest(Request request) throws AuthFailureError { 82 | switch (request.getMethod()) { 83 | case Method.GET: 84 | return new HttpGet(request.getUrl()); 85 | case Method.DELETE: 86 | return new HttpDelete(request.getUrl()); 87 | case Method.POST: { 88 | HttpPost postRequest = new HttpPost(request.getUrl()); 89 | postRequest.addHeader(HTTP.CONTENT_TYPE, request.getBodyContentType()); 90 | setEntityIfNonEmptyBody(postRequest, request); 91 | return postRequest; 92 | } 93 | case Method.PUT: { 94 | HttpPut putRequest = new HttpPut(request.getUrl()); 95 | putRequest.addHeader(HTTP.CONTENT_TYPE, request.getBodyContentType()); 96 | setEntityIfNonEmptyBody(putRequest, request); 97 | return putRequest; 98 | } 99 | case Method.HEAD: 100 | return new HttpHead(request.getUrl()); 101 | case Method.OPTIONS: 102 | return new HttpOptions(request.getUrl()); 103 | case Method.TRACE: 104 | return new HttpTrace(request.getUrl()); 105 | case Method.PATCH: { 106 | HttpPatch patchRequest = new HttpPatch(request.getUrl()); 107 | patchRequest.addHeader(HTTP.CONTENT_TYPE, request.getBodyContentType()); 108 | setEntityIfNonEmptyBody(patchRequest, request); 109 | return patchRequest; 110 | } 111 | default: 112 | throw new IllegalStateException("Unknown request method."); 113 | } 114 | } 115 | 116 | private static void setEntityIfNonEmptyBody(HttpEntityEnclosingRequestBase httpRequest, 117 | Request request) throws AuthFailureError { 118 | byte[] body = request.getBody(); 119 | if (body != null) { 120 | HttpEntity entity = new ByteArrayEntity(body); 121 | httpRequest.setEntity(entity); 122 | } 123 | } 124 | 125 | /** 126 | * Called before the request is executed using the underlying HttpClient. 127 | *

128 | *

Overwrite in subclasses to augment the request.

129 | */ 130 | protected void onPrepareRequest(HttpUriRequest request) throws IOException { 131 | request.addHeader("Accept-Encoding", "gzip"); 132 | } 133 | 134 | /** 135 | * The HttpPatch class does not exist in the Android framework, so this has been defined here. 136 | */ 137 | public static final class HttpPatch extends HttpEntityEnclosingRequestBase { 138 | public final static String METHOD_NAME = "PATCH"; 139 | 140 | public HttpPatch() { 141 | super(); 142 | } 143 | 144 | public HttpPatch(final URI uri) { 145 | super(); 146 | setURI(uri); 147 | } 148 | 149 | /** 150 | * @throws IllegalArgumentException if the uri is invalid. 151 | */ 152 | public HttpPatch(final String uri) { 153 | super(); 154 | setURI(URI.create(uri)); 155 | } 156 | 157 | @Override 158 | public String getMethod() { 159 | return METHOD_NAME; 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/stack/HttpStack.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid.stack; 17 | 18 | import com.vincestyling.netroid.AuthFailureError; 19 | import com.vincestyling.netroid.Request; 20 | import org.apache.http.HttpResponse; 21 | 22 | import java.io.IOException; 23 | 24 | /** 25 | * An HTTP stack abstraction. 26 | */ 27 | public interface HttpStack { 28 | /** 29 | * Performs an HTTP request with the given parameters. 30 | *

31 | *

A GET request is sent if request.getPostBody() == null. A POST request is sent otherwise, 32 | * and the Content-Type header is set to request.getPostBodyContentType().

33 | * 34 | * @param request the request to perform 35 | * @return the HTTP response 36 | */ 37 | HttpResponse performRequest(Request request) 38 | throws IOException, AuthFailureError; 39 | } 40 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/toolbox/AndroidAuthenticator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid.toolbox; 17 | 18 | import android.accounts.Account; 19 | import android.accounts.AccountManager; 20 | import android.accounts.AccountManagerFuture; 21 | import android.content.Context; 22 | import android.content.Intent; 23 | import android.os.Bundle; 24 | import com.vincestyling.netroid.AuthFailureError; 25 | 26 | /** 27 | * An Authenticator that uses {@link AccountManager} to get auth 28 | * tokens of a specified type for a specified account. 29 | */ 30 | public class AndroidAuthenticator implements Authenticator { 31 | private final Context mContext; 32 | private final Account mAccount; 33 | private final String mAuthTokenType; 34 | private final boolean mNotifyAuthFailure; 35 | 36 | /** 37 | * Creates a new authenticator. 38 | * 39 | * @param context Context for accessing AccountManager 40 | * @param account Account to authenticate as 41 | * @param authTokenType Auth token type passed to AccountManager 42 | */ 43 | public AndroidAuthenticator(Context context, Account account, String authTokenType) { 44 | this(context, account, authTokenType, false); 45 | } 46 | 47 | /** 48 | * Creates a new authenticator. 49 | * 50 | * @param context Context for accessing AccountManager 51 | * @param account Account to authenticate as 52 | * @param authTokenType Auth token type passed to AccountManager 53 | * @param notifyAuthFailure Whether to raise a notification upon auth failure 54 | */ 55 | public AndroidAuthenticator(Context context, Account account, String authTokenType, 56 | boolean notifyAuthFailure) { 57 | mContext = context; 58 | mAccount = account; 59 | mAuthTokenType = authTokenType; 60 | mNotifyAuthFailure = notifyAuthFailure; 61 | } 62 | 63 | /** 64 | * Returns the Account being used by this authenticator. 65 | */ 66 | public Account getAccount() { 67 | return mAccount; 68 | } 69 | 70 | /** 71 | * Returns the Auth Token Type used by this authenticator. 72 | */ 73 | public String getAuthTokenType() { 74 | return mAuthTokenType; 75 | } 76 | 77 | @Override 78 | public String getAuthToken() throws AuthFailureError { 79 | final AccountManager accountManager = AccountManager.get(mContext); 80 | AccountManagerFuture future = accountManager.getAuthToken(mAccount, 81 | mAuthTokenType, mNotifyAuthFailure, null, null); 82 | Bundle result; 83 | try { 84 | result = future.getResult(); 85 | } catch (Exception e) { 86 | throw new AuthFailureError("Error while retrieving auth token", e); 87 | } 88 | String authToken = null; 89 | if (future.isDone() && !future.isCancelled()) { 90 | if (result.containsKey(AccountManager.KEY_INTENT)) { 91 | Intent intent = result.getParcelable(AccountManager.KEY_INTENT); 92 | throw new AuthFailureError(intent); 93 | } 94 | authToken = result.getString(AccountManager.KEY_AUTHTOKEN); 95 | } 96 | if (authToken == null) { 97 | throw new AuthFailureError("Got null auth token for type: " + mAuthTokenType); 98 | } 99 | 100 | return authToken; 101 | } 102 | 103 | @Override 104 | public void invalidateAuthToken(String authToken) { 105 | AccountManager.get(mContext).invalidateAuthToken(mAccount.type, authToken); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/toolbox/Authenticator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid.toolbox; 17 | 18 | import com.vincestyling.netroid.AuthFailureError; 19 | 20 | /** 21 | * An interface for interacting with auth tokens. 22 | */ 23 | public interface Authenticator { 24 | /** 25 | * Synchronously retrieves an auth token. 26 | * 27 | * @throws AuthFailureError If authentication did not succeed 28 | */ 29 | String getAuthToken() throws AuthFailureError; 30 | 31 | /** 32 | * Invalidates the provided auth token. 33 | */ 34 | void invalidateAuthToken(String authToken); 35 | } 36 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/toolbox/ByteArrayPool.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid.toolbox; 17 | 18 | import java.util.*; 19 | 20 | /** 21 | * ByteArrayPool is a source and repository of byte[] objects. Its purpose is to 22 | * supply those buffers to consumers who need to use them for a short period of time and then 23 | * dispose of them. Simply creating and disposing such buffers in the conventional manner can 24 | * considerable heap churn and garbage collection delays on Android, which lacks good management of 25 | * short-lived heap objects. It may be advantageous to trade off some memory in the form of a 26 | * permanently allocated pool of buffers in order to gain heap performance improvements; that is 27 | * what this class does. 28 | *

29 | * A good candidate user for this class is something like an I/O system that uses large temporary 30 | * byte[] buffers to copy data around. In these use cases, often the consumer wants 31 | * the buffer to be a certain minimum size to ensure good performance (e.g. when copying data chunks 32 | * off of a stream), but doesn't mind if the buffer is larger than the minimum. Taking this into 33 | * account and also to maximize the odds of being able to reuse a recycled buffer, this class is 34 | * free to return buffers larger than the requested size. The caller needs to be able to gracefully 35 | * deal with getting buffers any size over the minimum. 36 | *

37 | * If there is not a suitably-sized buffer in its recycling pool when a buffer is requested, this 38 | * class will allocate a new buffer and return it. 39 | *

40 | * This class has no special ownership of buffers it creates; the caller is free to take a buffer 41 | * it receives from this pool, use it permanently, and never return it to the pool; additionally, 42 | * it is not harmful to return to this pool a buffer that was allocated elsewhere, provided there 43 | * are no other lingering references to it. 44 | *

45 | * This class ensures that the total size of the buffers in its recycling pool never exceeds a 46 | * certain byte limit. When a buffer is returned that would cause the pool to exceed the limit, 47 | * least-recently-used buffers are disposed. 48 | */ 49 | public class ByteArrayPool { 50 | /** 51 | * The buffer pool, arranged both by last use and by buffer size 52 | */ 53 | private List mBuffersByLastUse = new LinkedList(); 54 | private List mBuffersBySize = new ArrayList(64); 55 | 56 | /** 57 | * The total size of the buffers in the pool 58 | */ 59 | private int mCurrentSize = 0; 60 | 61 | /** 62 | * The maximum aggregate size of the buffers in the pool. Old buffers are discarded to stay 63 | * under this limit. 64 | */ 65 | private final int mSizeLimit; 66 | 67 | /** 68 | * Compares buffers by size 69 | */ 70 | protected static final Comparator BUF_COMPARATOR = new Comparator() { 71 | @Override 72 | public int compare(byte[] lhs, byte[] rhs) { 73 | return lhs.length - rhs.length; 74 | } 75 | }; 76 | 77 | /** 78 | * @param sizeLimit the maximum size of the pool, in bytes 79 | */ 80 | private ByteArrayPool(int sizeLimit) { 81 | mSizeLimit = sizeLimit; 82 | } 83 | 84 | /** 85 | * Singleton for this class. 86 | */ 87 | private static ByteArrayPool mPool; 88 | 89 | /** 90 | * Get the singleton instance. 91 | */ 92 | public static ByteArrayPool get() { 93 | return mPool; 94 | } 95 | 96 | /** 97 | * Init and persisting the singleton instance. 98 | */ 99 | public static void init(int poolSize) { 100 | mPool = new ByteArrayPool(poolSize); 101 | } 102 | 103 | /** 104 | * Returns a buffer from the pool if one is available in the requested size, or allocates a new 105 | * one if a pooled one is not available. 106 | * 107 | * @param len the minimum size, in bytes, of the requested buffer. The returned buffer may be 108 | * larger. 109 | * @return a byte[] buffer is always returned. 110 | */ 111 | public synchronized byte[] getBuf(int len) { 112 | for (int i = 0; i < mBuffersBySize.size(); i++) { 113 | byte[] buf = mBuffersBySize.get(i); 114 | if (buf.length >= len) { 115 | mCurrentSize -= buf.length; 116 | mBuffersBySize.remove(i); 117 | mBuffersByLastUse.remove(buf); 118 | return buf; 119 | } 120 | } 121 | return new byte[len]; 122 | } 123 | 124 | /** 125 | * Returns a buffer to the pool, throwing away old buffers if the pool would exceed its allotted 126 | * size. 127 | * 128 | * @param buf the buffer to return to the pool. 129 | */ 130 | public synchronized void returnBuf(byte[] buf) { 131 | if (buf == null || buf.length > mSizeLimit) { 132 | return; 133 | } 134 | mBuffersByLastUse.add(buf); 135 | int pos = Collections.binarySearch(mBuffersBySize, buf, BUF_COMPARATOR); 136 | if (pos < 0) { 137 | pos = -pos - 1; 138 | } 139 | mBuffersBySize.add(pos, buf); 140 | mCurrentSize += buf.length; 141 | trim(); 142 | } 143 | 144 | /** 145 | * Removes buffers from the pool until it is under its size limit. 146 | */ 147 | private synchronized void trim() { 148 | while (mCurrentSize > mSizeLimit) { 149 | byte[] buf = mBuffersByLastUse.remove(0); 150 | mBuffersBySize.remove(buf); 151 | mCurrentSize -= buf.length; 152 | } 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/toolbox/PoolingByteArrayOutputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid.toolbox; 17 | 18 | import java.io.ByteArrayOutputStream; 19 | import java.io.IOException; 20 | 21 | /** 22 | * A variation of {@link java.io.ByteArrayOutputStream} that uses a pool of byte[] buffers instead 23 | * of always allocating them fresh, saving on heap churn. 24 | */ 25 | public class PoolingByteArrayOutputStream extends ByteArrayOutputStream { 26 | /** 27 | * If the {@link #PoolingByteArrayOutputStream(ByteArrayPool)} constructor is called, this is 28 | * the default size to which the underlying byte array is initialized. 29 | */ 30 | private static final int DEFAULT_SIZE = 256; 31 | 32 | private final ByteArrayPool mPool; 33 | 34 | /** 35 | * Constructs a new PoolingByteArrayOutputStream with a default size. If more bytes are written 36 | * to this instance, the underlying byte array will expand. 37 | */ 38 | public PoolingByteArrayOutputStream(ByteArrayPool pool) { 39 | this(pool, DEFAULT_SIZE); 40 | } 41 | 42 | /** 43 | * Constructs a new {@code ByteArrayOutputStream} with a default size of {@code size} bytes. If 44 | * more than {@code size} bytes are written to this instance, the underlying byte array will 45 | * expand. 46 | * 47 | * @param size initial size for the underlying byte array. The value will be pinned to a default 48 | * minimum size. 49 | */ 50 | public PoolingByteArrayOutputStream(ByteArrayPool pool, int size) { 51 | mPool = pool; 52 | buf = mPool.getBuf(Math.max(size, DEFAULT_SIZE)); 53 | } 54 | 55 | @Override 56 | public void close() throws IOException { 57 | mPool.returnBuf(buf); 58 | buf = null; 59 | super.close(); 60 | } 61 | 62 | @Override 63 | public void finalize() { 64 | mPool.returnBuf(buf); 65 | } 66 | 67 | /** 68 | * Ensures there is enough space in the buffer for the given number of additional bytes. 69 | */ 70 | private void expand(int i) { 71 | /* Can the buffer handle @i more bytes, if not expand it */ 72 | if (count + i <= buf.length) { 73 | return; 74 | } 75 | byte[] newbuf = mPool.getBuf((count + i) * 2); 76 | System.arraycopy(buf, 0, newbuf, 0, count); 77 | mPool.returnBuf(buf); 78 | buf = newbuf; 79 | } 80 | 81 | @Override 82 | public synchronized void write(byte[] buffer, int offset, int len) { 83 | expand(len); 84 | super.write(buffer, offset, len); 85 | } 86 | 87 | @Override 88 | public synchronized void write(int oneByte) { 89 | expand(1); 90 | super.write(oneByte); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /library/src/main/java/com/vincestyling/netroid/widget/NetworkImageView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Vince Styling 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.vincestyling.netroid.widget; 17 | 18 | import android.content.Context; 19 | import android.text.TextUtils; 20 | import android.util.AttributeSet; 21 | import android.view.ViewGroup.LayoutParams; 22 | import android.widget.ImageView; 23 | import com.vincestyling.netroid.NetroidError; 24 | import com.vincestyling.netroid.toolbox.ImageLoader; 25 | import com.vincestyling.netroid.toolbox.ImageLoader.ImageContainer; 26 | import com.vincestyling.netroid.toolbox.ImageLoader.ImageListener; 27 | 28 | /** 29 | * Handles fetching an image from a URL as well as the life-cycle of the 30 | * associated request. 31 | */ 32 | public class NetworkImageView extends ImageView { 33 | /** 34 | * The URL of the network image to load 35 | */ 36 | private String mUrl; 37 | 38 | /** 39 | * Resource ID of the image to be used as a placeholder until the network image is loaded. 40 | */ 41 | private int mDefaultImageId; 42 | 43 | /** 44 | * Resource ID of the image to be used if the network response fails. 45 | */ 46 | private int mErrorImageId; 47 | 48 | /** 49 | * Local copy of the ImageLoader. 50 | */ 51 | private ImageLoader mImageLoader; 52 | 53 | /** 54 | * Current ImageContainer. (either in-flight or finished) 55 | */ 56 | private ImageContainer mImageContainer; 57 | 58 | public NetworkImageView(Context context) { 59 | this(context, null); 60 | } 61 | 62 | public NetworkImageView(Context context, AttributeSet attrs) { 63 | this(context, attrs, 0); 64 | } 65 | 66 | public NetworkImageView(Context context, AttributeSet attrs, int defStyle) { 67 | super(context, attrs, defStyle); 68 | } 69 | 70 | /** 71 | * Sets URL of the image that should be loaded into this view. Note that calling this will 72 | * immediately either set the cached image (if available) or the default image specified by 73 | * {@link NetworkImageView#setDefaultImageResId(int)} on the view. 74 | *

75 | * NOTE: If applicable, {@link NetworkImageView#setDefaultImageResId(int)} and 76 | * {@link NetworkImageView#setErrorImageResId(int)} should be called prior to calling 77 | * this function. 78 | * 79 | * @param url The URL that should be loaded into this ImageView. 80 | * @param imageLoader ImageLoader that will be used to make the request. 81 | */ 82 | public void setImageUrl(String url, ImageLoader imageLoader) { 83 | mUrl = url; 84 | mImageLoader = imageLoader; 85 | // The URL has potentially changed. See if we need to load it. 86 | loadImageIfNecessary(false); 87 | } 88 | 89 | /** 90 | * Sets the default image resource ID to be used for this view until the attempt to load it 91 | * completes. 92 | */ 93 | public void setDefaultImageResId(int defaultImage) { 94 | mDefaultImageId = defaultImage; 95 | } 96 | 97 | /** 98 | * Sets the error image resource ID to be used for this view in the event that the image 99 | * requested fails to load. 100 | */ 101 | public void setErrorImageResId(int errorImage) { 102 | mErrorImageId = errorImage; 103 | } 104 | 105 | /** 106 | * Loads the image for the view if it isn't already loaded. 107 | * 108 | * @param isInLayoutPass True if this was invoked from a layout pass, false otherwise. 109 | */ 110 | private void loadImageIfNecessary(final boolean isInLayoutPass) { 111 | int width = getWidth(); 112 | int height = getHeight(); 113 | 114 | boolean wrapWidth = false, wrapHeight = false; 115 | if (getLayoutParams() != null) { 116 | wrapWidth = getLayoutParams().width == LayoutParams.WRAP_CONTENT; 117 | wrapHeight = getLayoutParams().height == LayoutParams.WRAP_CONTENT; 118 | } 119 | 120 | // if the view's bounds aren't known yet, and this is not a wrap-content/wrap-content 121 | // view, hold off on loading the image. 122 | boolean isFullyWrapContent = wrapWidth && wrapHeight; 123 | if (width == 0 && height == 0 && !isFullyWrapContent) { 124 | return; 125 | } 126 | 127 | // if the URL to be loaded in this view is empty, cancel any old requests and clear the 128 | // currently loaded image. 129 | if (TextUtils.isEmpty(mUrl)) { 130 | if (mImageContainer != null) { 131 | mImageContainer.cancelRequest(); 132 | mImageContainer = null; 133 | } 134 | setDefaultImageOrNull(); 135 | return; 136 | } 137 | 138 | // if there was an old request in this view, check if it needs to be canceled. 139 | if (mImageContainer != null && mImageContainer.getRequestUrl() != null) { 140 | if (mImageContainer.getRequestUrl().equals(mUrl)) { 141 | // if the request is from the same URL, return. 142 | return; 143 | } else { 144 | // if there is a pre-existing request, cancel it if it's fetching a different URL. 145 | mImageContainer.cancelRequest(); 146 | } 147 | } 148 | 149 | // Calculate the max image width / height to use while ignoring WRAP_CONTENT dimens. 150 | int maxWidth = wrapWidth ? 0 : width; 151 | int maxHeight = wrapHeight ? 0 : height; 152 | 153 | // The pre-existing content of this view didn't match the current URL. 154 | // Load the new image from the network. 155 | mImageContainer = mImageLoader.get(mUrl, 156 | new ImageListener() { 157 | @Override 158 | public void onError(NetroidError error) { 159 | if (mErrorImageId != 0) { 160 | setImageResource(mErrorImageId); 161 | } 162 | } 163 | 164 | @Override 165 | public void onSuccess(final ImageContainer response, boolean isImmediate) { 166 | // If this was an immediate response that was delivered inside of a layout 167 | // pass do not set the image immediately as it will trigger a requestLayout 168 | // inside of a layout. Instead, defer setting the image by posting back to 169 | // the main thread. 170 | if (isImmediate && isInLayoutPass) { 171 | post(new Runnable() { 172 | @Override 173 | public void run() { 174 | onSuccess(response, false); 175 | } 176 | }); 177 | return; 178 | } 179 | 180 | if (response.getBitmap() != null) { 181 | setImageBitmap(response.getBitmap()); 182 | } else { 183 | setDefaultImageOrNull(); 184 | } 185 | } 186 | }, maxWidth, maxHeight); 187 | } 188 | 189 | private void setDefaultImageOrNull() { 190 | if (mDefaultImageId != 0) { 191 | setImageResource(mDefaultImageId); 192 | } else { 193 | setImageBitmap(null); 194 | } 195 | } 196 | 197 | @Override 198 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 199 | super.onLayout(changed, left, top, right, bottom); 200 | loadImageIfNecessary(true); 201 | } 202 | 203 | @Override 204 | protected void onDetachedFromWindow() { 205 | if (mImageContainer != null) { 206 | // If the view was bound to an image request, cancel it and clear 207 | // out the image from the view. 208 | mImageContainer.cancelRequest(); 209 | setImageBitmap(null); 210 | // also clear out the container so we can reload the image if necessary. 211 | mImageContainer = null; 212 | } 213 | super.onDetachedFromWindow(); 214 | } 215 | 216 | @Override 217 | protected void drawableStateChanged() { 218 | super.drawableStateChanged(); 219 | invalidate(); 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 22 5 | buildToolsVersion "22.0.1" 6 | 7 | defaultConfig { 8 | applicationId "com.vincestyling.netroid.sample" 9 | minSdkVersion 9 10 | targetSdkVersion 22 11 | versionCode 1 12 | versionName "1.2.0" 13 | } 14 | 15 | compileOptions { 16 | sourceCompatibility JavaVersion.VERSION_1_6 17 | targetCompatibility JavaVersion.VERSION_1_6 18 | } 19 | 20 | signingConfigs { 21 | releaseConfig { 22 | storeFile file('debug.keystore') 23 | storePassword 'android' 24 | keyAlias 'AndroidDebugKey' 25 | keyPassword 'android' 26 | } 27 | } 28 | 29 | buildTypes { 30 | release { 31 | minifyEnabled true 32 | shrinkResources true 33 | debuggable false 34 | proguardFiles 'proguard-rules.pro' 35 | signingConfig signingConfigs.releaseConfig 36 | } 37 | } 38 | } 39 | 40 | dependencies { 41 | // compile fileTree(dir: 'libs', include: ['*.jar']) 42 | compile project(':library') 43 | } 44 | -------------------------------------------------------------------------------- /sample/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vince-styling/Netroid/7e67a08db8fecfee9d8c9ac4be625da108c4012c/sample/debug.keystore -------------------------------------------------------------------------------- /sample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/vince/dev/android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 36 | 37 | 40 | 41 | 42 | 43 | 44 | 45 | 48 | 49 | 50 | 51 | 52 | 53 | 56 | 57 | 58 | 59 | 60 | 61 | 64 | 65 | 68 | 69 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /sample/src/main/assets/cover_16539.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vince-styling/Netroid/7e67a08db8fecfee9d8c9ac4be625da108c4012c/sample/src/main/assets/cover_16539.jpg -------------------------------------------------------------------------------- /sample/src/main/java/com/vincestyling/netroid/sample/AppLog.java: -------------------------------------------------------------------------------- 1 | package com.vincestyling.netroid.sample; 2 | 3 | import android.util.Log; 4 | 5 | public class AppLog { 6 | private static final String TAG = "netroid_sample"; 7 | 8 | public static void d(String msg) { 9 | try { 10 | Log.d(TAG, msg); 11 | } catch (Exception e) { 12 | e.printStackTrace(); 13 | } 14 | } 15 | 16 | public static void d(String msg, Throwable tr) { 17 | try { 18 | Log.d(TAG, msg, tr); 19 | } catch (Exception e) { 20 | e.printStackTrace(); 21 | } 22 | } 23 | 24 | public static void i(String msg) { 25 | try { 26 | Log.i(TAG, msg); 27 | } catch (Exception e) { 28 | e.printStackTrace(); 29 | } 30 | } 31 | 32 | public static void i(String msg, Throwable tr) { 33 | try { 34 | Log.i(TAG, msg, tr); 35 | } catch (Exception e) { 36 | e.printStackTrace(); 37 | } 38 | } 39 | 40 | public static void e(String msg) { 41 | try { 42 | Log.e(TAG, msg); 43 | } catch (Exception e) { 44 | e.printStackTrace(); 45 | } 46 | } 47 | 48 | public static void e(String format, Object... args) { 49 | try { 50 | Log.e(TAG, String.format(format, args)); 51 | } catch (Exception e) { 52 | e.printStackTrace(); 53 | } 54 | } 55 | 56 | public static void e(String msg, Throwable tr) { 57 | try { 58 | Log.e(TAG, msg, tr); 59 | } catch (Exception e) { 60 | e.printStackTrace(); 61 | } 62 | } 63 | 64 | public static void e(Throwable tr) { 65 | try { 66 | Log.e(TAG, tr.getMessage(), tr); 67 | } catch (Exception e) { 68 | e.printStackTrace(); 69 | } 70 | } 71 | 72 | public static void w(String msg) { 73 | try { 74 | Log.w(TAG, msg); 75 | } catch (Exception e) { 76 | e.printStackTrace(); 77 | } 78 | } 79 | 80 | public static void w(String msg, Throwable tr) { 81 | try { 82 | Log.w(TAG, msg, tr); 83 | } catch (Exception e) { 84 | e.printStackTrace(); 85 | } 86 | } 87 | 88 | public static void v(String msg) { 89 | try { 90 | Log.v(TAG, msg); 91 | } catch (Exception e) { 92 | e.printStackTrace(); 93 | } 94 | } 95 | 96 | public static void v(String msg, Throwable tr) { 97 | try { 98 | Log.v(TAG, msg, tr); 99 | } catch (Exception e) { 100 | e.printStackTrace(); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /sample/src/main/java/com/vincestyling/netroid/sample/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.vincestyling.netroid.sample; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import com.vincestyling.netroid.sample.netroid.Netroid; 6 | 7 | public abstract class BaseActivity extends Activity { 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | initNetroid(); 12 | } 13 | 14 | // initialize netroid, this code should be invoke at Application in product stage. 15 | protected abstract void initNetroid(); 16 | 17 | @Override 18 | public void finish() { 19 | Netroid.destroy(); 20 | super.finish(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sample/src/main/java/com/vincestyling/netroid/sample/BaseListActivity.java: -------------------------------------------------------------------------------- 1 | package com.vincestyling.netroid.sample; 2 | 3 | import android.app.ListActivity; 4 | import android.graphics.Color; 5 | import android.graphics.drawable.ColorDrawable; 6 | import android.os.Bundle; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.BaseAdapter; 10 | import android.widget.TextView; 11 | import com.vincestyling.netroid.sample.mock.Book; 12 | import com.vincestyling.netroid.sample.mock.BookDataMock; 13 | import com.vincestyling.netroid.sample.netroid.Netroid; 14 | import com.vincestyling.netroid.widget.NetworkImageView; 15 | 16 | import java.util.List; 17 | 18 | public abstract class BaseListActivity extends ListActivity { 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | 23 | initNetroid(); 24 | 25 | getListView().setDivider(new ColorDrawable(Color.parseColor("#efefef"))); 26 | getListView().setFastScrollEnabled(true); 27 | getListView().setDividerHeight(1); 28 | 29 | final List bookList = BookDataMock.getData(); 30 | 31 | setListAdapter(new BaseAdapter() { 32 | @Override 33 | public int getCount() { 34 | return bookList.size(); 35 | } 36 | 37 | @Override 38 | public Book getItem(int position) { 39 | return bookList.get(position); 40 | } 41 | 42 | @Override 43 | public long getItemId(int position) { 44 | return position; 45 | } 46 | 47 | @Override 48 | public View getView(int position, View convertView, ViewGroup parent) { 49 | if (convertView == null) { 50 | convertView = getLayoutInflater().inflate(R.layout.list_item, null); 51 | } 52 | 53 | NetworkImageView imvCover = (NetworkImageView) convertView.findViewById(R.id.imvCover); 54 | TextView txvAuthor = (TextView) convertView.findViewById(R.id.txvAuthor); 55 | TextView txvName = (TextView) convertView.findViewById(R.id.txvName); 56 | 57 | Book book = getItem(position); 58 | 59 | Netroid.displayImage(book.getImageUrl(), imvCover, 60 | android.R.drawable.ic_menu_rotate, android.R.drawable.ic_delete); 61 | txvAuthor.setText(book.getAuthor()); 62 | txvName.setText(book.getName()); 63 | 64 | return convertView; 65 | } 66 | }); 67 | } 68 | 69 | // initialize netroid, this code should be invoke at Application in product stage. 70 | protected abstract void initNetroid(); 71 | 72 | @Override 73 | public void finish() { 74 | Netroid.destroy(); 75 | super.finish(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /sample/src/main/java/com/vincestyling/netroid/sample/BatchImageRequestDiskActivity.java: -------------------------------------------------------------------------------- 1 | package com.vincestyling.netroid.sample; 2 | 3 | import com.vincestyling.netroid.cache.DiskCache; 4 | import com.vincestyling.netroid.request.ImageRequest; 5 | import com.vincestyling.netroid.sample.netroid.Netroid; 6 | import com.vincestyling.netroid.sample.netroid.SelfImageLoader; 7 | 8 | import java.io.File; 9 | import java.util.concurrent.TimeUnit; 10 | 11 | public class BatchImageRequestDiskActivity extends BaseListActivity { 12 | // initialize netroid, this code should be invoke at Application in product stage. 13 | @Override 14 | protected void initNetroid() { 15 | File diskCacheDir = new File(getCacheDir(), "netroid"); 16 | int diskCacheSize = 50 * 1024 * 1024; // 50MB 17 | Netroid.init(new DiskCache(diskCacheDir, diskCacheSize)); 18 | 19 | Netroid.setImageLoader(new SelfImageLoader(Netroid.getRequestQueue(), null, getResources(), getAssets()) { 20 | @Override 21 | public void makeRequest(ImageRequest request) { 22 | request.setCacheExpireTime(TimeUnit.MINUTES, 10); 23 | } 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /sample/src/main/java/com/vincestyling/netroid/sample/BatchImageRequestMemActivity.java: -------------------------------------------------------------------------------- 1 | package com.vincestyling.netroid.sample; 2 | 3 | import com.vincestyling.netroid.cache.BitmapImageCache; 4 | import com.vincestyling.netroid.request.ImageRequest; 5 | import com.vincestyling.netroid.sample.netroid.Netroid; 6 | import com.vincestyling.netroid.sample.netroid.SelfImageLoader; 7 | 8 | import java.util.concurrent.TimeUnit; 9 | 10 | public class BatchImageRequestMemActivity extends BaseListActivity { 11 | // initialize netroid, this code should be invoke at Application in product stage. 12 | @Override 13 | protected void initNetroid() { 14 | Netroid.init(null); 15 | 16 | int memoryCacheSize = 5 * 1024 * 1024; // 5MB 17 | Netroid.setImageLoader(new SelfImageLoader(Netroid.getRequestQueue(), 18 | new BitmapImageCache(memoryCacheSize), getResources(), getAssets()) { 19 | @Override 20 | public void makeRequest(ImageRequest request) { 21 | request.setCacheExpireTime(TimeUnit.DAYS, 10); 22 | } 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /sample/src/main/java/com/vincestyling/netroid/sample/BatchImageRequestMultCacheActivity.java: -------------------------------------------------------------------------------- 1 | package com.vincestyling.netroid.sample; 2 | 3 | import com.vincestyling.netroid.cache.BitmapImageCache; 4 | import com.vincestyling.netroid.cache.DiskCache; 5 | import com.vincestyling.netroid.request.ImageRequest; 6 | import com.vincestyling.netroid.sample.netroid.Netroid; 7 | import com.vincestyling.netroid.sample.netroid.SelfImageLoader; 8 | 9 | import java.io.File; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | public class BatchImageRequestMultCacheActivity extends BaseListActivity { 13 | // initialize netroid, this code should be invoke at Application in product stage. 14 | @Override 15 | protected void initNetroid() { 16 | int memoryCacheSize = 5 * 1024 * 1024; // 5MB 17 | 18 | File diskCacheDir = new File(getCacheDir(), "netroid"); 19 | int diskCacheSize = 50 * 1024 * 1024; // 50MB 20 | 21 | Netroid.init(new DiskCache(diskCacheDir, diskCacheSize)); 22 | 23 | Netroid.setImageLoader(new SelfImageLoader(Netroid.getRequestQueue(), 24 | new BitmapImageCache(memoryCacheSize), getResources(), getAssets()) { 25 | @Override 26 | public void makeRequest(ImageRequest request) { 27 | request.setCacheExpireTime(TimeUnit.MINUTES, 1); 28 | } 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /sample/src/main/java/com/vincestyling/netroid/sample/CommonHttpRequestActivity.java: -------------------------------------------------------------------------------- 1 | package com.vincestyling.netroid.sample; 2 | 3 | import android.app.AlertDialog; 4 | import android.app.ProgressDialog; 5 | import android.os.Bundle; 6 | import android.os.SystemClock; 7 | import android.view.View; 8 | import android.widget.Button; 9 | import android.widget.Toast; 10 | import com.vincestyling.netroid.DefaultRetryPolicy; 11 | import com.vincestyling.netroid.Listener; 12 | import com.vincestyling.netroid.NetroidError; 13 | import com.vincestyling.netroid.Request; 14 | import com.vincestyling.netroid.cache.DiskCache; 15 | import com.vincestyling.netroid.request.JsonArrayRequest; 16 | import com.vincestyling.netroid.request.JsonObjectRequest; 17 | import com.vincestyling.netroid.request.StringRequest; 18 | import com.vincestyling.netroid.sample.netroid.Netroid; 19 | import org.json.JSONArray; 20 | import org.json.JSONObject; 21 | 22 | import java.io.File; 23 | import java.util.concurrent.TimeUnit; 24 | 25 | /** 26 | * This sample demonstrating a bunch of most basic request operations. included JsonObject, 27 | * JsonArray, String, Ignore Disk Cache to Force Performing, Various Listener Events. 28 | */ 29 | public class CommonHttpRequestActivity extends BaseActivity implements View.OnClickListener { 30 | private String PREFIX = "http://netroid.cn/dummy/"; 31 | 32 | private Button btnJsonObject; 33 | private Button btnJsonArray; 34 | private Button btnStringRequs; 35 | private Button btnForceUpdate; 36 | private Button btnEventListener; 37 | 38 | @Override 39 | protected void onCreate(Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | setContentView(R.layout.common_http_request); 42 | 43 | btnJsonObject = (Button) findViewById(R.id.btnJsonObject); 44 | btnJsonArray = (Button) findViewById(R.id.btnJsonArray); 45 | btnStringRequs = (Button) findViewById(R.id.btnStringRequs); 46 | btnForceUpdate = (Button) findViewById(R.id.btnForceUpdate); 47 | btnEventListener = (Button) findViewById(R.id.btnEventListener); 48 | 49 | btnJsonObject.setOnClickListener(this); 50 | btnJsonArray.setOnClickListener(this); 51 | btnStringRequs.setOnClickListener(this); 52 | btnForceUpdate.setOnClickListener(this); 53 | btnEventListener.setOnClickListener(this); 54 | } 55 | 56 | // initialize netroid, this code should be invoke at Application in product stage. 57 | @Override 58 | protected void initNetroid() { 59 | File diskCacheDir = new File(getCacheDir(), "netroid"); 60 | int diskCacheSize = 50 * 1024 * 1024; // 50MB 61 | Netroid.init(new DiskCache(diskCacheDir, diskCacheSize)); 62 | } 63 | 64 | @Override 65 | public void onClick(View view) { 66 | if (view.equals(btnJsonObject)) { 67 | processJsonObject(); 68 | } else if (view.equals(btnJsonArray)) { 69 | processJsonArray(); 70 | } else if (view.equals(btnStringRequs)) { 71 | processStringRequs(); 72 | } else if (view.equals(btnForceUpdate)) { 73 | processForceUpdate(); 74 | } else if (view.equals(btnEventListener)) { 75 | processEventListener(); 76 | } 77 | } 78 | 79 | private void processJsonObject() { 80 | String url = PREFIX + "hot_keywords"; 81 | JsonObjectRequest request = new JsonObjectRequest(url, null, new BaseListener()); 82 | request.setCacheExpireTime(TimeUnit.MINUTES, 1); 83 | request.addHeader("HeaderTest", "11"); 84 | Netroid.add(request); 85 | } 86 | 87 | private void processJsonArray() { 88 | String url = PREFIX + "categories"; 89 | JsonArrayRequest request = new JsonArrayRequest(url, new BaseListener()); 90 | request.setCacheExpireTime(TimeUnit.SECONDS, 10); 91 | Netroid.add(request); 92 | } 93 | 94 | private void processStringRequs() { 95 | String url = PREFIX + "offwith_1"; 96 | StringRequest request = new StringRequest(Request.Method.GET, url, new BaseListener()); 97 | request.setCacheExpireTime(TimeUnit.HOURS, 1); 98 | Netroid.add(request); 99 | } 100 | 101 | private void processForceUpdate() { 102 | String url = PREFIX + "offwith_1"; 103 | StringRequest request = new StringRequest(url, new BaseListener()); 104 | request.setForceUpdate(true); 105 | Netroid.add(request); 106 | } 107 | 108 | private void processEventListener() { 109 | final String REQUESTS_TAG = "Request-Demo"; 110 | String url = "http://facebook.com/"; 111 | StringRequest request = new StringRequest(url, new BaseListener() { 112 | long startTimeMs; 113 | int retryCount; 114 | 115 | @Override 116 | public void onPreExecute() { 117 | super.onPreExecute(); 118 | startTimeMs = SystemClock.elapsedRealtime(); 119 | Toast.makeText(CommonHttpRequestActivity.this, "onPreExecute()", Toast.LENGTH_SHORT).show(); 120 | } 121 | 122 | @Override 123 | public void onFinish() { 124 | super.onFinish(); 125 | Toast.makeText(CommonHttpRequestActivity.this, "onFinish()", Toast.LENGTH_SHORT).show(); 126 | } 127 | 128 | @Override 129 | public void onSuccess(String response) { 130 | super.onSuccess(response); 131 | Toast.makeText(CommonHttpRequestActivity.this, "onSuccess()", Toast.LENGTH_SHORT).show(); 132 | } 133 | 134 | @Override 135 | public void onError(NetroidError error) { 136 | super.onError(error); 137 | Toast.makeText(CommonHttpRequestActivity.this, "onError()", Toast.LENGTH_SHORT).show(); 138 | } 139 | 140 | @Override 141 | public void onRetry() { 142 | super.onRetry(); 143 | Toast.makeText(CommonHttpRequestActivity.this, "onRetry()", Toast.LENGTH_SHORT).show(); 144 | long executedTime = SystemClock.elapsedRealtime() - startTimeMs; 145 | if (++retryCount > 5 || executedTime > 30000) { 146 | AppLog.e("retryCount : %d executedTime : %d", retryCount, executedTime); 147 | Netroid.getRequestQueue().cancelAll(REQUESTS_TAG); 148 | } 149 | } 150 | 151 | @Override 152 | public void onCancel() { 153 | super.onCancel(); 154 | Toast.makeText(CommonHttpRequestActivity.this, "onCancel()", Toast.LENGTH_SHORT).show(); 155 | } 156 | }); 157 | 158 | request.setRetryPolicy(new DefaultRetryPolicy(DefaultRetryPolicy.DEFAULT_TIMEOUT_MS, 20, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT)); 159 | request.setTag(REQUESTS_TAG); 160 | Netroid.add(request); 161 | } 162 | 163 | private void showResult(String result) { 164 | AlertDialog.Builder builder = new AlertDialog.Builder(this); 165 | builder.setMessage(result); 166 | builder.show(); 167 | } 168 | 169 | private class BaseListener extends Listener { 170 | private ProgressDialog mProgressDialog; 171 | 172 | @Override 173 | public void onPreExecute() { 174 | mProgressDialog = ProgressDialog.show(CommonHttpRequestActivity.this, null, "request executing"); 175 | } 176 | 177 | @Override 178 | public void onFinish() { 179 | if (mProgressDialog != null) { 180 | mProgressDialog.cancel(); 181 | } 182 | } 183 | 184 | @Override 185 | public void onSuccess(T response) { 186 | showResult(response.toString()); 187 | } 188 | 189 | @Override 190 | public void onError(NetroidError error) { 191 | showResult("REQUEST ERROR : " + error.getMessage()); 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /sample/src/main/java/com/vincestyling/netroid/sample/GridViewActivity.java: -------------------------------------------------------------------------------- 1 | package com.vincestyling.netroid.sample; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | import android.widget.*; 7 | import com.vincestyling.netroid.cache.BitmapImageCache; 8 | import com.vincestyling.netroid.sample.mock.Book; 9 | import com.vincestyling.netroid.sample.mock.BookDataMock; 10 | import com.vincestyling.netroid.sample.netroid.Netroid; 11 | import com.vincestyling.netroid.sample.netroid.SelfImageLoader; 12 | import com.vincestyling.netroid.widget.NetworkImageView; 13 | 14 | import java.util.Collections; 15 | import java.util.List; 16 | 17 | /** 18 | * This sample aim to show you how Netroid resolved GridView.getView(position == 0) 19 | * invoke too many times then make the grid disorder during dataset changed. It's 20 | * totally benefit by NetworkImageView widget which provided by Netroid. 21 | */ 22 | public class GridViewActivity extends BaseActivity { 23 | private BaseAdapter mAdapter; 24 | private List bookList; 25 | 26 | public void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | setContentView(R.layout.activity_gridview_p0); 29 | 30 | bookList = BookDataMock.getData(); 31 | Collections.shuffle(bookList); 32 | while (bookList.size() > 5) { 33 | bookList.remove(0); 34 | } 35 | 36 | final View btnAddView = findViewById(R.id.btnAddView); 37 | final View btnRemoveView = findViewById(R.id.btnRemoveView); 38 | 39 | btnAddView.setOnClickListener(new View.OnClickListener() { 40 | @Override 41 | public void onClick(View v) { 42 | List tmpBookList = BookDataMock.getData(); 43 | Collections.shuffle(tmpBookList); 44 | 45 | root: for (Book book : tmpBookList) { 46 | for (Book eBook : bookList) { 47 | if (eBook.equals(book)) continue root; 48 | } 49 | 50 | bookList.add(book); 51 | mAdapter.notifyDataSetChanged(); 52 | btnRemoveView.setVisibility(View.VISIBLE); 53 | 54 | return; 55 | } 56 | 57 | btnAddView.setVisibility(View.GONE); 58 | } 59 | }); 60 | 61 | btnRemoveView.setOnClickListener(new View.OnClickListener() { 62 | @Override 63 | public void onClick(View v) { 64 | bookList.remove(bookList.size() - 1); 65 | mAdapter.notifyDataSetChanged(); 66 | 67 | if (bookList.isEmpty()) { 68 | btnRemoveView.setVisibility(View.GONE); 69 | } 70 | } 71 | }); 72 | 73 | mAdapter = new BaseAdapter() { 74 | @Override 75 | public int getCount() { 76 | return bookList.size() + 1; 77 | } 78 | 79 | @Override 80 | public Book getItem(int position) { 81 | return bookList.get(position); 82 | } 83 | 84 | @Override 85 | public long getItemId(int position) { 86 | return position; 87 | } 88 | 89 | @Override 90 | public View getView(int position, View convertView, ViewGroup parent) { 91 | Holder holder; 92 | if (convertView == null) { 93 | convertView = getLayoutInflater().inflate(R.layout.grid_item, null); 94 | holder = new Holder(); 95 | holder.imvCover = (NetworkImageView) convertView.findViewById(R.id.imvCover); 96 | holder.txvName = (TextView) convertView.findViewById(R.id.txvName); 97 | convertView.setTag(holder); 98 | } else { 99 | holder = (Holder) convertView.getTag(); 100 | } 101 | 102 | if (position == bookList.size()) { 103 | Netroid.displayImage(SelfImageLoader.RES_DRAWABLE + R.drawable.click_to_add_more, holder.imvCover); 104 | holder.txvName.setText(""); 105 | } else { 106 | Book book = getItem(position); 107 | 108 | Netroid.displayImage(book.getImageUrl(), holder.imvCover, 109 | android.R.drawable.ic_menu_rotate, android.R.drawable.ic_delete); 110 | holder.txvName.setText(book.getName()); 111 | } 112 | 113 | return convertView; 114 | } 115 | 116 | class Holder { 117 | NetworkImageView imvCover; 118 | TextView txvName; 119 | } 120 | }; 121 | 122 | GridView grvDemonstration = (GridView) findViewById(R.id.grvDemonstration); 123 | grvDemonstration.setAdapter(mAdapter); 124 | 125 | grvDemonstration.setOnItemClickListener(new AdapterView.OnItemClickListener() { 126 | @Override 127 | public void onItemClick(AdapterView parent, View view, int position, long id) { 128 | if (position == bookList.size()) { 129 | btnAddView.performClick(); 130 | } else { 131 | showToast("clicked [" + bookList.get(position).getName() + "]"); 132 | } 133 | } 134 | }); 135 | } 136 | 137 | private Toast mToast; 138 | 139 | private void showToast(CharSequence msg) { 140 | if (mToast != null) mToast.cancel(); 141 | mToast = Toast.makeText(this, msg, Toast.LENGTH_SHORT); 142 | mToast.show(); 143 | } 144 | 145 | // initialize netroid, this code should be invoke at Application in product stage. 146 | @Override 147 | protected void initNetroid() { 148 | Netroid.init(null); 149 | 150 | int memoryCacheSize = 5 * 1024 * 1024; // 5MB 151 | Netroid.setImageLoader(new SelfImageLoader(Netroid.getRequestQueue(), 152 | new BitmapImageCache(memoryCacheSize), getResources(), getAssets())); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /sample/src/main/java/com/vincestyling/netroid/sample/ImageRequestActivity.java: -------------------------------------------------------------------------------- 1 | package com.vincestyling.netroid.sample; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.os.Environment; 6 | import android.view.View; 7 | import android.widget.Button; 8 | import android.widget.ImageView; 9 | import com.vincestyling.netroid.cache.BitmapImageCache; 10 | import com.vincestyling.netroid.cache.DiskCache; 11 | import com.vincestyling.netroid.sample.netroid.Netroid; 12 | import com.vincestyling.netroid.sample.netroid.SelfImageLoader; 13 | import com.vincestyling.netroid.widget.NetworkImageView; 14 | 15 | import java.io.File; 16 | 17 | public class ImageRequestActivity extends BaseActivity implements View.OnClickListener { 18 | private ImageView mImageView; 19 | private NetworkImageView mNetworkImageView; 20 | private Button btnLoadSingleImage; 21 | private Button btnImageLoaderHttp; 22 | private Button btnImageLoaderAssets; 23 | private Button btnImageLoaderSdcard; 24 | private Button btnGridView; 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | setContentView(R.layout.activity_image_request); 30 | 31 | mImageView = (ImageView) findViewById(R.id.imvAnchor); 32 | mNetworkImageView = (NetworkImageView) findViewById(R.id.imvNetwork); 33 | btnLoadSingleImage = (Button) findViewById(R.id.btnLoadSingleImage); 34 | btnImageLoaderHttp = (Button) findViewById(R.id.btnImageLoaderHttp); 35 | btnImageLoaderAssets = (Button) findViewById(R.id.btnImageLoaderAssets); 36 | btnImageLoaderSdcard = (Button) findViewById(R.id.btnImageLoaderSdcard); 37 | btnGridView = (Button) findViewById(R.id.btnGridView); 38 | 39 | btnLoadSingleImage.setOnClickListener(this); 40 | btnImageLoaderHttp.setOnClickListener(this); 41 | btnImageLoaderAssets.setOnClickListener(this); 42 | btnImageLoaderSdcard.setOnClickListener(this); 43 | btnGridView.setOnClickListener(this); 44 | } 45 | 46 | // initialize netroid, this code should be invoke at Application in product stage. 47 | @Override 48 | protected void initNetroid() { 49 | int memoryCacheSize = 5 * 1024 * 1024; // 5MB 50 | 51 | File diskCacheDir = new File(getCacheDir(), "netroid"); 52 | int diskCacheSize = 50 * 1024 * 1024; // 50MB 53 | 54 | Netroid.init(new DiskCache(diskCacheDir, diskCacheSize)); 55 | Netroid.setImageLoader(new SelfImageLoader(Netroid.getRequestQueue(), 56 | new BitmapImageCache(memoryCacheSize), getResources(), getAssets())); 57 | } 58 | 59 | @Override 60 | public void onClick(View view) { 61 | if (view.equals(btnLoadSingleImage)) { 62 | loadSingleImage(); 63 | } else if (view.equals(btnImageLoaderHttp)) { 64 | loadHttpImage(); 65 | } else if (view.equals(btnImageLoaderAssets)) { 66 | loadAssetsImage(); 67 | } else if (view.equals(btnImageLoaderSdcard)) { 68 | loadSdcardImage(); 69 | } else if (view.equals(btnGridView)) { 70 | loadGridView(); 71 | } 72 | } 73 | 74 | private void loadSingleImage() { 75 | String url = "http://upload.newhua.com/3/3e/1292303714308.jpg"; 76 | Netroid.displayImage(url, mImageView, android.R.drawable.ic_menu_rotate, android.R.drawable.ic_delete); 77 | } 78 | 79 | private void loadHttpImage() { 80 | String url = "http://i3.sinaimg.cn/blog/sports/idx/2014/0114/U5295P346T302D1F7961DT20140114132743.jpg"; 81 | Netroid.displayImage(url, mNetworkImageView, android.R.drawable.ic_menu_rotate, android.R.drawable.ic_delete); 82 | } 83 | 84 | private void loadAssetsImage() { 85 | String url = SelfImageLoader.RES_ASSETS + "cover_16539.jpg"; 86 | Netroid.displayImage(url, mNetworkImageView, android.R.drawable.ic_menu_rotate, android.R.drawable.ic_delete); 87 | } 88 | 89 | private void loadSdcardImage() { 90 | String url = SelfImageLoader.RES_SDCARD + Environment.getExternalStorageDirectory() + "/sample.jpg"; 91 | Netroid.displayImage(url, mNetworkImageView, android.R.drawable.ic_menu_rotate, android.R.drawable.ic_delete); 92 | } 93 | 94 | private void loadGridView() { 95 | // Note : when back from GridViewActivity then click any others buttons, 96 | // app will crash because Netroid requirements destroyed by GridViewActivity, 97 | // this problem not need to be solve in such sample context. 98 | startActivity(new Intent(this, GridViewActivity.class)); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /sample/src/main/java/com/vincestyling/netroid/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.vincestyling.netroid.sample; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.view.View; 7 | import android.widget.Button; 8 | 9 | /** 10 | * The main entry of all the samples. 11 | *

12 | * Note : every sub-activities acted a standalone test case, never involve of others. 13 | * The Netroid initialization of them in particularly, best 14 | * been invokes where app startup(i.e. {@link android.app.Application}). 15 | */ 16 | public class MainActivity extends Activity implements View.OnClickListener { 17 | private Button mButtonCommon; 18 | private Button mButtonImage; 19 | private Button btnBatchImageMem; 20 | private Button btnBatchImageDisk; 21 | private Button btnBatchImageMultCache; 22 | private Button btnFileDownload; 23 | private Button btnBlockingRequest; 24 | 25 | @Override 26 | protected void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | setContentView(R.layout.activity_main); 29 | 30 | mButtonCommon = (Button) findViewById(R.id.btnCommonHttpRequest); 31 | mButtonCommon.setOnClickListener(this); 32 | 33 | mButtonImage = (Button) findViewById(R.id.btnImageRequest); 34 | mButtonImage.setOnClickListener(this); 35 | 36 | btnBatchImageMem = (Button) findViewById(R.id.btnBatchImageMem); 37 | btnBatchImageMem.setOnClickListener(this); 38 | 39 | btnBatchImageDisk = (Button) findViewById(R.id.btnBatchImageDisk); 40 | btnBatchImageDisk.setOnClickListener(this); 41 | 42 | btnBatchImageMultCache = (Button) findViewById(R.id.btnBatchImageMultCache); 43 | btnBatchImageMultCache.setOnClickListener(this); 44 | 45 | btnFileDownload = (Button) findViewById(R.id.btnFileDownload); 46 | btnFileDownload.setOnClickListener(this); 47 | 48 | btnBlockingRequest = (Button) findViewById(R.id.btnBlockingRequest); 49 | btnBlockingRequest.setOnClickListener(this); 50 | } 51 | 52 | @Override 53 | public void onClick(View view) { 54 | Intent intent = null; 55 | if (view.equals(mButtonCommon)) { 56 | intent = new Intent(this, CommonHttpRequestActivity.class); 57 | } 58 | else if (view.equals(mButtonImage)) { 59 | intent = new Intent(this, ImageRequestActivity.class); 60 | } 61 | else if (view.equals(btnBatchImageDisk)) { 62 | intent = new Intent(this, BatchImageRequestDiskActivity.class); 63 | } 64 | else if (view.equals(btnBatchImageMem)) { 65 | intent = new Intent(this, BatchImageRequestMemActivity.class); 66 | } 67 | else if (view.equals(btnBatchImageMultCache)) { 68 | intent = new Intent(this, BatchImageRequestMultCacheActivity.class); 69 | } 70 | else if (view.equals(btnFileDownload)) { 71 | intent = new Intent(this, FileDownloadActivity.class); 72 | } 73 | else if (view.equals(btnBlockingRequest)) { 74 | intent = new Intent(this, OffWithMainThreadPerformingActivity.class); 75 | } 76 | startActivity(intent); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /sample/src/main/java/com/vincestyling/netroid/sample/OffWithMainThreadPerformingActivity.java: -------------------------------------------------------------------------------- 1 | package com.vincestyling.netroid.sample; 2 | 3 | import android.app.ProgressDialog; 4 | import android.os.AsyncTask; 5 | import android.os.Bundle; 6 | import android.os.Looper; 7 | import android.text.TextUtils; 8 | import android.view.View; 9 | import android.widget.TextView; 10 | import com.vincestyling.netroid.Listener; 11 | import com.vincestyling.netroid.request.StringRequest; 12 | import com.vincestyling.netroid.sample.netroid.TransactionalRequest; 13 | import com.vincestyling.netroid.sample.netroid.Netroid; 14 | 15 | import java.util.Arrays; 16 | import java.util.Collections; 17 | import java.util.List; 18 | 19 | /** 20 | * Sometimes, we may invoke our logical in an AsyncTask or just a Thread, 21 | * when the time we want to perform a Http Request in that, it is messy 22 | * to leave current thread to performing. Thus we brought you this sample 23 | * to demonstrating how to perform a blocking request in background threads. 24 | */ 25 | public class OffWithMainThreadPerformingActivity extends BaseActivity { 26 | private List urls; 27 | 28 | private TextView txvResult; 29 | 30 | @Override 31 | protected void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.activity_off_with_main_thread); 34 | 35 | findViewById(R.id.btnPerformByAsyncTask).setOnClickListener(new View.OnClickListener() { 36 | @Override 37 | public void onClick(View v) { 38 | new CombineTask().execute(); 39 | } 40 | }); 41 | 42 | findViewById(R.id.btnPerformByCustomRequest).setOnClickListener(new View.OnClickListener() { 43 | @Override 44 | public void onClick(View v) { 45 | produceDummyUrls(); 46 | 47 | // the two requests would be perform just like a transaction. 48 | Netroid.add(new TransactionalRequest(urls.get(0), urls.get(1), new Listener() { 49 | private ProgressDialog mProgressDialog; 50 | 51 | @Override 52 | public void onPreExecute() { 53 | mProgressDialog = ProgressDialog.show(OffWithMainThreadPerformingActivity.this, null, "task executing"); 54 | txvResult.setText(""); 55 | } 56 | 57 | @Override 58 | public void onSuccess(String result) { 59 | if (mProgressDialog != null) { 60 | mProgressDialog.cancel(); 61 | } 62 | 63 | if (TextUtils.isEmpty(result)) { 64 | result = "result is empty!"; 65 | } 66 | 67 | txvResult.setText(result); 68 | } 69 | })); 70 | } 71 | }); 72 | 73 | txvResult = (TextView) findViewById(R.id.txvResult); 74 | } 75 | 76 | // initialize netroid, this code should be invoke at Application in product stage. 77 | @Override 78 | protected void initNetroid() { 79 | Netroid.init(null); 80 | } 81 | 82 | private class CombineTask extends AsyncTask { 83 | private ProgressDialog mProgressDialog; 84 | 85 | @Override 86 | protected void onPreExecute() { 87 | mProgressDialog = ProgressDialog.show(OffWithMainThreadPerformingActivity.this, null, "task executing"); 88 | txvResult.setText(""); 89 | } 90 | 91 | @Override 92 | protected String doInBackground(Void... params) { 93 | return perform(); 94 | } 95 | 96 | @Override 97 | protected void onPostExecute(String result) { 98 | if (mProgressDialog != null) { 99 | mProgressDialog.cancel(); 100 | } 101 | 102 | if (TextUtils.isEmpty(result)) { 103 | result = "result is empty!"; 104 | } 105 | 106 | txvResult.setText(result); 107 | } 108 | } 109 | 110 | /** 111 | * In this method, we perform two requests randomly in blocking way, compare the response whether equals at last. 112 | * Notice : this method must run on backgroud thread. 113 | * 114 | * @return the result of two requests. 115 | */ 116 | private String perform() { 117 | AppLog.e("Is run on main thread : %s", Looper.myLooper() == Looper.getMainLooper()); 118 | 119 | produceDummyUrls(); 120 | 121 | final String[] results = new String[2]; 122 | 123 | for (int i = 0; i < 2; i++) { 124 | final int index = i; 125 | String url = urls.get(i); 126 | AppLog.e("perform url : %s", url); 127 | 128 | // perform request in blocking mode. 129 | Netroid.perform(new StringRequest(url, new Listener() { 130 | @Override 131 | public void onSuccess(String response) { 132 | results[index] = response; 133 | AppLog.e("perform url[%d] result : %s", index, response); 134 | } 135 | })); 136 | } 137 | 138 | return "result <1>[" + results[0] + "] result <2>[" + results[1] + "] equals : " + TextUtils.equals(results[0], results[1]); 139 | } 140 | 141 | private void produceDummyUrls() { 142 | String prefix = "http://netroid.cn/dummy/"; 143 | 144 | urls = Arrays.asList(prefix + "offwith_1", prefix + "offwith_2", prefix + "offwith_1_1"); 145 | // shuffling the List so we can make the results not always identical. 146 | Collections.shuffle(urls); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /sample/src/main/java/com/vincestyling/netroid/sample/mock/Book.java: -------------------------------------------------------------------------------- 1 | package com.vincestyling.netroid.sample.mock; 2 | 3 | import android.text.TextUtils; 4 | 5 | public class Book { 6 | private final String imageUrl; 7 | private final String name; 8 | private final String author; 9 | 10 | public Book(String imageUrl, String name, String author) { 11 | this.imageUrl = imageUrl; 12 | this.name = name; 13 | this.author = author; 14 | } 15 | 16 | public String getImageUrl() { 17 | return imageUrl; 18 | } 19 | 20 | public String getName() { 21 | return name; 22 | } 23 | 24 | public String getAuthor() { 25 | return author; 26 | } 27 | 28 | @Override 29 | public boolean equals(Object obj) { 30 | return obj instanceof Book && TextUtils.equals(name, ((Book) obj).name); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /sample/src/main/java/com/vincestyling/netroid/sample/netroid/Netroid.java: -------------------------------------------------------------------------------- 1 | package com.vincestyling.netroid.sample.netroid; 2 | 3 | import android.os.Build; 4 | import android.widget.ImageView; 5 | import com.vincestyling.netroid.*; 6 | import com.vincestyling.netroid.cache.DiskCache; 7 | import com.vincestyling.netroid.widget.NetworkImageView; 8 | import com.vincestyling.netroid.stack.HttpClientStack; 9 | import com.vincestyling.netroid.stack.HttpStack; 10 | import com.vincestyling.netroid.stack.HurlStack; 11 | import com.vincestyling.netroid.toolbox.BasicNetwork; 12 | import com.vincestyling.netroid.toolbox.FileDownloader; 13 | import com.vincestyling.netroid.toolbox.ImageLoader; 14 | import org.apache.http.protocol.HTTP; 15 | 16 | import java.util.concurrent.Executor; 17 | 18 | public class Netroid { 19 | public static final String USER_AGENT = "netroid_sample"; 20 | private RequestQueue mRequestQueue; 21 | private ImageLoader mImageLoader; 22 | private Network mNetwork; 23 | private DiskCache mDiskCache; 24 | private FileDownloader mFileDownloader; 25 | 26 | private Netroid() { 27 | /* cannot be instantiated */ 28 | } 29 | 30 | private static Netroid mInstance; 31 | 32 | public static void init(DiskCache cache) { 33 | mInstance = new Netroid(); 34 | 35 | mInstance.mDiskCache = cache; 36 | 37 | HttpStack stack; 38 | 39 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { 40 | stack = new HurlStack(USER_AGENT, null); 41 | } else { 42 | // Prior to Gingerbread, HttpUrlConnection was unreliable. 43 | // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html 44 | stack = new HttpClientStack(USER_AGENT); 45 | } 46 | 47 | mInstance.mNetwork = new BasicNetwork(stack, HTTP.UTF_8); 48 | int poolSize = RequestQueue.DEFAULT_NETWORK_THREAD_POOL_SIZE; 49 | mInstance.mRequestQueue = new RequestQueue(mInstance.mNetwork, poolSize, cache); 50 | mInstance.mRequestQueue.start(); 51 | } 52 | 53 | public static RequestQueue getRequestQueue() { 54 | if (mInstance.mRequestQueue != null) { 55 | return mInstance.mRequestQueue; 56 | } else { 57 | throw new IllegalStateException("RequestQueue not initialized"); 58 | } 59 | } 60 | 61 | public static void add(Request request) { 62 | getRequestQueue().add(request); 63 | } 64 | 65 | /** 66 | * Perform given request as blocking mode. Note make sure won't invoke on main thread. 67 | */ 68 | public static void perform(Request request) { 69 | // you might want to keep the ExecutorDelivery instance as Field, but it's 70 | // cheap constructing every time, depends how often you use this way. 71 | RequestPerformer.perform(request, Netroid.getNetwork(), new ExecutorDelivery(new Executor() { 72 | @Override 73 | public void execute(Runnable command) { 74 | // invoke run() directly. 75 | command.run(); 76 | } 77 | })); 78 | } 79 | 80 | public static void setImageLoader(ImageLoader imageLoader) { 81 | mInstance.mImageLoader = imageLoader; 82 | } 83 | 84 | public static ImageLoader getImageLoader() { 85 | if (mInstance.mImageLoader != null) { 86 | return mInstance.mImageLoader; 87 | } else { 88 | throw new IllegalStateException("ImageLoader not initialized"); 89 | } 90 | } 91 | 92 | public static Network getNetwork() { 93 | if (mInstance.mNetwork != null) { 94 | return mInstance.mNetwork; 95 | } else { 96 | throw new IllegalStateException("Network not initialized"); 97 | } 98 | } 99 | 100 | public static DiskCache getDiskCache() { 101 | return mInstance.mDiskCache; 102 | } 103 | 104 | public static void setFileDownloder(FileDownloader downloder) { 105 | mInstance.mFileDownloader = downloder; 106 | } 107 | 108 | public static FileDownloader getFileDownloader() { 109 | if (mInstance.mFileDownloader != null) { 110 | return mInstance.mFileDownloader; 111 | } else { 112 | throw new IllegalStateException("FileDownloader not initialized"); 113 | } 114 | } 115 | 116 | public static void displayImage(String url, ImageView imageView, int defaultImageResId, int errorImageResId) { 117 | ImageLoader.ImageListener listener = ImageLoader.getImageListener(imageView, defaultImageResId, errorImageResId); 118 | getImageLoader().get(url, listener, 0, 0); 119 | } 120 | 121 | public static void displayImage(String url, ImageView imageView) { 122 | displayImage(url, imageView, 0, 0); 123 | } 124 | 125 | public static void displayImage(String url, NetworkImageView imageView, int defaultImageResId, int errorImageResId) { 126 | imageView.setDefaultImageResId(defaultImageResId); 127 | imageView.setErrorImageResId(errorImageResId); 128 | imageView.setImageUrl(url, getImageLoader()); 129 | } 130 | 131 | public static void displayImage(String url, NetworkImageView imageView) { 132 | displayImage(url, imageView, 0, 0); 133 | } 134 | 135 | public static void destroy() { 136 | if (mInstance.mRequestQueue != null) { 137 | mInstance.mRequestQueue.stop(); 138 | mInstance.mRequestQueue = null; 139 | } 140 | 141 | if (mInstance.mFileDownloader != null) { 142 | mInstance.mFileDownloader.clearAll(); 143 | mInstance.mFileDownloader = null; 144 | } 145 | 146 | mInstance.mNetwork = null; 147 | mInstance.mImageLoader = null; 148 | mInstance.mDiskCache = null; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /sample/src/main/java/com/vincestyling/netroid/sample/netroid/SelfImageLoader.java: -------------------------------------------------------------------------------- 1 | package com.vincestyling.netroid.sample.netroid; 2 | 3 | import android.content.res.AssetManager; 4 | import android.content.res.Resources; 5 | import android.graphics.Bitmap; 6 | import android.graphics.BitmapFactory; 7 | import com.vincestyling.netroid.NetworkResponse; 8 | import com.vincestyling.netroid.RequestQueue; 9 | import com.vincestyling.netroid.request.ImageRequest; 10 | import com.vincestyling.netroid.toolbox.ImageLoader; 11 | import org.apache.http.protocol.HTTP; 12 | 13 | import java.io.ByteArrayOutputStream; 14 | import java.io.FileInputStream; 15 | import java.io.IOException; 16 | import java.io.InputStream; 17 | import java.util.concurrent.TimeUnit; 18 | 19 | public class SelfImageLoader extends ImageLoader { 20 | 21 | public static final String RES_ASSETS = "assets://"; 22 | public static final String RES_SDCARD = "sdcard://"; 23 | public static final String RES_DRAWABLE = "drawable://"; 24 | public static final String RES_HTTP = "http://"; 25 | 26 | private AssetManager mAssetManager; 27 | private Resources mResources; 28 | 29 | public SelfImageLoader(RequestQueue queue, ImageCache cache, Resources resources, AssetManager assetManager) { 30 | super(queue, cache); 31 | mResources = resources; 32 | mAssetManager = assetManager; 33 | } 34 | 35 | @Override 36 | public ImageRequest buildRequest(String requestUrl, int maxWidth, int maxHeight) { 37 | ImageRequest request; 38 | if (requestUrl.startsWith(RES_ASSETS)) { 39 | request = new ImageRequest(requestUrl.substring(RES_ASSETS.length()), maxWidth, maxHeight) { 40 | @Override 41 | public NetworkResponse perform() { 42 | try { 43 | return new NetworkResponse(toBytes(mAssetManager.open(getUrl())), HTTP.UTF_8); 44 | } catch (IOException e) { 45 | return new NetworkResponse(new byte[1], HTTP.UTF_8); 46 | } 47 | } 48 | }; 49 | } else if (requestUrl.startsWith(RES_SDCARD)) { 50 | request = new ImageRequest(requestUrl.substring(RES_SDCARD.length()), maxWidth, maxHeight) { 51 | @Override 52 | public NetworkResponse perform() { 53 | try { 54 | return new NetworkResponse(toBytes(new FileInputStream(getUrl())), HTTP.UTF_8); 55 | } catch (IOException e) { 56 | return new NetworkResponse(new byte[1], HTTP.UTF_8); 57 | } 58 | } 59 | }; 60 | } else if (requestUrl.startsWith(RES_DRAWABLE)) { 61 | request = new ImageRequest(requestUrl.substring(RES_DRAWABLE.length()), maxWidth, maxHeight) { 62 | @Override 63 | public NetworkResponse perform() { 64 | try { 65 | int resId = Integer.parseInt(getUrl()); 66 | Bitmap bitmap = BitmapFactory.decodeResource(mResources, resId); 67 | return new NetworkResponse(bitmap2Bytes(bitmap), HTTP.UTF_8); 68 | } catch (Exception e) { 69 | return new NetworkResponse(new byte[1], HTTP.UTF_8); 70 | } 71 | } 72 | }; 73 | } else if (requestUrl.startsWith(RES_HTTP)) { 74 | request = new ImageRequest(requestUrl, maxWidth, maxHeight); 75 | } else { 76 | return null; 77 | } 78 | 79 | makeRequest(request); 80 | return request; 81 | } 82 | 83 | public void makeRequest(ImageRequest request) { 84 | request.setCacheExpireTime(TimeUnit.MINUTES, 10); 85 | } 86 | 87 | public static byte[] toBytes(InputStream is) throws IOException { 88 | ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 89 | 90 | int nRead; 91 | byte[] data = new byte[16384]; 92 | while ((nRead = is.read(data, 0, data.length)) != -1) { 93 | buffer.write(data, 0, nRead); 94 | } 95 | buffer.flush(); 96 | 97 | return buffer.toByteArray(); 98 | } 99 | 100 | public static byte[] bitmap2Bytes(Bitmap bm) { 101 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 102 | bm.compress(Bitmap.CompressFormat.PNG, 100, baos); 103 | return baos.toByteArray(); 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /sample/src/main/java/com/vincestyling/netroid/sample/netroid/TransactionalRequest.java: -------------------------------------------------------------------------------- 1 | package com.vincestyling.netroid.sample.netroid; 2 | 3 | import android.text.TextUtils; 4 | import com.vincestyling.netroid.*; 5 | import com.vincestyling.netroid.request.StringRequest; 6 | import com.vincestyling.netroid.sample.AppLog; 7 | 8 | /** 9 | * This request claim two urls, one perform as blocking mode in {@link #prepare()}, the other perform as normal request. 10 | * We comparing both url's response in {@link #parseNetworkResponse(NetworkResponse)} and return the compared result. 11 | *

12 | * Just like a transaction flow, the second statement must depends on the first statement's result, and so on. 13 | *

14 | * Considering when we need various clauses which retrieve from remote to making 15 | * the final verdict, how remarkable we can do it like this elegant code. 16 | */ 17 | public class TransactionalRequest extends Request { 18 | private String clauseUrl; 19 | 20 | private String clauseResult; 21 | 22 | public TransactionalRequest(String url, String clauseUrl, IListener listener) { 23 | super(url, listener); 24 | this.clauseUrl = clauseUrl; 25 | } 26 | 27 | // perform the clause request in blocking mode first. 28 | @Override 29 | public void prepare() { 30 | // preparing may invoke again when the host request timeouted, 31 | // so don't perform the clause request repeatedly. 32 | if (clauseResult != null) return; 33 | 34 | Netroid.perform(new StringRequest(clauseUrl, new Listener() { 35 | @Override 36 | public void onSuccess(String response) { 37 | clauseResult = response; 38 | AppLog.e("perform clause request url[%s] result : %s", clauseUrl, clauseResult); 39 | } 40 | })); 41 | } 42 | 43 | @Override 44 | protected Response parseNetworkResponse(NetworkResponse response) { 45 | String result = HttpUtils.parseResponse(response); 46 | AppLog.e("perform url[%s] result : %s", getUrl(), result); 47 | 48 | String compared = "ComplexRequest:result <1>[" + clauseResult + "] result <2>[" + result + "] equals : " + TextUtils.equals(clauseResult, result); 49 | return Response.success(compared, response); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/click_to_add_more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vince-styling/Netroid/7e67a08db8fecfee9d8c9ac4be625da108c4012c/sample/src/main/res/drawable-xhdpi/click_to_add_more.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/default190x338.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vince-styling/Netroid/7e67a08db8fecfee9d8c9ac4be625da108c4012c/sample/src/main/res/drawable-xhdpi/default190x338.jpg -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_file_downloader.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 13 | 18 | 19 | 23 | 24 |