├── README.md
└── servicebestpractice
├── .gitignore
├── build.gradle
├── proguard-rules.pro
├── servicebestpractice.iml
└── src
├── androidTest
└── java
│ └── com
│ └── example
│ └── servicebestpractice
│ └── ApplicationTest.java
├── main
├── AndroidManifest.xml
├── java
│ └── com
│ │ └── example
│ │ └── servicebestpractice
│ │ ├── DownloadListener.java
│ │ ├── DownloadService.java
│ │ ├── DownloadTask.java
│ │ └── MainActivity.java
└── res
│ ├── layout
│ └── activity_main.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ ├── values-w820dp
│ └── dimens.xml
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
└── test
└── java
└── com
└── example
└── servicebestpractice
└── ExampleUnitTest.java
/README.md:
--------------------------------------------------------------------------------
1 | # OKHttp_DownloadFile
2 | (1)用OKHttp实现大文件下载(2)OKHttp实现文件下载的断点续传(3)取消下载时删除已下载的文件 Microstrong Microstrong&&you&&me
3 |
--------------------------------------------------------------------------------
/servicebestpractice/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/servicebestpractice/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion '23.0.1'
6 |
7 | defaultConfig {
8 | applicationId "com.example.servicebestpractice"
9 | minSdkVersion 17
10 | targetSdkVersion 24
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | testCompile 'junit:junit:4.12'
25 | compile 'com.android.support:appcompat-v7:24.2.1'
26 | compile 'com.squareup.okhttp3:okhttp:3.4.1'
27 | }
28 |
--------------------------------------------------------------------------------
/servicebestpractice/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 D:\androidDeveloper\android-studio-bundle-143.2821654-windows\sdk1/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 |
--------------------------------------------------------------------------------
/servicebestpractice/servicebestpractice.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | generateDebugSources
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/servicebestpractice/src/androidTest/java/com/example/servicebestpractice/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.example.servicebestpractice;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/servicebestpractice/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/servicebestpractice/src/main/java/com/example/servicebestpractice/DownloadListener.java:
--------------------------------------------------------------------------------
1 | package com.example.servicebestpractice;
2 |
3 | /**
4 | * Created by Administrator on 2017/2/23.
5 | */
6 | public interface DownloadListener {
7 |
8 |
9 | /**
10 | * 通知当前的下载进度
11 | * @param progress
12 | */
13 | void onProgress(int progress);
14 |
15 | /**
16 | * 通知下载成功
17 | */
18 | void onSuccess();
19 |
20 | /**
21 | * 通知下载失败
22 | */
23 | void onFailed();
24 |
25 | /**
26 | * 通知下载暂停
27 | */
28 | void onPaused();
29 |
30 | /**
31 | * 通知下载取消事件
32 | */
33 | void onCanceled();
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/servicebestpractice/src/main/java/com/example/servicebestpractice/DownloadService.java:
--------------------------------------------------------------------------------
1 | package com.example.servicebestpractice;
2 |
3 | import android.app.Notification;
4 | import android.app.NotificationManager;
5 | import android.app.PendingIntent;
6 | import android.app.Service;
7 | import android.content.Context;
8 | import android.content.Intent;
9 | import android.graphics.BitmapFactory;
10 | import android.os.Binder;
11 | import android.os.Environment;
12 | import android.os.IBinder;
13 | import android.support.v4.app.NotificationCompat;
14 | import android.widget.Toast;
15 |
16 | import java.io.File;
17 |
18 | /**
19 | * 为了保证DownloadTask可以一直在后台运行,我们还需要创建一个下载的服务。
20 | */
21 | public class DownloadService extends Service {
22 |
23 | private DownloadTask downloadTask;
24 |
25 | private String downloadUrl;
26 |
27 | private DownloadListener listener=new DownloadListener() {
28 |
29 | /**
30 | * 构建了一个用于显示下载进度的通知
31 | * @param progress
32 | */
33 | @Override
34 | public void onProgress(int progress) {
35 | //NotificationManager的notify()可以让通知显示出来。
36 | //notify(),接收两个参数,第一个参数是id:每个通知所指定的id都是不同的。第二个参数是Notification对象。
37 | getNotificationManager().notify(1,getNotification("Downloading...",progress));
38 | }
39 |
40 | /**
41 | * 创建了一个新的通知用于告诉用户下载成功啦
42 | */
43 | @Override
44 | public void onSuccess() {
45 | downloadTask=null;
46 | //下载成功时将前台服务通知关闭,并创建一个下载成功的通知
47 | stopForeground(true);
48 | getNotificationManager().notify(1,getNotification("Download Success",-1));
49 | Toast.makeText(DownloadService.this,"Download Success",Toast.LENGTH_SHORT).show();
50 | }
51 |
52 | /**
53 | *用户下载失败
54 | */
55 | @Override
56 | public void onFailed() {
57 | downloadTask=null;
58 | //下载失败时,将前台服务通知关闭,并创建一个下载失败的通知
59 | stopForeground(true);
60 | getNotificationManager().notify(1,getNotification("Download Failed",-1));
61 | Toast.makeText(DownloadService.this,"Download Failed",Toast.LENGTH_SHORT).show();
62 | }
63 |
64 | /**
65 | * 用户暂停
66 | */
67 | @Override
68 | public void onPaused() {
69 | downloadTask=null;
70 | Toast.makeText(DownloadService.this,"Download Paused",Toast.LENGTH_SHORT).show();
71 | }
72 |
73 | /**
74 | * 用户取消
75 | */
76 | @Override
77 | public void onCanceled() {
78 | downloadTask=null;
79 | //取消下载,将前台服务通知关闭,并创建一个下载失败的通知
80 | stopForeground(true);
81 | Toast.makeText(DownloadService.this,"Download Canceled",Toast.LENGTH_SHORT).show();
82 | }
83 | };
84 |
85 | private DownloadBinder mBinder=new DownloadBinder();
86 |
87 | @Override
88 | public IBinder onBind(Intent intent) {
89 | return mBinder;
90 | }
91 |
92 | /**
93 | * 为了要让DownloadService可以和活动进行通信,我们创建了一个DownloadBinder对象
94 | */
95 | class DownloadBinder extends Binder{
96 |
97 | /**
98 | * 开始下载
99 | * @param url
100 | */
101 | public void startDownload(String url){
102 | if(downloadTask==null){
103 | downloadUrl=url;
104 | downloadTask=new DownloadTask(listener);
105 | //启动下载任务
106 | downloadTask.execute(downloadUrl);
107 | startForeground(1,getNotification("Downloading...",0));
108 | Toast.makeText(DownloadService.this, "Downloading...", Toast.LENGTH_SHORT).show();
109 | }
110 | }
111 |
112 | /**
113 | * 暂停下载
114 | */
115 | public void pauseDownload(){
116 | if(downloadTask!=null){
117 | downloadTask.pauseDownload();
118 | }
119 | }
120 |
121 | /**
122 | * 取消下载
123 | */
124 | public void cancelDownload(){
125 | if(downloadTask!=null){
126 | downloadTask.cancelDownload();
127 | }else {
128 | if(downloadUrl!=null){
129 | //取消下载时需要将文件删除,并将通知关闭
130 | String fileName=downloadUrl.substring(downloadUrl.lastIndexOf("/"));
131 | String directory= Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
132 | File file=new File(directory+fileName);
133 | if(file.exists()){
134 | file.delete();
135 | }
136 | getNotificationManager().cancel(1);
137 | stopForeground(true);
138 | Toast.makeText(DownloadService.this, "Canceled", Toast.LENGTH_SHORT).show();
139 | }
140 | }
141 |
142 | }
143 |
144 |
145 |
146 | }
147 |
148 | /**
149 | * 获取NotificationManager的实例,对通知进行管理
150 | * @return
151 | */
152 | private NotificationManager getNotificationManager(){
153 | return (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
154 | }
155 |
156 | /**
157 | *
158 | * @param title
159 | * @param progress
160 | * @return
161 | */
162 | private Notification getNotification(String title,int progress){
163 | Intent intent=new Intent(this,MainActivity.class);
164 | //PendingIntent是等待的Intent,这是跳转到一个Activity组件。当用户点击通知时,会跳转到MainActivity
165 | PendingIntent pi=PendingIntent.getActivity(this,0,intent,0);
166 | /**
167 | * 几乎Android系统的每一个版本都会对通知这部分功能进行获多或少的修改,API不稳定行问题在通知上面凸显的尤其严重。
168 | * 解决方案是:用support库中提供的兼容API。support-v4库中提供了一个NotificationCompat类,使用它可以保证我们的
169 | * 程序在所有的Android系统版本中都能正常工作。
170 | */
171 | NotificationCompat.Builder builder=new NotificationCompat.Builder(this);
172 | //设置通知的小图标
173 | builder.setSmallIcon(R.mipmap.ic_launcher);
174 | //设置通知的大图标,当下拉系统状态栏时,就可以看到设置的大图标
175 | builder.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher));
176 | //当通知被点击的时候,跳转到MainActivity中
177 | builder.setContentIntent(pi);
178 | //设置通知的标题
179 | builder.setContentTitle(title);
180 | if(progress>0){
181 | //当progress大于或等于0时,才需要显示下载进度
182 | builder.setContentText(progress+"%");
183 | builder.setProgress(100,progress,false);
184 | }
185 | return builder.build();
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/servicebestpractice/src/main/java/com/example/servicebestpractice/DownloadTask.java:
--------------------------------------------------------------------------------
1 | package com.example.servicebestpractice;
2 |
3 | import android.os.AsyncTask;
4 | import android.os.Environment;
5 |
6 | import java.io.File;
7 | import java.io.IOException;
8 | import java.io.InputStream;
9 | import java.io.RandomAccessFile;
10 |
11 | import okhttp3.OkHttpClient;
12 | import okhttp3.Request;
13 | import okhttp3.Response;
14 |
15 | /**
16 | * Created by Administrator on 2017/2/23.
17 | */
18 |
19 | /**
20 | * String 在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。
21 | * Integer 后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。
22 | * Integer 当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。
23 | */
24 | public class DownloadTask extends AsyncTask {
25 |
26 | public static final int TYPE_SUCCESS=0;
27 |
28 | public static final int TYPE_FAILED=1;
29 |
30 | public static final int TYPE_PAUSED=2;
31 |
32 | public static final int TYPE_CANCELED=3;
33 |
34 | private DownloadListener listener;
35 |
36 | private boolean isCanceled=false;
37 |
38 | private boolean isPaused=false;
39 |
40 | private int lastProgress;
41 |
42 | public DownloadTask(DownloadListener listener) {
43 | this.listener = listener;
44 | }
45 |
46 | /**
47 | * 这个方法中的所有代码都会在子线程中运行,我们应该在这里处理所有的耗时任务。
48 | * @param params
49 | * @return
50 | */
51 | @Override
52 | protected Integer doInBackground(String... params) {
53 | InputStream is=null;
54 | RandomAccessFile savedFile=null;
55 | File file=null;
56 | long downloadLength=0; //记录已经下载的文件长度
57 | //文件下载地址
58 | String downloadUrl=params[0];
59 | //下载文件的名称
60 | String fileName=downloadUrl.substring(downloadUrl.lastIndexOf("/"));
61 | //下载文件存放的目录
62 | String directory= Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
63 | //创建一个文件
64 | file=new File(directory+fileName);
65 | if(file.exists()){
66 | //如果文件存在的话,得到文件的大小
67 | downloadLength=file.length();
68 | }
69 | //得到下载内容的大小
70 | long contentLength=getContentLength(downloadUrl);
71 | if(contentLength==0){
72 | return TYPE_FAILED;
73 | }else if(contentLength==downloadLength){
74 | //已下载字节和文件总字节相等,说明已经下载完成了
75 | return TYPE_SUCCESS;
76 | }
77 | OkHttpClient client=new OkHttpClient();
78 | /**
79 | * HTTP请求是有一个Header的,里面有个Range属性是定义下载区域的,它接收的值是一个区间范围,
80 | * 比如:Range:bytes=0-10000。这样我们就可以按照一定的规则,将一个大文件拆分为若干很小的部分,
81 | * 然后分批次的下载,每个小块下载完成之后,再合并到文件中;这样即使下载中断了,重新下载时,
82 | * 也可以通过文件的字节长度来判断下载的起始点,然后重启断点续传的过程,直到最后完成下载过程。
83 | */
84 | Request request=new Request.Builder()
85 | .addHeader("RANGE","bytes="+downloadLength+"-") //断点续传要用到的,指示下载的区间
86 | .url(downloadUrl)
87 | .build();
88 | try {
89 | Response response=client.newCall(request).execute();
90 | if(response!=null){
91 | is=response.body().byteStream();
92 | savedFile=new RandomAccessFile(file,"rw");
93 | savedFile.seek(downloadLength);//跳过已经下载的字节
94 | byte[] b=new byte[1024];
95 | int total=0;
96 | int len;
97 | while((len=is.read(b))!=-1){
98 | if(isCanceled){
99 | return TYPE_CANCELED;
100 | }else if(isPaused){
101 | return TYPE_PAUSED;
102 | }else {
103 | total+=len;
104 | savedFile.write(b,0,len);
105 | //计算已经下载的百分比
106 | int progress=(int)((total+downloadLength)*100/contentLength);
107 | //注意:在doInBackground()中是不可以进行UI操作的,如果需要更新UI,比如说反馈当前任务的执行进度,
108 | //可以调用publishProgress()方法完成。
109 | publishProgress(progress);
110 | }
111 |
112 | }
113 | response.body().close();
114 | return TYPE_SUCCESS;
115 | }
116 | } catch (IOException e) {
117 | e.printStackTrace();
118 | }finally {
119 | try{
120 | if(is!=null){
121 | is.close();
122 | }
123 | if(savedFile!=null){
124 | savedFile.close();
125 | }
126 | if(isCanceled&&file!=null){
127 | file.delete();
128 | }
129 | }catch (Exception e){
130 | e.printStackTrace();
131 | }
132 | }
133 | return TYPE_FAILED;
134 | }
135 |
136 | /**
137 | * 当在后台任务中调用了publishProgress(Progress...)方法之后,onProgressUpdate()方法
138 | * 就会很快被调用,该方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以
139 | * 对界面进行相应的更新。
140 | * @param values
141 | */
142 | protected void onProgressUpdate(Integer...values){
143 | int progress=values[0];
144 | if(progress>lastProgress){
145 | listener.onProgress(progress);
146 | lastProgress=progress;
147 | }
148 | }
149 |
150 | /**
151 | * 当后台任务执行完毕并通过Return语句进行返回时,这个方法就很快被调用。返回的数据会作为参数
152 | * 传递到此方法中,可以利用返回的数据来进行一些UI操作。
153 | * @param status
154 | */
155 | @Override
156 | protected void onPostExecute(Integer status) {
157 | switch (status){
158 | case TYPE_SUCCESS:
159 | listener.onSuccess();
160 | break;
161 | case TYPE_FAILED:
162 | listener.onFailed();
163 | break;
164 | case TYPE_PAUSED:
165 | listener.onPaused();
166 | break;
167 | case TYPE_CANCELED:
168 | listener.onCanceled();
169 | break;
170 | default:
171 | break;
172 | }
173 | }
174 |
175 | public void pauseDownload(){
176 | isPaused=true;
177 | }
178 |
179 | public void cancelDownload(){
180 | isCanceled=true;
181 | }
182 |
183 | /**
184 | * 得到下载内容的大小
185 | * @param downloadUrl
186 | * @return
187 | */
188 | private long getContentLength(String downloadUrl){
189 | OkHttpClient client=new OkHttpClient();
190 | Request request=new Request.Builder().url(downloadUrl).build();
191 | try {
192 | Response response=client.newCall(request).execute();
193 | if(response!=null&&response.isSuccessful()){
194 | long contentLength=response.body().contentLength();
195 | response.body().close();
196 | return contentLength;
197 | }
198 | } catch (IOException e) {
199 | e.printStackTrace();
200 | }
201 | return 0;
202 | }
203 |
204 | }
205 |
--------------------------------------------------------------------------------
/servicebestpractice/src/main/java/com/example/servicebestpractice/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.servicebestpractice;
2 |
3 | import android.Manifest;
4 | import android.content.ComponentName;
5 | import android.content.Intent;
6 | import android.content.ServiceConnection;
7 | import android.content.pm.PackageManager;
8 | import android.os.Bundle;
9 | import android.os.IBinder;
10 | import android.support.v4.app.ActivityCompat;
11 | import android.support.v4.content.ContextCompat;
12 | import android.support.v7.app.AppCompatActivity;
13 | import android.view.View;
14 | import android.widget.Button;
15 | import android.widget.Toast;
16 |
17 | public class MainActivity extends AppCompatActivity implements View.OnClickListener{
18 |
19 | private DownloadService.DownloadBinder downloadBinder;
20 |
21 | private ServiceConnection connection=new ServiceConnection() {
22 |
23 | @Override
24 | public void onServiceConnected(ComponentName name, IBinder service) {
25 | downloadBinder=(DownloadService.DownloadBinder) service;
26 | }
27 |
28 | @Override
29 | public void onServiceDisconnected(ComponentName name) {
30 |
31 | }
32 | };
33 |
34 | @Override
35 | protected void onCreate(Bundle savedInstanceState) {
36 | super.onCreate(savedInstanceState);
37 | setContentView(R.layout.activity_main);
38 | Button startDownload=(Button) findViewById(R.id.start_download);
39 | startDownload.setOnClickListener(this);
40 | Button pauseDownload=(Button) findViewById(R.id.pause_download);
41 | pauseDownload.setOnClickListener(this);
42 | Button cancelDownload=(Button)findViewById(R.id.cancel_download);
43 | cancelDownload.setOnClickListener(this);
44 | Intent intent=new Intent(this,DownloadService.class);
45 | //这一点至关重要,因为启动服务可以保证DownloadService一直在后台运行,绑定服务则可以让MaiinActivity和DownloadService
46 | //进行通信,因此两个方法的调用都必不可少。
47 | startService(intent); //启动服务
48 | bindService(intent,connection,BIND_AUTO_CREATE);//绑定服务
49 | /**
50 | *运行时权限处理:我们需要再用到权限的地方,每次都要检查是否APP已经拥有权限
51 | * 下载功能,需要些SD卡的权限,我们在写入之前检查是否有WRITE_EXTERNAL_STORAGE权限,没有则申请权限
52 | */
53 | if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!=PackageManager.PERMISSION_GRANTED){
54 | ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
55 | }
56 | }
57 |
58 | public void onClick(View v){
59 | if(downloadBinder==null){
60 | return;
61 | }
62 | switch (v.getId()){
63 | case R.id.start_download:
64 | //String url="http://raw.githubusercontent.com/guolindev/eclipse/master/eclipse-inst-win64.exe";
65 | String url="http://10.0.2.2:8080/ChromeSetup.exe";
66 | downloadBinder.startDownload(url);
67 | break;
68 | case R.id.pause_download:
69 | downloadBinder.pauseDownload();
70 | break;
71 | case R.id.cancel_download:
72 | downloadBinder.cancelDownload();
73 | break;
74 | default:
75 | break;
76 | }
77 | }
78 |
79 | /**
80 | * 用户选择允许或拒绝后,会回调onRequestPermissionsResult
81 | * @param requestCode 请求码
82 | * @param permissions
83 | * @param grantResults 授权结果
84 | */
85 | @Override
86 | public void onRequestPermissionsResult(int requestCode,String[] permissions,int[] grantResults) {
87 | switch (requestCode){
88 | case 1:
89 | if(grantResults.length>0&&grantResults[0]!= PackageManager.PERMISSION_GRANTED){
90 | Toast.makeText(this,"拒绝权限将无法使用程序",Toast.LENGTH_SHORT).show();
91 | finish();
92 | }
93 | break;
94 | }
95 | }
96 |
97 | @Override
98 | protected void onDestroy() {
99 | super.onDestroy();
100 | //解除绑定服务
101 | unbindService(connection);
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/servicebestpractice/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
20 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/servicebestpractice/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Microstrong0305/OKHttp_DownloadFile/f50aca792f67de32e1f098c97eec09016204a026/servicebestpractice/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/servicebestpractice/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Microstrong0305/OKHttp_DownloadFile/f50aca792f67de32e1f098c97eec09016204a026/servicebestpractice/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/servicebestpractice/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Microstrong0305/OKHttp_DownloadFile/f50aca792f67de32e1f098c97eec09016204a026/servicebestpractice/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/servicebestpractice/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Microstrong0305/OKHttp_DownloadFile/f50aca792f67de32e1f098c97eec09016204a026/servicebestpractice/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/servicebestpractice/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Microstrong0305/OKHttp_DownloadFile/f50aca792f67de32e1f098c97eec09016204a026/servicebestpractice/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/servicebestpractice/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/servicebestpractice/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/servicebestpractice/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/servicebestpractice/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ServiceBestPractice
3 |
4 |
--------------------------------------------------------------------------------
/servicebestpractice/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/servicebestpractice/src/test/java/com/example/servicebestpractice/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.example.servicebestpractice;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------