00.1 ANDROID 面向对象的六大原则——单一职责原则

返回设计模式博客目录
|
第一篇:单一职责原则
第二篇:开闭原则
第三篇:里氏替换原则
第四篇:依赖倒置原则
第五篇:接口隔离原则
第六篇:迪米特原则

请实现一个简易的图片加载器(ImageLoader)?

以下是一个新手实现的图片加载器源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
package com.xxt.xtest;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.LruCache;
import android.widget.ImageView;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ImageLoader {
// 图片缓存
LruCache<String, Bitmap> mImageCache;
// 线程池,线程数量为 CPU 的数量
ExecutorService mExecutorService = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors());
// UI Handler
Handler mUiHandler = new Handler(Looper.getMainLooper());
public ImageLoader() {
initImageCache();
}
/**
* 初始化内存缓存
*/
private void initImageCache() {
// 计算可用的最大内存
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 取四分之一的可用内存作为缓存
final int cacheSize = maxMemory / 4;
mImageCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
}
};
}
/**
* 加载指定 url 的图片并显示
*/
public void displayImage(final String url, final ImageView imageView) {
// 先从内存中找
Bitmap bmp = mImageCache.get(url);
if (bmp != null) {
imageView.setImageBitmap(bmp);
return;
}
// 内存中没有,去加载
imageView.setTag(url);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downloadImage(url);
if (bitmap == null) return;
if (url.equals(imageView.getTag())) {
updateImageView(imageView, bitmap);
}
mImageCache.put(url, bitmap);
}
});
}
/**
* 通知界面更新显示图片
* @param imageView ImageView
* @param bitmap 位图
*/
private void updateImageView(final ImageView imageView,
final Bitmap bitmap) {
mUiHandler.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bitmap);
}
});
}
/**
* 下载指定 url 的图片
* @param imageUrl 图片链接
* @return Bitmap
*/
private Bitmap downloadImage(String imageUrl) {
if (TextUtils.isEmpty(imageUrl)) return null;
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(conn.getInputStream());
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
}

测试代码:

1
2
3
4
5
// 动态申请权限
requestRuntimePermission(new String[]{"android.permission.WRITE_EXTERNAL_STORAGE"}, null);
String url = "https://upload-images.jianshu.io/upload_images/14186083-9bb468395ee3d048.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240";
ImageView imageView = findViewById(R.id.image);
new ImageLoader().displayImage(url, imageView);

图片加载器

上述代码虽然满足功能需求,但是所有的功能代码都写在一个类中,这样随着功能的增多,ImageLoader 类会越来越大,代码也越来越负责,图片加载系统就越来越脆弱……

我们可以参照单一职责原则,把 ImageLoader 拆分一下,让各个功能独立出来:

  • ImageCache:用于处理图片缓存。
  • ImageUtil:图片工具类,如获取图片大小、下载图片等。

改进后的源码:
ImageLoader.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
package com.xxt.xtest;
import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Looper;
import android.widget.ImageView;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ImageLoader {
// 图片缓存
private ImageCache mImageCache = new ImageCache();
// 线程池,线程数量为 CPU 的数量
private ExecutorService mExecutorService = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors());
// UI Handler
private Handler mUiHandler = new Handler(Looper.getMainLooper());
/**
* 加载指定 url 的图片并显示
*/
public void displayImage(final String url, final ImageView imageView) {
// 先从内存中找
Bitmap bmp = mImageCache.get(url);
if (bmp != null) {
imageView.setImageBitmap(bmp);
return;
}
// 内存中没有,去加载
imageView.setTag(url);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = ImageUtil.downloadImage(url);
if (bitmap == null) return;
if (url.equals(imageView.getTag())) {
updateImageView(imageView, bitmap);
}
mImageCache.put(url, bitmap);
}
});
}
/**
* 通知界面更新显示图片
* @param imageView ImageView
* @param bitmap 位图
*/
private void updateImageView(final ImageView imageView,
final Bitmap bitmap) {
mUiHandler.post(new Runnable() {
@Override
public void run() {
imageView.setImageBitmap(bitmap);
}
});
}
}

ImageCache.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.xxt.xtest;
import android.graphics.Bitmap;
import androidx.annotation.NonNull;
import androidx.collection.LruCache;
public class ImageCache {
// 图片缓存
private LruCache<String, Bitmap> mImageCache;
public ImageCache() {
initImageCache();
}
private void initImageCache() {
// 计算可使用的最大内存
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 取四分之一的可用内存作为缓存
final int cacheSize = maxMemory / 4;
mImageCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(@NonNull String key, @NonNull Bitmap value) {
return ImageUtil.getBitmapSize(value) / 1024;
}
};
}
public void put(@NonNull String url, @NonNull Bitmap bitmap) {
mImageCache.put(url, bitmap);
}
public Bitmap get(@NonNull String url) {
return mImageCache.get(url);
}
}

