Android项目实践系列(二) - 深度定制Logger
日志信息对于程序员的作用我就不多说了,好的日志信息对于程序的调试,调优有着不可小觑的作用。随着项目的进展,你可能越发觉得安卓自带的日志类 android.util.Log 不能满足项目的需求,需要深度定制适合自己项目的 Logger 。
如果你有以下的需求:
1. 将 Android 日志信息记录到一个文件里面
2. 深度定制化或者控制日志的显示,例如在发布产品后,考虑到性能,你需要阻止所有的日志信息
那么该文章将尤其适合你。 本文章我将整合使用 android.util.Log 以及经典的 log4j 进行深度定制化日志管理。
项目准备:
0. Android开发环境(Eclipse + ADT + AndroidSDK + ...)
1. 下载 log4j JAR包
2. 阅读相关文档,设置好开发环境(导入 log4j JAR包)
项目说明:
该项目主要包括三个类:
1. CLogger.java
2. CLogConfiguration.java
3. CLoggerHelper.java
这一小节中,我们将创建一个 Android-Logger 的 Android Project ,需要实现程序员可以控制 LogCat 中打印日志的类型。
CLogger.java -> 用于自定义 Logger ,封装 android.util.Log
CLogConfiguration.java -> 用于定义 Logger 的配置信息
CLogger中我们定义的类型Level级别: VERBOSE < DEBUG < INFO < EARN < ERROR < What a Terrible Failure
首先我们看一下测试类 LoggerActivity.java
2
3 private static final String TAG = "LoggerActivity";
4
5 @Override
6 public void onCreate(Bundle savedInstanceState) {
7 super.onCreate(savedInstanceState);
8 setContentView(R.layout.activity_logger);
9
10 CLogConfiguration logConfiguration = new CLogConfiguration();
11 logConfiguration.setLogcatLogShownLevel(Level.ERROR_INT);
12 CLogger.setLogConfiguration(logConfiguration);
13
14 CLogger.v(TAG, "this is a VERBOSE log");
15 CLogger.d(TAG, "this is a DEBUG log");
16 CLogger.i(TAG, "this is a INFO log");
17 CLogger.w(TAG, "this is a WARN log");
18 CLogger.e(TAG, "this is a ERROR log");
19 CLogger.wtf(TAG, "this is a What a Terrible Failure log");
20 }
21 }
备注:
1. 第 14 ~ 19 行定义需要测试的 CLogger 类型
2. 第 12 行将 CLogConfiguration 信息设置到 CLogger 中
3. 第 10 ~ 11 行定义 CLogConfiguration 对象, 此处 logConfiguration.setLogcatLogShownLevel(Level.ERROR_INT), 那么只显示 "this is a ERROR log" 和 "this is a What a Terrible Failure log"。 如果将 "Level.ERROR_INT" 改为 "Level.DEBUG_INT",那么将会显示 "this is a DEBUG log", "this is a INFO log", "this is a WARN log", "this is a ERROR log", "this is a What a Terrible Failure log"。
然后我们看一下 CLogger.java 类
2 private static CLogConfiguration logConfiguration = new CLogConfiguration();
3
4 public static void setLogConfiguration(CLogConfiguration logConfiguration) {
5 CLogger.logConfiguration = logConfiguration;
6 }
7
8 public static void configure() {
9 logConfiguration.configure();
10 }
11
12 protected static String generateMessage(String tag, String msg) {
13 tag = tag == null ? "" : tag;
14 msg = msg == null ? "" : msg;
15 return "(" + tag + ") " + msg;
16 }
17
18 /**
19 * Send a VERBOSE log message and log the exception.
20 *
21 * @param tag
22 * Used to identify the source of a log message.
23 * @param msg
24 * The message you would like logged.
25 */
26 public static int v(String tag, String msg) {
27 if (logConfiguration.getLogcatLogShownLevel() < Level.DEBUG_INT) {
28 return Log.v(tag, msg);
29 }
30 return 0;
31 }
32
33 /**
34 * Send a VERBOSE log message and log the exception.
35 *
36 * @param tag
37 * Used to identify the source of a log message.
38 * @param msg
39 * The message you would like logged.
40 * @param tr
41 * An exception to log.
42 */
43 public static int v(String tag, String msg, Throwable tr) {
44 if (logConfiguration.getLogcatLogShownLevel() < Level.DEBUG_INT) {
45 return Log.v(tag, msg, tr);
46 }
47 return 0;
48 }
49
50 /**
51 * Send a DEBUG log message and log the exception.
52 *
53 * @param tag
54 * Used to identify the source of a log message.
55 * @param msg
56 * The message you would like logged.
57 */
58 public static int d(String tag, String msg) {
59 if (logConfiguration.getLogcatLogShownLevel() < Level.INFO_INT) {
60 return Log.d(tag, msg);
61 }
62 return 0;
63 }
64
65 /**
66 * Send a DEBUG log message and log the exception.
67 *
68 * @param tag
69 * Used to identify the source of a log message.
70 * @param msg
71 * The message you would like logged.
72 * @param tr
73 * An exception to log.
74 */
75 public static int d(String tag, String msg, Throwable tr) {
76 if (logConfiguration.getLogcatLogShownLevel() < Level.INFO_INT) {
77 return Log.d(tag, msg, tr);
78 }
79 return 0;
80 }
81
82 /**
83 * Send a INFO log message and log the exception.
84 *
85 * @param tag
86 * Used to identify the source of a log message.
87 * @param msg
88 * The message you would like logged.
89 */
90 public static int i(String tag, String msg) {
91 if (logConfiguration.getLogcatLogShownLevel() < Level.WARN_INT) {
92 return Log.i(tag, msg);
93 }
94 return 0;
95 }
96
97 /**
98 * Send a INFO log message and log the exception.
99 *
100 * @param tag
101 * Used to identify the source of a log message.
102 * @param msg
103 * The message you would like logged.
104 * @param tr
105 * An exception to log.
106 */
107 public static int i(String tag, String msg, Throwable tr) {
108 if (logConfiguration.getLogcatLogShownLevel() < Level.WARN_INT) {
109 return Log.i(tag, msg, tr);
110 }
111 return 0;
112 }
113
114 /**
115 * Send a WARN log message and log the exception.
116 *
117 * @param tag
118 * Used to identify the source of a log message.
119 * @param msg
120 * The message you would like logged.
121 */
122 public static int w(String tag, String msg) {
123 if (logConfiguration.getLogcatLogShownLevel() < Level.ERROR_INT) {
124 return Log.w(tag, msg);
125 }
126 return 0;
127 }
128
129 /**
130 * Send a WARN log message and log the exception.
131 *
132 * @param tag
133 * Used to identify the source of a log message.
134 * @param msg
135 * The message you would like logged.
136 * @param tr
137 * An exception to log.
138 */
139 public static int w(String tag, String msg, Throwable tr) {
140 if (logConfiguration.getLogcatLogShownLevel() < Level.ERROR_INT) {
141 return Log.w(tag, msg, tr);
142 }
143 return 0;
144 }
145
146 /**
147 * Send a ERROR log message and log the exception.
148 *
149 * @param tag
150 * Used to identify the source of a log message.
151 * @param msg
152 * The message you would like logged.
153 */
154 public static int e(String tag, String msg) {
155 return Log.e(tag, msg);
156 }
157
158 /**
159 * Send a ERROR log message and log the exception.
160 *
161 * @param tag
162 * Used to identify the source of a log message.
163 * @param msg
164 * The message you would like logged.
165 * @param tr
166 * An exception to log.
167 */
168 public static int e(String tag, String msg, Throwable tr) {
169 return Log.e(tag, msg, tr);
170 }
171
172 /**
173 * What a Terrible Failure: Report a condition that should never happen.
174 *
175 * @param tag
176 * Used to identify the source of a log message.
177 * @param tr
178 * An exception to log.
179 */
180 public static int wtf(String tag, Throwable tr) {
181 return Log.wtf(tag, tr);
182 }
183
184 /**
185 * What a Terrible Failure: Report a condition that should never happen.
186 *
187 * @param tag
188 * Used to identify the source of a log message.
189 * @param msg
190 * he message you would like logged.
191 */
192 public static int wtf(String tag, String msg) {
193 return Log.wtf(tag, msg);
194 }
195
196 /**
197 * What a Terrible Failure: Report a condition that should never happen.
198 *
199 * @param tag
200 * Used to identify the source of a log message.
201 * @param msg
202 * he message you would like logged.
203 * @param tr
204 * An exception to log. May be null.
205 */
206 public static int wtf(String tag, String msg, Throwable tr) {
207 return Log.wtf(tag, msg, tr);
208 }
209
210 }
备注:
1. 此类中我们主要需要关注 "logConfiguration.getLogcatLogShownLevel() < Level.*_INT"
2. 此类中 CLogger.e() 和 CLogger.wtf() 的信息默认都会打印出来,不受控制
最后我们来关注下类 CLogConfiguration.java
2
3 private int logcatLogShownLevel = Level.DEBUG_INT;
4
5 public void configure() {
6
7 }
8
9 public int getLogcatLogShownLevel() {
10 return logcatLogShownLevel;
11 }
12
13 public void setLogcatLogShownLevel(int logcatLogShownLevel) {
14 this.logcatLogShownLevel = logcatLogShownLevel;
15 }
16
17 }
备注: 当前我们只定义了logcatLogShownLevel 属性,改属性用于控制Logcat控制台显示日志的类型,默认显示Debug以上的信息
开头语: 首先我想介绍下,如何使用 Android Project 自带的模拟器查看手机设备中生成的文件(夹),以及如何查看文件内容。如果你已经知道,可以略过此开头语。
如何查看模拟器中生成的文件(夹) ?
-> Eclipse > Window > Open Perspective > Others 选择 DDMS 视图, 你可以在File Explorer 下看到模拟器中所有的文件夹和文件
如何查看模拟器中生成的文件的内容?
-> 选择你要查看的文件 > Pull a file from the device
这一章节,是在上一章节的基础上进行扩展的,原有代码略有改动,而且加入了 CLoggerHelper.java 类,因此你需要在了解了上一章节的基础上学习本章节。
同样我们从LoggerActivity.java开始
2
3 private static final String TAG = "LoggerActivity";
4
5 @Override
6 public void onCreate(Bundle savedInstanceState) {
7 super.onCreate(savedInstanceState);
8 setContentView(R.layout.activity_logger);
9
10 CLogConfiguration logConfiguration = new CLogConfiguration();
11 logConfiguration.setLogFileFolderPath(CLoggerHelper.getApkDeviceAbsolutePath() + "cc");
12 logConfiguration.setLogFileName("mobile.log");
13 logConfiguration.setLogcatLogShownLevelInt(Level.ERROR_INT);
14 CLogger.setLogConfiguration(logConfiguration);
15 CLogger.configure();
16
17 CLogger.v(TAG, "this is a VERBOSE log");
18 CLogger.d(TAG, "this is a DEBUG log");
19 CLogger.i(TAG, "this is a INFO log");
20 CLogger.w(TAG, "this is a WARN log");
21 CLogger.e(TAG, "this is a ERROR log");
22 CLogger.wtf(TAG, "this is a What a Terrible Failure log");
23 }
24 }
备注:
1. 可以看到代码添加了 第 11,12,15 行用于配置将日志写入手机文件中所需要的信息
2. 第 11,12 行分别用于设置日志文件存放的路径以及日志文件的名称
3. 第 15 行是不可省略的, 用于具体的配置
4. 此文件中同样可以设置何种类别下记录日志信息, 例如 logConfiguration.setLogFileWrittenLevel(Level.DEBUG); 将日志记录的级别从默认的ERROR级别设置为DEBUG级别
其次我们来看看CLogger.java 和 CLoggerHelper.java
2
3 private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger("AndroidLogger");
4 private static CLogConfiguration logConfiguration = new CLogConfiguration();
5
6 public static void setLogConfiguration(CLogConfiguration logConfiguration) {
7 CLogger.logConfiguration = logConfiguration;
8 }
9
10 public static void configure() {
11 logConfiguration.configure();
12 }
13
14 protected static String generateMessage(String tag, String msg) {
15 tag = tag == null ? "" : tag;
16 msg = msg == null ? "" : msg;
17 return "(" + tag + ") " + msg;
18 }
19
20 /**
21 * Send a VERBOSE log message and log the exception.
22 *
23 * @param tag
24 * Used to identify the source of a log message.
25 * @param msg
26 * The message you would like logged.
27 */
28 public static int v(String tag, String msg) {
29
30 if (logConfiguration.getLogcatLogShownLevelInt() < Level.DEBUG_INT) {
31 return Log.v(tag, msg);
32 }
33 return 0;
34 }
35
36 /**
37 * Send a VERBOSE log message and log the exception.
38 *
39 * @param tag
40 * Used to identify the source of a log message.
41 * @param msg
42 * The message you would like logged.
43 * @param tr
44 * An exception to log.
45 */
46 public static int v(String tag, String msg, Throwable tr) {
47 if (logConfiguration.getLogcatLogShownLevelInt() < Level.DEBUG_INT) {
48 return Log.v(tag, msg, tr);
49 }
50 return 0;
51 }
52
53 /**
54 * Send a DEBUG log message and log the exception.
55 *
56 * @param tag
57 * Used to identify the source of a log message.
58 * @param msg
59 * The message you would like logged.
60 */
61 public static int d(String tag, String msg) {
62 if (logConfiguration.getLogcatLogShownLevelInt() < Level.INFO_INT) {
63 return Log.d(tag, msg);
64 }
65 return 0;
66 }
67
68 /**
69 * Send a DEBUG log message and log the exception.
70 *
71 * @param tag
72 * Used to identify the source of a log message.
73 * @param msg
74 * The message you would like logged.
75 * @param tr
76 * An exception to log.
77 */
78 public static int d(String tag, String msg, Throwable tr) {
79 if (logConfiguration.getLogcatLogShownLevelInt() < Level.INFO_INT) {
80 return Log.d(tag, msg, tr);
81 }
82 return 0;
83 }
84
85 /**
86 * Send a INFO log message and log the exception.
87 *
88 * @param tag
89 * Used to identify the source of a log message.
90 * @param msg
91 * The message you would like logged.
92 */
93 public static int i(String tag, String msg) {
94 log.info(generateMessage(tag, msg));
95 if (logConfiguration.getLogcatLogShownLevelInt() < Level.WARN_INT) {
96 return Log.i(tag, msg);
97 }
98 return 0;
99 }
100
101 /**
102 * Send a INFO log message and log the exception.
103 *
104 * @param tag
105 * Used to identify the source of a log message.
106 * @param msg
107 * The message you would like logged.
108 * @param tr
109 * An exception to log.
110 */
111 public static int i(String tag, String msg, Throwable tr) {
112 log.info(generateMessage(tag, msg), tr);
113 if (logConfiguration.getLogcatLogShownLevelInt() < Level.WARN_INT) {
114 return Log.i(tag, msg, tr);
115 }
116 return 0;
117 }
118
119 /**
120 * Send a WARN log message and log the exception.
121 *
122 * @param tag
123 * Used to identify the source of a log message.
124 * @param msg
125 * The message you would like logged.
126 */
127 public static int w(String tag, String msg) {
128 log.warn(generateMessage(tag, msg));
129 if (logConfiguration.getLogcatLogShownLevelInt() < Level.ERROR_INT) {
130 return Log.w(tag, msg);
131 }
132 return 0;
133 }
134
135 /**
136 * Send a WARN log message and log the exception.
137 *
138 * @param tag
139 * Used to identify the source of a log message.
140 * @param msg
141 * The message you would like logged.
142 * @param tr
143 * An exception to log.
144 */
145 public static int w(String tag, String msg, Throwable tr) {
146 log.warn(generateMessage(tag, msg), tr);
147 if (logConfiguration.getLogcatLogShownLevelInt() < Level.ERROR_INT) {
148 return Log.w(tag, msg, tr);
149 }
150 return 0;
151 }
152
153 /**
154 * Send a ERROR log message and log the exception.
155 *
156 * @param tag
157 * Used to identify the source of a log message.
158 * @param msg
159 * The message you would like logged.
160 */
161 public static int e(String tag, String msg) {
162 log.error(generateMessage(tag, msg));
163 return Log.e(tag, msg);
164 }
165
166 /**
167 * Send a ERROR log message and log the exception.
168 *
169 * @param tag
170 * Used to identify the source of a log message.
171 * @param msg
172 * The message you would like logged.
173 * @param tr
174 * An exception to log.
175 */
176 public static int e(String tag, String msg, Throwable tr) {
177 log.error(generateMessage(tag, msg), tr);
178 return Log.e(tag, msg, tr);
179 }
180
181 /**
182 * What a Terrible Failure: Report a condition that should never happen.
183 *
184 * @param tag
185 * Used to identify the source of a log message.
186 * @param tr
187 * An exception to log.
188 */
189 public static int wtf(String tag, Throwable tr) {
190 log.fatal(generateMessage(tag, ""), tr);
191 return Log.wtf(tag, tr);
192 }
193
194 /**
195 * What a Terrible Failure: Report a condition that should never happen.
196 *
197 * @param tag
198 * Used to identify the source of a log message.
199 * @param msg
200 * he message you would like logged.
201 */
202 public static int wtf(String tag, String msg) {
203 log.fatal(generateMessage(tag, msg));
204 return Log.wtf(tag, msg);
205 }
206
207 /**
208 * What a Terrible Failure: Report a condition that should never happen.
209 *
210 * @param tag
211 * Used to identify the source of a log message.
212 * @param msg
213 * he message you would like logged.
214 * @param tr
215 * An exception to log. May be null.
216 */
217 public static int wtf(String tag, String msg, Throwable tr) {
218 log.fatal(generateMessage(tag, msg), tr);
219 return Log.wtf(tag, msg, tr);
220 }
221
222 }
2 private static String apkDeviceAbsolutePath = Environment.getExternalStorageDirectory() + File.separator;
3 private static String apkDeviceRelativePath = "sdcard" + File.separator;
4
5 public static String getApkDeviceAbsolutePath() {
6 return apkDeviceAbsolutePath;
7 }
8
9 public static String getApkDeviceRelativePath() {
10 return apkDeviceRelativePath;
11 }
12 }
备注:
1. 在CLogger.java文件中, 你需要关注下 private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger("AndroidLogger");
2. 类 CLoggerHelper 主要用于获取设备的绝对和相对路径(即手机内存根目录和内存卡根目录)。
最后最重要的当然是CLogConfiguration.java
2
3 private int logcatLogShownLevelInt = Level.DEBUG_INT;
4
5 private Level logFileWrittenLevel = Level.ERROR;
6
7 private int logFileWrittenLevelInt = Level.ERROR_INT;
8 private String logFilePattern = "%d{yyyy-MM-dd HH:mm:ss.SSS} %p %c:%L - %m%n";
9 private int maxBackupIndex = 5;
10 private long maxFileSize = 524288L;// 512K //1048576L; // 1MB
11 private boolean immediateFlush = true;
12 private boolean useFileAppender = true;
13 private boolean resetConfiguration = true;
14 private boolean internalDebugging = false;
15
16 private String logFileFolderPath = "";
17 private String logFileName = "";
18
19 public CLogConfiguration() {
20
21 }
22
23 public void configure() {
24 org.apache.log4j.Logger root = org.apache.log4j.Logger.getRootLogger();
25
26 if (isResetConfiguration()) {
27 LogManager.getLoggerRepository().resetConfiguration();
28 }
29
30 LogLog.setInternalDebugging(internalDebugging);
31
32 if (isUseFileAppender() && createFolderIfNotExisted()) {
33 configureFileAppender(root);
34 }
35
36 root.setLevel(logFileWrittenLevel);
37 }
38
39 private boolean createFolderIfNotExisted() {
40 File dir = new File(logFileFolderPath);
41 if (!dir.exists()) {
42 if (dir.mkdirs()) {
43 return true;
44 } else {
45 return false;
46 }
47 }
48 return true;
49 }
50
51 private void configureFileAppender(org.apache.log4j.Logger root) {
52 Layout fileLayout = new PatternLayout(getLogFilePattern());
53 RollingFileAppender rollingFileAppender;
54 try {
55 rollingFileAppender = new RollingFileAppender(fileLayout, getFileFullPath());
56 } catch (IOException e) {
57 Log.e("CLogConfiguration", "rollingFileAppender error -> ", e);
58 return;
59 }
60
61 rollingFileAppender.setMaxBackupIndex(getMaxBackupIndex());
62 rollingFileAppender.setMaximumFileSize(getMaxFileSize());
63 rollingFileAppender.setImmediateFlush(isImmediateFlush());
64
65 root.addAppender(rollingFileAppender);
66 }
67
68 private String getFileFullPath() {
69 return logFileFolderPath + File.separator + logFileName;
70 }
71
72 public int getLogcatLogShownLevelInt() {
73 return logcatLogShownLevelInt;
74 }
75
76 public void setLogcatLogShownLevelInt(int logcatLogShownLevelInt) {
77 this.logcatLogShownLevelInt = logcatLogShownLevelInt;
78 }
79
80 public boolean isResetConfiguration() {
81 return resetConfiguration;
82 }
83
84 public void setResetConfiguration(boolean resetConfiguration) {
85 this.resetConfiguration = resetConfiguration;
86 }
87
88 public boolean isInternalDebugging() {
89 return internalDebugging;
90 }
91
92 public void setInternalDebugging(boolean internalDebugging) {
93 this.internalDebugging = internalDebugging;
94 }
95
96 public Level getLogFileWrittenLevel() {
97 return logFileWrittenLevel;
98 }
99
100 public void setLogFileWrittenLevel(Level logFileWrittenLevel) {
101 this.logFileWrittenLevel = logFileWrittenLevel;
102 }
103
104 public int getMaxBackupIndex() {
105 return maxBackupIndex;
106 }
107
108 public void setMaxBackupIndex(int maxBackupIndex) {
109 this.maxBackupIndex = maxBackupIndex;
110 }
111
112 public long getMaxFileSize() {
113 return maxFileSize;
114 }
115
116 public void setMaxFileSize(long maxFileSize) {
117 this.maxFileSize = maxFileSize;
118 }
119
120 public boolean isImmediateFlush() {
121 return immediateFlush;
122 }
123
124 public void setImmediateFlush(boolean immediateFlush) {
125 this.immediateFlush = immediateFlush;
126 }
127
128 public boolean isUseFileAppender() {
129 return useFileAppender;
130 }
131
132 public void setUseFileAppender(boolean useFileAppender) {
133 this.useFileAppender = useFileAppender;
134 }
135
136 public String getLogFileFolderPath() {
137 return logFileFolderPath;
138 }
139
140 public void setLogFileFolderPath(String logFileFolderPath) {
141 this.logFileFolderPath = logFileFolderPath;
142 }
143
144 public String getLogFileName() {
145 return logFileName;
146 }
147
148 public void setLogFileName(String logFileName) {
149 this.logFileName = logFileName;
150 }
151
152 public String getLogFilePattern() {
153 return logFilePattern;
154 }
155
156 public void setLogFilePattern(String logFilePattern) {
157 this.logFilePattern = logFilePattern;
158 }
159
160 public int getLogFileWrittenLevelInt() {
161 return logFileWrittenLevelInt;
162 }
163
164 public void setLogFileWrittenLevelInt(int logFileWrittenLevelInt) {
165 this.logFileWrittenLevelInt = logFileWrittenLevelInt;
166 }
167
168 }
备注: 该文件主要用于配置log4j的配置信息。
后续
如果不能在手机中创建文件或者创建失败,那可能是缺少权限,你可以在 AndroidManifest.xml 文件中添加相应的权限。
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
http://developer.android.com/reference/android/util/Log.html
http://logging.apache.org/log4j/1.2/
作者信息:
QQ: 1321518080
Email: hucaijun520.ok@163.com