在开发APP中,总有许多问题,对象之间的组合与调用,工具类的编写,APP常见的流程,都是可以借鉴的。由于使用的是Bmob的API,对它的封装总不是很好。查看Wonderful(也是基于Bmob后台的),为此学习它是很必要的,并且一个个流程看下来,感觉思路就清淅了。为此,有了这篇文章,分享缎带大家

关键问题-关于Bmob API封装

public void signUp(String userName,String password,String email){
	User user = new User();
	user.setUsername(userName);
	user.setPassword(password);
	user.setEmail(email);
	user.setSex(Constant.SEX_FEMALE);
	user.setSignature("这个家伙很懒,什么也不说。。。");
	user.signUp(mContext, new SaveListener() {
		
		@Override
		public void onSuccess() {
			// TODO Auto-generated method stub
			if(signUpLister != null){
				signUpLister.onSignUpSuccess();
			}else{
				LogUtils.i(TAG,"signup listener is null,you must set one!");
			}
		}

		@Override
		public void onFailure(int arg0, String msg) {
			// TODO Auto-generated method stub
			if(signUpLister != null){
				signUpLister.onSignUpFailure(msg);
			}else{
				LogUtils.i(TAG,"signup listener is null,you must set one!");
			}
		}
	});
}

逻辑分析: 在登录中,使用一个流程,登录失败或成功,又一次自定义接口,的添加UI提示. 在Bmob接口使用中,存在这样的一个问题: BmobAPI操作是一个操作,需要在UI是显示Progress或都是其他分散用户注意力的操作。

在关于API操作中一些工具类 TextUtils.isEmpty()用于判断字符器是否为空

关于在Progress进度显示问题 在调用BmobAPI前调用Progressbar progressbar.setVisibility(View.VISIBLE)显示进度 在BmobAPI中设置Progressbar为 progressabr.setVisibility(View.GONE);去掉进度

所有的BmobAPI 都有统一的流程。

因此根据以上方法,可以重新构造API,对ProgressDialog进行统一的处理

MPV去留

在编写MVP模式时,由于笼于代码太多,不紧凑,需要更改代码结构,重新编写,紧凑的代码,为此放弃使用MVP构想,编写时不方便,不再采用,构想编写使用DataBind来重构APP,以此来提长效率

工具类的编写参考

在编写APP 常用了许多提示用户,增加友好度的一此控件,而经常使用,或者为一些作为判断依据的,编写方式

/**
 * 解决Toast重复出现多次,保持全局只有一个Toast实例
 * 
 */
public class ToastFactory {
	private static Context context = null;
	private static Toast toast = null;

	public static Toast getToast(Context context, String text) {
		if (ToastFactory.context == context) {
			// toast.cancel();
			toast.setText(text);
			toast.setDuration(Toast.LENGTH_SHORT);

		} else {

			ToastFactory.context = context;
			toast = Toast.makeText(context, text, Toast.LENGTH_SHORT);
		}
		return toast;
	}

	public static void cancelToast() {
		if (toast != null) {
			toast.cancel();
		}
	}

}

关于缓存问题

在APP中,使用缓存是一个很常见的问题,手机流量需要省流量,是一个不是问题的问题.

public class CacheUtils {
  /**
   * 获取/data/data/files目录
   * @param context
   * @return
   */
public static File getFileDirectory(Context context) {
    File appCacheDir=null;
    if(appCacheDir == null) {
        appCacheDir=context.getFilesDir();
    }
    if(appCacheDir == null) {
        String cacheDirPath="/data/data/" + context.getPackageName() + "/files/";
        appCacheDir=new File(cacheDirPath);
    }
    return appCacheDir;
}


public static File getCacheDirectory(Context context, boolean preferExternal,String dirName) {
    File appCacheDir = null;
    if (preferExternal && MEDIA_MOUNTED
            .equals(Environment.getExternalStorageState()) && hasExternalStoragePermission(context)) {
        appCacheDir = getExternalCacheDir(context,dirName);
    }
    if (appCacheDir == null) {
        appCacheDir = context.getCacheDir();
    }
    if (appCacheDir == null) {
        String cacheDirPath = "/data/data/" + context.getPackageName() + "/cache/";
        Log.w("Can't define system cache directory! '%s' will be used.", cacheDirPath);
        appCacheDir = new File(cacheDirPath);
    }
    return appCacheDir;
}


private static File getExternalCacheDir(Context context,String dirName) {
    File dataDir = new File(new File(Environment.getExternalStorageDirectory(), "Android"), "data");
    File appCacheDir2 = new File(new File(dataDir, context.getPackageName()), "cache");
    File appCacheDir = new File(appCacheDir2, dirName);
    if (!appCacheDir.exists()) {
        if (!appCacheDir.mkdirs()) {
            Log.w(TAG,"Unable to create external cache directory");
            return null;                                                                                                                  
        }
        try {
            new File(appCacheDir, ".nomedia").createNewFile();
        } catch (IOException e) {
            Log.i(TAG,"Can't create \".nomedia\" file in application external cache directory");
        }
    }
    return appCacheDir;
}
private static final String TAG = "CacheUtils";
private static final String EXTERNAL_STORAGE_PERMISSION = "android.permission.WRITE_EXTERNAL_STORAGE";
private static boolean hasExternalStoragePermission(Context context) {
    int perm = context.checkCallingOrSelfPermission(EXTERNAL_STORAGE_PERMISSION);
    return perm == PackageManager.PERMISSION_GRANTED;
}

}

