重构之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
原创粉丝点击