为什么要封装公共方法
封装公共方法有2方面的原因:
一是功能方面的原因:有些方法很多地方都会用,而且它输入输出明确,并且跟业务逻辑无关。比如检查用户是否登录,检查某串数字是否为合法的手机号。像这种方法就应该封装起来,供各个模块调用,避免重复造轮子。
二是防止出错:每一个合格的程序员就是从一个个错误中走出来的,任何一个架构包括android/iOS都有一些容易犯的错,我们可以把这些容易犯错的地方封装一下,每次用统一的,规定好的处理方式,这样就不会出错了。
防止重复:这个词在框架构造中提到的次数最多。这也是编写高可用代码的最重要的因素之一。
封装哪些内容及依据
下面就是一个最基本的应用需要封装的方法:
- 时间相关:获取本地时间,获取服务器时间,获取时间格式化等等。
- log和toast:log和toast的封装是为了做开关。应用正式版本中大多数调试log都应关闭。
- 异常上传:把客户端错误上传至服务器,就可以从服务端查看客户端哪里问题最严重,有的放矢。对于android来说,获取未捕获异常也很重要,请 查看此文 ,向下滚动至第三点:异常类捕获。
- 常见错误规避:如类型转换,subString这些容易引发异常的地方。
- 一些工具方法:如dp转px,px转dp,md5,判断手机号邮箱是否合法,获取设备信息等等。
代码:
贴这些代码的目的是分辨出哪些代码可以放在utils中,并且要养成往utils中提取代码的习惯。
更充分一点儿说:只要有2个地方使用的类似代码,就需要考虑是否可以提取成公共方法了。
时间相关函数:
//android: TimeUtils.java
public class TimeUtils {
private static long timeOffset = Long.MAX_VALUE;//同服务器的时间差
public static long getLocalTime(){//获取本地时间,单位:s
return (long)(getLocalTimeMs() / 1000.0);
}
public static long getCurrTime(){//获取服务器时间,单位:s
return getCurrTimeInner(getLocalTime(), 1);
}
private static long getCurrTimeInner(long base, long factor){
if(timeOffset != Long.MAX_VALUE && timeOffset != 0){
return base + timeOffset * factor;
}
return base;
}
public static long getLocalTimeMs(){//获取本地时间,单位:ms
return System.currentTimeMillis();
}
public static long getCurrTimeMs(){//获取服务器时间,单位:ms
return getCurrTimeInner(getLocalTimeMs(), 1000);
}
public static void adjustTimeOff(long serverTimeStamp){//调整时间差,需要在调用服务器接口时获取到服务器时间后调用
timeOffset = serverTimeStamp - getLocalTime();
}
}
//iOS: TimeUtils.h
#import <Foundation/Foundation.h>
@interface TimeUtils : NSObject
+(double)getLocalTimeWithSec;//获取本地时间,单位:s
+(double)getLocalTimeWithMSec;//获取本地时间,单位:ms
+(double)getCurrentTimeWithSec;//获取服务器时间,单位:s
+(double)getCurrentTimeWithMSec;//获取服务器时间,单位:ms
+(void)setTimeOffsetWithServer:(double) serverTime;调整时间差,需要在调用服务器接口时获取到服务器时间后调用
@end
//iOS: TimeUtils.m
#import "TimeUtils.h"
static double sTimeOffWithServer = 0;
@implementation TimeUtils
+(double)getLocalTimeWithSec{
return [[NSDate date]timeIntervalSince1970];
}
+(double)getLocalTimeWithMSec{
return [self getCurrentTimeWithSec] * 1000;
}
+(void)setTimeOffsetWithServer:(double) serverTime{
sTimeOffWithServer = serverTime - [self getLocalTimeWithSec];
}
+(double)getCurrentTimeWithSec{
return [self getLocalTimeWithSec] + sTimeOffWithServer;
}
+(double)getCurrentTimeWithMSec{
return [self getLocalTimeWithMSec] + sTimeOffWithServer;
}
@end
log及toast封装
关联章节:网络模块封装
//android LogUtils.java
public class LogUtils {
public static boolean isOpen = true;
//所有非错误log必须使用此方法打印
public static void d(String tag, String msg){
if (isOpen){
Log.d(tag, msg);
}
}
//所有不带有异常的错误必须使用此方法打印
public static void e(String tag, String msg){
if (isOpen){
Log.e(tag, msg);
}else{
uploadErrorLog(Utils.getClientInfo(), msg);
}
}
public static String[] exceptionToString(Throwable e){
StackTraceElement[] eles = e.getStackTrace();
String []ret = new String[eles.length + 1];
ret[0] = e.toString();
for (int i = 0; i < eles.length; i++) {
ret[i + 1] = " at " + eles[i].toString();
}
return ret;
}
//所有带有异常的错误必须使用此方法打印
public static void e(String tag, Throwable tr){
if(isOpen){
String exarr[] = exceptionToString(tr);
for (int i = 0; i < exarr.length; i++){
e(tag, exarr[i]);
}
}else{
String exarr[] = exceptionToString(tr);
StringBuilder sb = new StringBuilder();
for (String s: exarr){
sb.append(s);
}
//注:ClientInfo中包含:设备信息,应用信息,设备号等信息。
uploadErrorLog(Utils.getClientInfo(), sb.toString());
}
}
//上传错误至服务器
private static void uploadErrorLog(String clientInfo, String errorString){
//TODO:向特定服务器上传
//此处为伪代码,具体代码请联系本系列的第四章,统一编写。
Server.post("{\"clientInfo\":" + clientInfo + ",\"errorString\":" + errorString);
}
//提示用toast
public static void toastTip(Context context,@NonNull String msg) {
showToast(context, msg);
}
//调试用toast
public static void toastDebug(Context context,@NonNull String msg) {
if (isOpen) {
showToast(context, msg);
}
}
private static void showToast(Context context,@NonNull String msg) {
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
}
}
//iOS: LogUtils.h
//debug log的宏定义:所有非错误log必须使用此宏打印
#if defined(DEBUG) && DEBUG == 1
#define DebugLog(...) NSLog(__VA_ARGS__)
#else
#define DebugLog(...)
#endif
//error log 的宏定义:所有错误log必须使用此宏打印
#if defined(DEBUG) && DEBUG == 1
#define ErrorLog(...) DebugLog(__VA_ARGS__)
#else
#define ErrorLog(...) \
do{ \
NSString *errorString = [NSString stringWithFormat: __VA_ARGS__]; \
NSLog(errorString); \
[LogUtils uploadErrorLogWithClientInfo:[Utils getClientInfo] andErrorString:errorString]; \
}while(0);
#endif
//tip toast宏定义:所有提示用户所用的宏定义必须使用此宏打印
#define TipToast(msg, duration, viewCtl) [LogUtils toastWithMessage: msg andDuration: duration andViewController:viewCtl]
//debug toast宏定义:所有debug用的宏定义必须使用此宏打印
#if defined(DEBUG) && DEBUG == 1
#define DebugToast(msg, duration, viewCtl) TipToast(msg, duration, viewCtl)
#else
#define DebugToast(msg, duration, viewCtl)
#endif
@interface LogUtils : NSObject
//上传错误数据至服务器。
+(void) uploadErrorLogWithClientInfo:(NSString *)clientInfo andErrorString:(NSString*) errorString;
//为某个页面显示toast,模拟android
+(void) toastWithMessage:(NSString *)msg andDuration:(NSInteger) duration andViewCotroller:(UIViewController *)viewCtl;
@end
//iOS: LogUtils.m
#import "LogUtils.h"
@implements LogUtils
+(void)uploadErrorLogWithClientInfo:(NSString *)clientInfo andErrorString:(NSString*) errorString{
//TODO:向特定服务器上传
//此处为伪代码,具体代码请联系本系列的第四章:[网络模块封装](http://blog.csdn.net/hard_man/article/details/50699346),统一编写。
[Server postWithString: "{\"clientInfo\":" + clientInfo + ",\"errorString\":" + errorString];
}
+(void) toastWithMessage:(NSString *)msg andDuration:(NSInteger) duration andViewCotroller:(UIViewController *)viewCtl{
UIView *baseView = [[UIView alloc] init];
[viewCtl.view addSubview:baseView];
baseView.userInteractionEnabled = NO;
//此处使用Masory来指定View的大小和位置
[baseView makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(viewCtl.view);
}];
UIView *toastBg = [[UIView alloc] init];
//背景色
toastBg.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.8];
//设置圆角
toastBg.layer.cornerRadius = 3;
toastBg.layer.masksToBounds = YES;
//关闭点击
toastBg.userInteractionEnabled = NO;
[baseView addSubview:toastBg];
[toastBg makeConstraints:^(MASConstraintMaker *make) {
make.width.lessThanOrEqualTo(viewCtl.view.bounds.size.width - 30);
make.centerX.equalTo(baseView);
make.centerY.equalTo(baseView).offset(0);
}];
//文字
UILabel *label = [[UILabel alloc] init];
label.text = msg;
label.textColor = [Color whiteColor];
label.textAlign = NSTextAlignmentLeft;
label.font = [UIFont systemFontOfSize: 15];
label.numberOfLines = 3;
label.userInteractionEnabled = NO;
[toastBg addSubview:label];
[label makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(toastBg).insets(UIEdgeInsetsMake(5, 10, 5, 10));
}];
[baseView layoutIfNeeded];
[UIView animateWithDuration:0.3 delay:duration options:UIViewAnimationOptionCurveLinear animations:^{
toastBg.alpha = 0;
} completion:^(BOOL finished) {
[baseView removeFromSuperview];
}];
}
@end
//android: Utils.java
//下面是我项目中使用的,查看更多可[点击此处](http://www.cnblogs.com/cr330326/p/4422507.html)。
public class Utils{
//获取app信息
public static String getClientInfo(){
StringBuilder ret = new StringBuilder();
//系统版本
ret.append("os:android");
//系统版本号:
ret.append(",osVersion:" + android.os.Build.VERSION.RELEASE);
//手机型号:
ret.append(",phoneBrand:" + android.os.Build.BRAND);
ret.append(",phoneModel:" + android.os.Build.MODEL);
// ret.append(",phoneDevice:" + android.os.Build.DEVICE);
// ret.append(",phoneID:" + android.os.Build.ID);
// ret.append(",phoneBootLoader:" + android.os.Build.BOOTLOADER);
// ret.append(",phoneBoard:" + android.os.Build.BOARD);
// ret.append(",phoneCpuAbi:" + android.os.Build.CPU_ABI);
// ret.append(",phoneCpuAbi2:" + android.os.Build.CPU_ABI2);
// ret.append(",phoneDisplay:" + android.os.Build.DISPLAY);
// ret.append(",phoneFingerPrint:" + android.os.Build.FINGERPRINT);
// ret.append(",phoneHardware:" + android.os.Build.HARDWARE);
// ret.append(",phoneHost:" + android.os.Build.HOST);
// ret.append(",phoneManufacturer:" + android.os.Build.MANUFACTURER);
// ret.append(",phoneProduct:" + android.os.Build.PRODUCT);
// ret.append(",phoneRadio:" + android.os.Build.RADIO);
// ret.append(",phoneSerial:" + android.os.Build.SERIAL);
// ret.append(",phoneTags:" + android.os.Build.TAGS);
// ret.append(",phoneTime:" + android.os.Build.TIME);
// ret.append(",phoneType:" + android.os.Build.TYPE);
// ret.append(",phoneUser:" + android.os.Build.USER);
// ret.append(",phoneGetRadioVersion:" + android.os.Build.getRadioVersion());
//app 版本号
try {
PackageInfo pkgInfo = Constants.GLOBAL_CONTEXT.getPackageManager().getPackageInfo(Constants.GLOBAL_CONTEXT.getPackageName(), 0);
ret.append(",appVersionName:" + pkgInfo.versionName);
ret.append(",appVersionCode:" + pkgInfo.versionCode);
} catch (NameNotFoundException e) {
}
//uuid
TelephonyManager telephonyManager = (TelephonyManager) Constants.GLOBAL_CONTEXT.getSystemService(Context.TELEPHONY_SERVICE);
ret.append(",uuid:" + telephonyManager.getDeviceId());
return ret.toString();
}
//避免出错的substring
public static String subString(String src, int start, int to){
if(src != null){
int len = src.length;
int wantLen = to - start + 1;
if(wantLen < len){
to = len - 1;
wantLen = to - start + 1;
}
if(to >= start){
return src.substring(start, wantLen);
}else{
return null;
}
}
return null;
}
public static int getStatusBarHeight() {
Class<?> c = null;
Object obj = null;
Field field = null;
int x = 0, sbar = 0;
try {
c = Class.forName("com.android.internal.R$dimen");
obj = c.newInstance();
field = c.getField("status_bar_height");
x = Integer.parseInt(field.get(obj).toString());
sbar = context.getResources().getDimensionPixelSize(x);
} catch (Exception e1) {
e1.printStackTrace();
}
if (sbar == 0) {
sbar = Util.dp(20);
}
return sbar;
}
//获取屏幕高度
public static int getScreenHeight() {
return context.getResources().getDisplayMetrics().heightPixels;
}
public static int getScreenWidth() {
return context.getResources().getDisplayMetrics().widthPixels;
}
//dp sp px 之间转换
public static int px2dp(int px) {
return (int) (1.0f * px / context.getResources().getDisplayMetrics().density + 0.5f);
}
public static int dp2px(int dp) {
return (int) (1.0f * dp * context.getResources().getDisplayMetrics().density + 0.5f);
}
public static int sp2px(int sp) {
return (int) (1.0f * sp * context.getResources().getDisplayMetrics().scaledDensity + 0.5f);
}
public static int px2sp(int px) {
return (int) (1.0f * px / context.getResources().getDisplayMetrics().scaledDensity + 0.5f);
}
public static int sp2dp(int sp) {
return px2dp(sp2px(sp));
}
public static int dp2sp(int dp) {
return px2sp(dp2px(dp));
}
public static int dp(int dp) {
return dp2px(dp);
}
public static int sp(int sp) {
return sp2px(sp);
}
//读取asstes图片
public static Bitmap getImageFromAssetsFile(String fileName, Context context) {
Bitmap image = null;
AssetManager am = context.getResources().getAssets();
try {
InputStream is = am.open(fileName);
image = BitmapFactory.decodeStream(is);
is.close();
} catch (IOException e) {
e.printStackTrace();
}
return image;
}
//验证邮箱格式
public static boolean isEmail(String email) {
String str = "^([\\w-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([\\w-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)$";
Pattern p = Pattern.compile(str);
Matcher m = p.matcher(email);
return m.matches();
}
//验证手机号码格式
public static boolean isMobileNO(String mobiles) {
Pattern p = Pattern
.compile("^1(3[0-9]|4[57]|5[0-35-9]|8[025-9]|7[0-9])\\d{8}$");
Matcher m = p.matcher(mobiles);
return m.matches();
}
}
//iOS: Utils.h
@interface Utils:NSObject
//下面是一系列回调函数block
typedef void (^VoidIntCallback) (NSUInteger);
typedef void (^VoidCallback) ();
typedef void (^VoidStringCallback) (NSString *);
typedef void (^VoidBoolCallback) (BOOL);
typedef void (^VoidIdCallback) (id);
//获取当前设备名称
+ (NSString *)getDeviceName;
//获取设备信息
+(NSString *)getClientInfo;
+(int)getRandomNumber:(int)from to:(int)to;
//获取文件夹的大小
+(float) folderSizeAtPath:(NSString*) folderPath;
+(long long) fileSizeAtPath:(NSString*) filePath;
//清除缓存
+(NSString *)clearCache;
//判断输入是否合法
+(BOOL) isValidPhone:(NSString *)num;
+(BOOL) isValidEmail:(NSString *)email;
//一个页面中局部view显示/隐藏时所用的动画。默认采取渐显/渐隐的方式
+(void) createShowAnimForViewTypeChangeWithOneView:(UIView *) view andComplete:(VoidCallback) completeCb;
+(void) createHideAnimForViewTypeChangeWithOneView:(UIView *) view andComplete:(VoidCallback) completeCb;
+(void) createAnimForViewTypeChangeWithFromView:(UIView *) fromView toView:(UIView *)toView andComplete:(VoidCallback) completeCb;
@end
//iOS: Utils.m
#import "Utils.h"
#import <ADSupport/ASIdentifierManager.h>
@implements Utils
//这个函数是从网上找的代码,不是很准确,使用的方法也奇怪,这里只是表示一个意思,可令getClientInfo调用。
//想要更正确的代码,请自行查找。
+ (NSString *)getDeviceName{
CGRect rect = [[UIScreen mainScreen] bounds];
CGFloat width = rect.size.width;
CGFloat height = rect.size.height;
//get current interface Orientation
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
//unknown
if (UIInterfaceOrientationUnknown == orientation) {
return @"unknown";
}
// portrait width * height
// iPhone4:320*480
// iPhone5:320*568
// iPhone6:375*667
// iPhone6Plus:414*736
//portrait
if (UIInterfaceOrientationPortrait == orientation) {
if (width == 320.0f) {
if (height == 480.0f) {
return @"iphone4/iPhone4s";//iphone4
} else {
return @"iPhone5/iPhone5s";
}
} else if (width == 375.0f) {
return @"iPhone6/iPhone6s";
} else if (width == 414.0f) {
return @"iPhone6plus/iPhone6sPlus";
}
} else if (UIInterfaceOrientationLandscapeLeft == orientation || UIInterfaceOrientationLandscapeRight == orientation) {//landscape
if (height == 320.0) {
if (width == 480.0f) {
return @"iphone4/iPhone4s";
} else {
return @"iPhone5/iPhone5s";
}
} else if (height == 375.0f) {
return @"iPhone6/iPhone6s";
} else if (height == 414.0f) {
return @"iPhone6plus/iPhone6sPlus";
}
}
return -1;
}
//获取设备信息
+(NSString *)getClientInfo{
NSMutableString *ret = [[NSMutableString alloc]init];
[ret appendString:@"os:iphone"];
[ret appendFormat:@",osVersion:%f", [[[UIDevice currentDevice] systemVersion] floatValue]];
[ret appendString:@",phoneBrand:iphone"];
[ret appendFormat:@",phoneModel:%@", [self getDeviceName]];
[ret appendFormat:@",appVersionName:%@", [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]];
[ret appendFormat:@",appVersionCode:%@", [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]];
NSString * adId = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
NSString * deviceId = [[UIDevice currentDevice].identifierForVendor UUIDString];
[ret appendFormat:@",deviceId:%@", deviceId];
NSString *identifier = nil;
if(!adId){
identifier = deviceId;
}else{
identifier = adId;
[ret appendFormat:@",adId:%@", adId];
}
[ret appendFormat:@",uuid:%@", identifier];
return ret;
}
+ (long long) fileSizeAtPath:(NSString*) filePath{
NSFileManager* manager = [NSFileManager defaultManager];
if ([manager fileExistsAtPath:filePath]){
return [[manager attributesOfItemAtPath:filePath error:nil] fileSize];
}
return 0;
}
+(int)getRandomNumber:(int)from to:(int)to{
return (int)(from + (arc4random() % (to - from + 1)));
}
//遍历文件夹获得文件夹大小,返回多少M
+ (float ) folderSizeAtPath:(NSString*) folderPath{
NSFileManager* manager = [NSFileManager defaultManager];
if (![manager fileExistsAtPath:folderPath]) return 0;
NSEnumerator *childFilesEnumerator = [[manager subpathsAtPath:folderPath] objectEnumerator];
NSString* fileName;
long long folderSize = 0;
while ((fileName = [childFilesEnumerator nextObject]) != nil){
NSString* fileAbsolutePath = [folderPath stringByAppendingPathComponent:fileName];
folderSize += [self fileSizeAtPath:fileAbsolutePath];
}
return folderSize/(1024.0*1024.0);
}
+ (NSString *)clearCache
{
//清除缓存目录
NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *searchPath = [searchPaths lastObject];
NSString *str = [NSString stringWithFormat:@"缓存已清除%.1fM", [self folderSizeAtPath:searchPath]];
NSLog(@"%@",str);
NSArray *files = [[NSFileManager defaultManager] subpathsAtPath:searchPath];
for (NSString *p in files) {
NSError *error;
NSString *currPath = [searchPath stringByAppendingPathComponent:p];
if ([[NSFileManager defaultManager] fileExistsAtPath:currPath]) {
BOOL ret = [[NSFileManager defaultManager] removeItemAtPath:currPath error:&error];
YYLog(@"移除文件 %@ ret= %d", currPath, ret);
}else{
YYLog(@"文件不存在 %@", currPath);
}
}
return str;
}
+(BOOL) isValidNum:(NSString *)num{
const char *cvalue = [num UTF8String];
int len = (int)strlen(cvalue);
for (int i = 0; i < len; i++) {
if(cvalue[i] < '0' || cvalue[i] > '9'){
return NO;
}
}
return YES;
}
+(BOOL) isValidPhone:(NSString *)num{
if (!num) {
return NO;
}
const char *cvalue = [num UTF8String];
int len = (int)strlen(cvalue);
if (len != 11) {
return NO;
}
if (![Util isValidNum:num])
{
return NO;
}
NSString *preString = [[NSString stringWithFormat:@"%@",num] substringToIndex:2];
if ([preString isEqualToString:@"13"] ||
[preString isEqualToString: @"15"] ||
[preString isEqualToString: @"18"] ||
[preString isEqualToString: @"17"])
{
return YES;
}
else
{
return NO;
}
return YES;
}
+ (BOOL) isValidEmail:(NSString *)e
{
if (!e) {
return NO;
}
NSArray *array = [e componentsSeparatedByString:@"."];
if ([array count] >= 4) {
return NO;
}
NSString *emailRegex = @"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}";
NSPredicate *emailTest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", emailRegex];
return [emailTest evaluateWithObject:e];
}
@end
代码清单:
//android:
LogUtils.java
TimeUtils.java
Utils.java
//iOS:
LogUtils.h
LogUtils.m
TimeUtils.h
TimeUtils.m
Utils.h
Utils.m
至此框架基本搭建完毕,可以快乐地写页面去啦。
当然在项目进行的过程中,也需要慢慢给这个还瘦弱的框架添枝加叶。
让它慢慢壮大,更加完整。
等经过一个或2个项目的洗礼,就会成为一个完整的,不错的框架了。