从以上代码可以看到, ` int perm = context.checkCallingOrSelfPermission(EXTERNAL_STORAGE_PERMISSION); perm ==PackageManager.PERMISSION_GRANTED;` 这两句话,用于动态检查权限问题,以避免用户在安全软件中禁止APP来禁止权限,在源代码中有如下解析

/**
 * Determine whether the calling process of an IPC <em>or you</em> have been
 * granted a particular permission.  This is the same as
 * {@link #checkCallingPermission}, except it grants your own permissions
 * if you are not currently processing an IPC.  Use with care!
 *
 * @param permission The name of the permission being checked.
 *
 * @return {@link PackageManager#PERMISSION_GRANTED} if the calling
 * pid/uid is allowed that permission, or
 * {@link PackageManager#PERMISSION_DENIED} if it is not.
 *
 * @see PackageManager#checkPermission(String, String)
 * @see #checkPermission
 * @see #checkCallingPermission
 */
@CheckResult(suggest="#enforceCallingOrSelfPermission(String,String)")
@PackageManager.PermissionResult
public abstract int checkCallingOrSelfPermission(@NonNull String permission);

简而言之,就是在checkCallingOrSelfPermission中检查这个权限是否注册,并返回一个固定值,并与固定值比较,若已经注册,就返回true

关于收藏的实现,相当于离线

关于收藏,是在本地添加一个数据库记录,并将图片缓存到本地,服务端并没有对就的表,此举简化了开发,减少了网络流量

关于APP个性化配置文件

通过对SharedPreferences的使用,实现对APP个性化的配置,为方便读取配置,编写成一个工具类

/**
 * 首选项工具类
 * 
 * @author adison
 * 
 */