ImageUtil.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
package com.xxt.xtest;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.text.TextUtils;
import java.net.HttpURLConnection;
import java.net.URL;
public class ImageUtil {
/**
* 下载图片
* @param imageUrl 图片链接
* @return Bitmap
*/
public static Bitmap downloadImage(String imageUrl) {
if (TextUtils.isEmpty(imageUrl)) {
return null;
}
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(conn.getInputStream());
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
/**
* 计算图片大小
* @param bitmap 图片
* @return int
*/
public static int getBitmapSize(Bitmap bitmap) {
if (bitmap == null) {
return 0;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return bitmap.getAllocationByteCount();
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
return bitmap.getByteCount();
}
return bitmap.getRowBytes() * bitmap.getHeight();
}
}

单一职责原则概述


应该有且仅有一个原因引起类的变更 (There should never be more than one reason for a class to change)。

单一职责原则为我们提供了一个编写程序的准则,要求我们在编写类,抽象类,接口时,要使其功能职责单一纯碎,将导致其变更的因素缩减到最少。

如果一个类承担的职责过多,就等于把这些职责耦合在一起。一个职责的变化可能会影响或损坏其他职责的功能。而且职责越多,这个类变化的几率就会越大,类的稳定性就会越低。

在软件开发中,经常会遇到一个功能类 T 负责两个不同的职责:职责 P1,职责 P2。现因需求变更需要更改职责 P1 来满足新的业务需求,当我们实现完成后,发现因更改职责 P1 竟导致原本能够正常运行的职责 P2 发生故障。而修复职责 P2 又不得不更改职责 P1 的逻辑,这便是因为功能类 T 的职责不够单一,职责 P1 与职责 P2 耦合在一起导致的。

附:动态权限申请代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
package com.xxt.xtest;
import android.content.pm.PackageManager;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.util.ArrayList;
import java.util.List;
public class BaseActivity extends AppCompatActivity {
private PermissionListener mListener;
/**
* 权限申请
* @param permissions 待申请的权限集合
* @param listener 申请结果监听事件
*/
protected void requestRuntimePermission(String[] permissions,
PermissionListener listener){
this.mListener = listener;
// 用于存放为授权的权限
List<String> permissionList = new ArrayList<>();
// 遍历传递过来的权限集合
for (String permission : permissions) {
// 判断是否已经授权
if (ContextCompat.checkSelfPermission(this,permission)
!= PackageManager.PERMISSION_GRANTED){
// 未授权,则加入待授权的权限集合中
permissionList.add(permission);
}
}
// 判断集合,如果集合不为空,则需要去授权
if (!permissionList.isEmpty()) {
ActivityCompat.requestPermissions(this,
permissionList.toArray(new String[permissionList.size()]),
1);
}
// 为空,则已经全部授权
else {
if (listener != null) {
listener.onGranted();
}
}
}
/**
* 权限申请结果
* @param requestCode 请求码
* @param permissions 所有的权限集合
* @param grantResults 授权结果集合
*/
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions,
grantResults);
if (requestCode == 1 && grantResults.length > 0) {
// 被用户拒绝的权限集合
List<String> deniedPermissions = new ArrayList<>();
// 用户通过的权限集合
List<String> grantedPermissions = new ArrayList<>();
for (int i = 0; i < grantResults.length; i++) {
// 获取授权结果,这是一个int类型的值
int grantResult = grantResults[i];
// 用户拒绝授权的权限
if (grantResult != PackageManager.PERMISSION_GRANTED){
String permission = permissions[i];
deniedPermissions.add(permission);
}
// 用户同意的权限
else {
String permission = permissions[i];
grantedPermissions.add(permission);
}
}
// 用户拒绝权限为空
if (deniedPermissions.isEmpty()) {
if(mListener != null){
mListener.onGranted();
}
}
// 不为空
else {
if(mListener != null){
// 回调授权成功的接口
mListener.onDenied(deniedPermissions);
// 回调授权失败的接口
mListener.onGranted(grantedPermissions);
mListener.onDenied();
}
}
}
}
}

PermissionListener.java

1
2
3
4
5
6
7
8
9
10
public interface PermissionListener {
// 授权成功
void onGranted();
// 授权部分
void onGranted(List<String> grantedPermission);
// 拒绝授权
void onDenied(List<String> deniedPermission);
// 授权失败
void onDenied();
}