爲什麼要封裝公共方法
封裝公共方法有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個項目的洗禮,就會成爲一個完整的,不錯的框架了。