public class Sputil {
private Context context;
private SharedPreferences sp = null;
private Editor edit = null;

/**
 * 创建默认sp
 * 
 * @param context
 */
public Sputil(Context context) {
	this(context, PreferenceManager.getDefaultSharedPreferences(context));
}

/**
 * 通过文件名创建sp
 * 
 * @param context
 * @param filename
 */
public Sputil(Context context, String filename) {
	this(context, context.getSharedPreferences(filename,
			Context.MODE_WORLD_WRITEABLE));
}

/**
 * 通过sp创建sp
 * 
 * @param context
 * @param sp
 */
public Sputil(Context context, SharedPreferences sp) {
	this.context = context;
	this.sp = sp;
	edit = sp.edit();
}

public SharedPreferences getInstance() {
	return sp;
}

// Set

// Boolean
public void setValue(String key, boolean value) {
	edit.putBoolean(key, value);
	edit.commit();
}

public void setValue(int resKey, boolean value) {
	setValue(this.context.getString(resKey), value);
}

// Float
public void setValue(String key, float value) {
	edit.putFloat(key, value);
	edit.commit();
}

public void setValue(int resKey, float value) {
	setValue(this.context.getString(resKey), value);
}

// Integer
public void setValue(String key, int value) {
	edit.putInt(key, value);
	edit.commit();
}

public void setValue(int resKey, int value) {
	setValue(this.context.getString(resKey), value);
}

// Long
public void setValue(String key, long value) {
	edit.putLong(key, value);
	edit.commit();
}

public void setValue(int resKey, long value) {
	setValue(this.context.getString(resKey), value);
}

// String
public void setValue(String key, String value) {
	edit.putString(key, value);
	edit.commit();
}

public void setValue(int resKey, String value) {
	setValue(this.context.getString(resKey), value);
}

// Get

// Boolean
public boolean getValue(String key, boolean defaultValue) {
	return sp.getBoolean(key, defaultValue);
}

public boolean getValue(int resKey, boolean defaultValue) {
	return getValue(this.context.getString(resKey), defaultValue);
}

// Float
public float getValue(String key, float defaultValue) {
	return sp.getFloat(key, defaultValue);
}

public float getValue(int resKey, float defaultValue) {
	return getValue(this.context.getString(resKey), defaultValue);
}

// Integer
public int getValue(String key, int defaultValue) {
	return sp.getInt(key, defaultValue);
}

public int getValue(int resKey, int defaultValue) {
	return getValue(this.context.getString(resKey), defaultValue);
}

// Long
public long getValue(String key, long defaultValue) {
	return sp.getLong(key, defaultValue);
}

public long getValue(int resKey, long defaultValue) {
	return getValue(this.context.getString(resKey), defaultValue);
}

// String
public String getValue(String key, String defaultValue) {
	return sp.getString(key, defaultValue);
}

public String getValue(int resKey, String defaultValue) {
	return getValue(this.context.getString(resKey), defaultValue);
}

// Delete
public void remove(String key) {
	edit.remove(key);
	edit.commit();
}

public void clear() {
	edit.clear();
	edit.commit();
}

/**
 * 是否第一次启动应用
 * 
 * @param context
 * @return
 */
public boolean isFirstStart(Context context) {
	try {
		PackageInfo info = context.getPackageManager().getPackageInfo(
				context.getPackageName(), 0);
		int curVersion = info.versionCode;
		int lastVersion = sp.getInt("version", 0);
		if (curVersion > lastVersion) {
			// 如果当前版本大于上次版本,该版本属于第一次启动
			// 将当前版本写入preference中,则下次启动的时候,据此判断,不再为首次启动
			return true;
		} else {
			return false;
		}
	} catch (NameNotFoundException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}

	return false;
}

/**
 * 是否第一次安装应用
 * 
 * @param context
 * @return
 */
public boolean isFirstInstall(Context context) {
	int install = sp.getInt("first_install", 0);
	if (install == 0)
		return true;

	return false;
}

/**
 * 应用已启动
 * 
 * @param context
 */
public void setStarted(Context context) {
	try {
		PackageInfo info = context.getPackageManager().getPackageInfo(
				context.getPackageName(), 0);
		int curVersion = info.versionCode;
		sp.edit().putInt("version", curVersion).commit();
	} catch (NameNotFoundException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
}

/**
 * 应用已安装并启动
 * 
 * @param context
 */
public void setInstalled(Context context) {
	sp.edit().putInt("first_install", 1).commit();
}



/**
 * 是否需要改变数据
 * 
 * @param context
 * @param typeID
 * @return
 */
public  boolean needChangeIndexContent(Context context, String openID) {
	
	String save = sp.getString(openID, "");
	String cur = getDateByNumber();
	if (save.equals(cur)) {
		// be the last statement in the method
		return false;
	}
	return true;
}

/**
 * 保存更新日期
 * 
 * @param context
 * @param typeID
 */
public void saveChangeIndexContent(Context context, String openID) {
	
	String cur = getDateByNumber();
	sp.edit().putString(openID, cur).commit();
}

/**
 * 记录日期,决定是否数据是否需要改动
 * 
 * @return
 */
public static String getDateByNumber() {
	SimpleDateFormat s = new SimpleDateFormat("yyyy-MM-dd",
			new Locale("zh"));
	String cur = s.format(new Date());
	return cur;
}
} 在以上的代码中,可以看到,首启动这个选项,在应用安装完成后的第一次启动, 一般会有引导页出现,这也就是为什么需要引导页的存在了

超简单的引导页

在某一个界面停留几秒,并初始化APP参数后,再跳转到内容界面,Handler快捷使用方法

/**
 * 根据时间进行页面跳转
 */
private void redirectByTime() {
	new Handler().postDelayed(new Runnable() {
		
		@Override
		public void run() {
			// TODO Auto-generated method stub
			redictToActivity(SplashActivity.this, MainActivity.class, null);
			finish();
		}
	}, DELAY_TIME);
}

跳转界面的常用方法

/**
 * Activity跳转
 * @param context
 * @param targetActivity
 * @param bundle
 */
public void redictToActivity(Context context,Class<?> targetActivity,Bundle bundle){
	Intent intent = new Intent(context, targetActivity);
	if(null != bundle){
		intent.putExtras(bundle);
	}
	startActivity(intent);
}

最后在APP开发中,有如下常用的控件

下拉刷新控件、上拉刷新控件,更新数据问题
图片控件universal-image-loader-1.9.1,解决了OOM以及缓存问题