重构之Android
来源:互联网 发布:逃避深度思考 知乎 编辑:程序博客网 时间:2024/05/17 07:19
一:重构
【
1、重新规划Android项目结构
第一步:建立AndroidLib类库,将与业务无关的逻辑转移到AndroidLib。应至少包括五大部分:
activity基类:public abstract class BaseActivity extends Activity-------AppBaseActivity----具体的一个Activity
public abstract class AppBaseActivity extends BaseActivity {
}
net:网络底层封装
cache:缓存数据和图片的相关处理
ui:存放自定义控件
utils:存放的是各种与业务无关的公共方法
第二步:将主项目中的类分门别类地进行划分
activity
adapter:放适配器
entity:将所有的实体放在一起
db:SQLLite相关逻辑的封装
engine:存放业务相关的类
ui:存放自定义的控件
utils:存放所有的共用方法
interfaces:真正意义上的接口,一I命名
listener:基于Listener的接口,命名以On作为开头
2、为Activity定义新的生命周期
设计模式中有一条原则是:单一责任原则。单一责任的定义是:一个类或方法,只做一件事情。
用这条原则来观察Activity中的onCreate方法,通常要干好多事:通过继承实现接口的方式,重写onCreate方法---
public abstract class BaseActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initVariables();
initViews(savedInstanceState);
loadData();
}
//定义为抽象的方法,用于子类继承用的
protected abstract void initVariables();
protected abstract void initViews(Bundle savedInstanceState);
protected abstract void loadData();
}
引用:
public class LoginNewActivity extends AppBaseActivity implements View.OnClickListener {
private int loginTimes;
private String strEmail;
private EditText etPassword;
private EditText etEmail;
private Button btnLogin;
@Override
protected void initVariables() {
loginTimes = -1;
Bundle bundle = getIntent().getExtras();
strEmail = bundle.getString(AppConstants.Email);
}
@Override
protected void initViews(Bundle savedInstanceState) {
setContentView(R.layout.activity_login);
etEmail = (EditText)findViewById(R.id.email);
etEmail.setText(strEmail);
etPassword = (EditText)findViewById(R.id.password);
//登录事件
btnLogin = (Button)findViewById(R.id.sign_in_button);
btnLogin.setOnClickListener(this);
}
@Override
protected void loadData() {
//获取2个MobileAPI,获取天气数据,获取城市数据
loadWeatherData();
loadCityData();
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.sign_in_button:
gotoLoginActivity();
}
}
private void gotoLoginActivity() {
Intent intent = new Intent(LoginNewActivity.this,
PersonCenterActivity.class);
startActivity(intent);
}
private void loadWeatherData() {
//发起网络请求,代码从略
}
private void loadCityData() {
//发起网络请求,代码从略
}
}
对Activity生命周期重新定义是借鉴了JavaScript的做法。JavaScript因为是脚本语言,所以必须要细化每个方法,才能保证结构清晰,不
至于写错变量和语法。
3、统一事件编程模型
常见做法是实现事件接口,重写相应的事件方法如onClick,再在switch......case中R.id......筛选实现。根据面向对象编程的思想,就是initViews方法中实例化控件后,不希望再出现R.id...。就是在初始化控件的时候,就给控件添加相应的事件。
// 登录事件
Button btnLogin = (Button) findViewById(
R.id.sign_in_button);
btnLogin.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
gotoLoginActivity();
}
});
有两个优点:
1>直接在控件对象上增加点击事件,是面向对象的写法。
2>件onClick方面的实现,封装成一个方法,减少代码的臃肿度。
4、实体化编程
1>在网络请求中使用实体
JSONObect和JSONArray都是不支持序列化的,在值传递的时候只好将这种对象封装到一个全局变量中,在跳转前设置,在跳转后取出,
这并不是明智之举。
// 第一种写法,基于JSONObject
try {
JSONObject jsonResponse = new JSONObject(result);
JSONObject weatherinfo = jsonResponse
.getJSONObject("weatherinfo");
String city = weatherinfo.getString("city");
int cityId = weatherinfo.getInt("cityid");
tvCity.setText(city);
tvCityId.setText(String.valueOf(cityId));
} catch (JSONException e) {
e.printStackTrace();
}
如果通过传统的字典键值取值法会存在问题:
@1:根据key值取value,这是一个字典键值对,字典比实体更晦涩难懂,容易产生bug。
@2:每次都要手动从JSONObject或者JSONArray中取值,很繁琐。
====通过fastJSON和GSON实例化实体对象
首先得导入相应的.jar包。
public class WeatherInfo {
private String city;
private String cityid;
private String temp;
private String WD;
private String WS;
private String SD;
private String WSE;
private String time;
private String isRadar;
private String Radar;
private String njd;
private String qy;
........................................}
public class WeatherEntity {
private WeatherInfo weatherinfo;
public WeatherInfo getWeatherInfo() {
return weatherinfo;
}
public void setWeatherInfo(WeatherInfo weatherinfo) {
this.weatherinfo = weatherinfo;
}
}
fastJSON映射方式:
// 第2种写法,基于fastJSON
WeatherEntity weatherEntity = JSON.parseObject(content,
WeatherEntity.class);
WeatherInfo weatherInfo = weatherEntity.getWeatherInfo();
if (weatherInfo != null) {
tvCity.setText(weatherInfo.getCity());
tvCityId.setText(weatherInfo.getCityid());
}
// 第3种写法,基于GSON
Gson gson = new Gson();
WeatherEntity weatherEntity = gson.fromJson(content,
WeatherEntity.class);
WeatherInfo weatherInfo = weatherEntity.getWeatherInfo();
if (weatherInfo != null) {
tvCity.setText(weatherInfo.getCity());
tvCityId.setText(weatherInfo.getCityid());
}
====特殊注意
这里说一件非常狗血的事情,就是在我们使用fastJSON后,App四处起火,主要表现为:
1) 加了符号Annotation的实体属性,已使用就崩溃。
2)当有泛型属性时,一使用就崩溃。
在调试的时候没事,可是每次打签名混淆包,就会出现上述问题。解决这个问题需要在混淆文件中添加两行代码:
-keepattributes Signature //避免混淆泛型
-keepattributes *Annotation* //不混淆注解
2>实体生成器
Json Class Generator。可以生成Android和IOS以及WindowsPhone的实体。
工具地址如:http://www.xamasoft.com/json-class-generator/
项目地址: http://files.cnblogs.com/Jax/EntityGenerator.zip。
说明:工具代码为C# .NET代码。
3>在页面跳转中使用实体
Activity之间的数据应该如何传递。
一种偷懒的方法是,设置一个全局变量,在来源页设置全局变量,在目标页接收全局变量。
CinemaBean cinema = new CinemaBean();
cinema.setCinemaId("1");
cinema.setCinemaName("星美");
//使用全局变量的方式传递参数
GlobalVariables.Cinema = cinema;
--接收全局变量的值:
// 使用全局变量的方式传值
CinemaBean cinema = GlobalVariables.Cinema;
if (cinema != null) {
cinemaName = cinema.getCinemaName();
} else {
cinemaName = "";
}
这里的GlobalVariables类是一个全局变量,定义如下:
public class GlobalVariables{
public static CinemaBean Cinema;
}
注意:不建议使用全局变量。
App一旦切换到后台,当手机内存不足的时候,就会回收这些全局变量,从而当App再次切换回前台时,再继续使用全局变量,就会因为它
们为空而崩溃。
而必须使用全局变量,就一定要把它们序列化到本地。这样即使全局变量为空,也能从本地文件中恢复。
==着重研究使用Intent在页面间来传递数据实体的机制:
public class AppConstants {
public final static String Email = "Email";
public final static String Cinema = "Cinema";
}
//传递对象来源
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this,
LoginNewActivity.class);
intent.putExtra(AppConstants.Email, "jianqiang.bao@qq.com");
CinemaBean cinema = new CinemaBean();
cinema.setCinemaId("1");
cinema.setCinemaName("星美");
//使用intent上挂可序列化实体的方式传递参数
intent.putExtra(AppConstants.Cinema, cinema);
startActivity(intent);
}
//接收数据
Bundle bundle = getIntent().getExtras();
strEmail = bundle.getString(AppConstants.Email);
CinemaBean cinema = (CinemaBean)getIntent()
.getSerializableExtra(AppConstants.Cinema);
if (cinema != null) {
cinemaName = cinema.getCinemaName();
} else {
cinemaName = "";
}
//这里的CinemaBean要实现Serializable接口,以支持序列化:
public class CinemaBean implements Serializable {
private static final long serialVersionUID = 1L;}
5>Adapter模板
//要求所有的Adapter都继承自BaseAdapter,从构造函数注入List<自定义实体>这样的数据集合,从而完成ListView的填充工作。
public class CinemaAdapter extends BaseAdapter {
private final ArrayList<CinemaBean> cinemaList;
private final AppBaseActivity context;
public CinemaAdapter(ArrayList<CinemaBean> cinemaList,
AppBaseActivity context) {
this.cinemaList = cinemaList;
this.context = context;
}
public int getCount() {
return cinemaList.size();
}
public CinemaBean getItem(final int position) {
return cinemaList.get(position);
}
public long getItemId(final int position) {
return position;
}
public View getView(final int position, View convertView,
final ViewGroup parent) {
final Holder holder;
if (convertView == null) {
holder = new Holder();
convertView = context.getLayoutInflater().inflate(
R.layout.item_cinemalist, null);
holder.tvCinemaName = (TextView) convertView
.findViewById(R.id.tvCinemaName);
holder.tvCinemaId = (TextView) convertView
.findViewById(R.id.tvCinemaId);
convertView.setTag(holder);
} else {
holder = (Holder) convertView.getTag();
}
CinemaBean cinema = cinemaList.get(position);
holder.tvCinemaName.setText(cinema.getCinemaName());
holder.tvCinemaId.setText(cinema.getCinemaId());
return convertView;
}
class Holder {
TextView tvCinemaName;
TextView tvCinemaId;
}
}
----对于每个自定义的Adapter,都要实现以下4个方法:
getCount()、getItem()、getItemId()、getView()
----此外,还要内置一个Holder嵌套类,用于存放ListView中每一行中的控件。ViewHolder的存在,可以避免频繁创建用一个列表项,从
而极大地节省内存。
====那么,在Activity中,在使用Adapter的地方,按照下面的方式把列表数据传递过去:
public class ListDemoActivity extends AppBaseActivity {
ListView lvCinemaList;
ArrayList<CinemaBean> cinemaList;
@Override
protected void initVariables() {
cinemaList = new ArrayList<CinemaBean>();
CinemaBean cinema1 = new CinemaBean();
cinema1.setCinemaId("1");
cinema1.setCinemaName("星美");
CinemaBean cinema2 = new CinemaBean();
cinema2.setCinemaId("2");
cinema2.setCinemaName("万达");
cinemaList.add(cinema1);
cinemaList.add(cinema2);
}
@Override
protected void initViews(Bundle savedInstanceState) {
setContentView(R.layout.activity_listdemo);
lvCinemaList = (ListView) findViewById(R.id.lvCinemalist);
CinemaAdapter adapter = new CinemaAdapter(
cinemaList, ListDemoActivity.this);
lvCinemaList.setAdapter(adapter);
lvCinemaList.setOnItemClickListener(
new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
//do something
}
});
}
@Override
protected void loadData() {
}
}
6、类型安全转换函数
统计线上崩溃问题时,发现因为类型转换不正确导致的崩溃占了很大的比例。主要集中在两个地方:Object类型的对象、substring函数。
1)对于一个Object类型的对象,直接使用字符串操作函数toString,当其为null时就会崩溃。
解决方法:
public class Utils {
/**
*
* @Title: convertToInt
* @Description: 对象转化为整数数字类型
* @param value
* @param defaultValue
* @return integer
* @throws
*/
public final static int convertToInt(Object value, int defaultValue) {
if (value == null || "".equals(value.toString().trim())) {
return defaultValue;
}
try {
return Integer.valueOf(value.toString());
} catch (Exception e) {
try {
return Double.valueOf(value.toString()).intValue();
} catch (Exception e1) {
return defaultValue;
}
}
}
}
再通过此种方式引用,就不会崩溃了:
int result = Utils.converToInt(obj , 0);
2)如果长度不够,那么执行substring函数的时候,就会崩溃:
Java的substring函数有2个参数:start和end。
--解决方法:
String cityName = "T";
String firstLetter = "";
if(cityName.length() > 1) {
firstLetter = cityName.substring(1, 2);
}
====总结:
以上两类问题的根源,都来自MobileAPI返回的数据,由此而引出另一个很严肃的问题,对于从MobileAPI返回的数据,对待数据要分级别
对待:
1)对于那些不需要加工就能直接展示的数据,即使为空,只要在页面不显示就行,不会影响到逻辑。
2)对于那些很重要的数据,比如涉及到支付的金额不能为空时的逻辑,这是需要弹出提示框提示用户当前服务不可用,并停止接下来的工
作。
】
4 0
- 重构之Android
- Android代码重构之命名规范
- android代码重构之MVC框架
- android studio之代码重构
- Android项目重构之路
- android项目重构之mvp
- android项目重构之状态模式
- android 项目重构之 MVC 模式
- Android之view重绘
- Android之view重绘
- android代码重构之字符串资源处理及其格式化
- ANDROID项目重构之路:架构篇
- Android项目重构之路:界面篇
- ANDROID项目重构之路:实现篇
- Android项目重构之路:架构篇
- Android项目重构之路(一):架构篇
- Android项目重构之路(二):界面篇
- Android项目重构之路(三):实现篇
- 遍历Collection集合中的6种方法
- jQuery基础--02
- 防止表单重复提交
- HashSet排序【自定义一种排序方式:例如按照字符串的长短比较大小。如长度相同,就以数字比较】
- java函数式编程之UnaryOperator
- 重构之Android
- iMac登录后黑屏,只剩下鼠标可见
- 4523: [Cqoi2016]路由表
- Qt信号与槽自动关联机制
- 使用libcurl显示下载进度
- 函数及函数优化
- eval(符合js语法规范的字符串) 函数:
- xilinx 14.7 迷醉记录
- 文章标题