简单的android框架:SAF

jopen 10年前

SAF(Simple Android Framework)是一个简单的android框架,它为开发Android app提供了基础性组件。SAF已经在多个项目中使用,包括今夜酒店特价app、锦江之星app、京东内部的一个app等等。

SAFApp

SAFApp其实不能算是一个完整的模块,SAFApp继承了Application。增加了一个可作为缓存存放app全局变量的session,一个ImageLoader,一个记录Activity的List。

Event Bus

事件总线框架,类似于google guava、square otto的event bus。它是一种消息发布-订阅模式,它的工作机制类似于观察者模式,通过通知者去注册观察者,最后由通知者向观察者发布消息。

Event Bus解耦了asyncTask、handler、thread、broadcast等组件。使用Event bus可以轻松地跨多个Fragment进行通讯。

它用法很简单,在Activity或者Fragment中使用,其中event是一个简单的POJO

 // 退出系统的事件  eventBus.post(new LogoutEvent());

回调事件,同样在Activity或者Fragment中定义好。回调方法名可以随便定义,参数须要和event一一对应。并且在方法名前加上注解Subscribe

     /**        * 退出整个app        * @param event        */        @Subscribe        public void onLogoutEvent(LogoutEvent event) {        }
<p>@Subscribe可以使用枚举</p>
     /**        * 使用ThreadMode.BackgroundThread枚举,表示在后台线程运行,不在主线程中运行。        * @param event        */        @Subscribe(ThreadMode.BackgroundThread)        public void onBackendFresh(BackendFreshEvent event) {          }

使用枚举BackgroundThread时,如果在回调方法中需要更新ui,则必须要配合handler使用。 在不使用枚举的情况下,@Subscribe会默认使用PostThread,表示回调方法会在主线程中运行。 如果在一个Activity中存在多个Fragment,并且在Activity或者在Fragment中存在订阅同一event的回调方法。如果发出 event的请求时,这些回调方法都会起作用。

Rest Client

Rest Client模块提供了http的get、post、put、delete方法。这个模块还不是很完善,只是适应自身项目需要,未来会不断增加新的功能。 这个模块没有基于apache httpclient,完全基于jdk中的HttpURLConnection。

同步调用get方法:

           RestClient client = RestClient.get(url);

String body = client.body();

异步调用get方法:

      RestClient.get(url,new HttpResponseHandler(){              public void onSuccess(String content) {              // content为http请求成功后返回的response            }        });

同步调用post方法:post body内容为json

      RestClient client = RestClient.post(url);        client.acceptJson().contentType("application/json", null);        client.send(jsonString); // jsonString是已经由json对象转换成string类型        String body = client.body();

异步调用post方法:post body内容为json

      RestClient.post(url,json,new HttpResponseHandler(){ // json对应的是fastjson的JSONObject对象                public void onSuccess(String content) {              }           });

异步调用post方法:以form形式传递数据

      RestClient.post(urlString, map, new HttpResponseHandler(){                @Override              public void onSuccess(String content) {                }          });

Image Cache

图片缓存模块包括2级缓存,内存中的cache和sd卡上存放在文件中的cache。

图片缓存模块通过ImageLoader进行图片加载。 如果app中使用了SAFApp,则无须创建新的ImageLoader就可以使用。

           // 第一个参数是图片的url,第二个参数是ImageView对象,第三个参数是默认图片            imageLoader.displayImage(url, imageView ,R.drawable.defalut_icon);

Dependency Injection

Dependency Injection是依赖注入的意思,简称DI。

SAF中的DI包括以下几个方面:

  • Inject View :简化组件的查找注册,目前支持约定大于配置,如果代码中的组件名称跟layout中要注入的组件id相同,则无需写(id=R.id.xxxx)
  • Inject Views:支持多个相同类型组件的注入
  • Inject Service :简化系统服务的注册,目前只支持android的系统服务
  • Inject Extra :简化2个Activity之间Extra传递
  • InflateLayout :简化布局填充时,组件的查找注册
  • OnClick:简化各种组件的Click事件写法
  • OnItemClick:简化ListView的ItemView事件写法

Inject View

Inject View可以简化组件的查找注册,包括android自带的组件和自定义组件。在使用Inject View之前,我们会这样写代码

      public class MainActivity extends Activity {                private ImageView imageView;                @Override              protected void onCreate(Bundle savedInstanceState) {                super.onCreate(savedInstanceState);                  setContentView(R.layout.activity_main);                imageView = (ImageView) findViewById(R.id.imageview);              }         }

在使用Inject View之后,会这样写代码

      public class MainActivity extends Activity {                @InjectView(id= R.id.imageview)              private ImageView imageView;                @Override              protected void onCreate(Bundle savedInstanceState) {                 super.onCreate(savedInstanceState);                   setContentView(R.layout.activity_main);                 Injector.injectInto(this);              }        }

约定大于配置的写法,无需写(id= R.id.imageview)

      public class MainActivity extends Activity {                @InjectView              private ImageView imageview;                @Override              protected void onCreate(Bundle savedInstanceState) {                 super.onCreate(savedInstanceState);                   setContentView(R.layout.activity_main);                 Injector.injectInto(this);              }        }

目前,@InjectView可用于Activity、Dialog、Fragment中。在Activity和Dialog用法相似,在Fragment中用法有一点区别。

      public class DemoFragment extends Fragment {                   @InjectView(id=R.id.title)                 private TextView titleView;                   @InjectView(id=R.id.imageview)                 private ImageView imageView;                   @Override                 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {                        View v = inflater.inflate(R.layout.fragment_demo, container, false);                          Injector.injectInto(this,v); // 和Activity使用的区别之处在这里                          initViews();                        initData();                          return v;                 }                  ......         }

Inject Views

      public class MainActivity extends Activity {                @InjectViews(ids={R.id.imageView1,R.id.imageView2})              private List<ImageView> imageviews;                @Override              protected void onCreate(Bundle savedInstanceState) {                 super.onCreate(savedInstanceState);                   setContentView(R.layout.activity_main);                 Injector.injectInto(this);              }        }

Inject Extra

     /**        * MainActivity传递数据给SecondActivity        * Intent i = new Intent(MainActivity.this,SecondActivity.class);                                                       * i.putExtra("test", "saf");        * i.putExtra("test_object", hello);        * startActivity(i);        * 在SecondActivity可以使用@InjectExtra注解        *        * @author Tony Shen        *        */       public class SecondActivity extends Activity{               @InjectExtra(key="test")             private String testStr;               @InjectExtra(key="test_object")             private Hello hello;               protected void onCreate(Bundle savedInstanceState) {                 super.onCreate(savedInstanceState);                   Injector.injectInto(this);                 Log.i("++++++++++++","testStr="+testStr);                 Log.i("++++++++++++","hello="+SAFUtil.printObject(hello)); // 该方法用于打印对象            }        }

InflateLayout

    /**       * @author Tony Shen       *       */       @InflateLayout(id=R.layout.my_view)       public class MyView extends LinearLayout {              @InjectView(id = R.id.textview1)            public TextView view1;              @InjectView(id = R.id.textview2)            public TextView view2;             public MyView(Context context) {               super(context);           }      }

在Activity、Fragment中的写法:

          MyView myView = Injector.build(mContext, MyView.class);

OnClick

<p>@OnClick 可以在Activity、Fragment、Dialog、View中使用,支持多个组件绑定同一个方法。</p>
 public class AddCommentFragment extends BaseFragment {         @Override       public View onCreateView(LayoutInflater inflater, ViewGroup container,                           Bundle savedInstanceState) {             View v = inflater.inflate(R.layout.fragment_add_comment, container, false);             Injector.injectInto(this, v);             initView();             return v;      }        @OnClick(id={R.id.left_menu,R.id.btn_comment_cancel})      void clickLeftMenu() {          popBackStack();      }        @OnClick(id=R.id.btn_comment_send)      void clickCommentSend() {          if (StringHelper.isBlank(commentEdit.getText().toString())) {             ToastUtil.showShort(mContext, R.string.the_comment_need_more_character);          } else {             AsyncTaskExecutor.executeAsyncTask(new AddCommentTask(showDialog(mContext)));          }      }        ....  }

Sqlite ORM

顾名思义就是sqlite的orm框架,采用oop的方式简化对sqlite的操作。 首先需要在AndroidManifest.xml中配上一些参数

    <!-- 表示在com.example.testsaf.db这个package下的类都是db的domain,一个类对应db里的一张表-->      <meta-data          android:name="DOMAIN_PACKAGE"          android:value="com.example.testsaf.db" />        <!-- 表示db的名称-->      <meta-data          android:name="DB_NAME"          android:value="testsaf.db" />         <!-- 表示db的版本号-->       <meta-data          android:name="DB_VERSION"          android:value="1" />

使用orm框架需要初始化DBManager,需要在Applicaion中完成。SAF中的SAFApp,没有初始化DBManager,如果需要使用SAFApp可以重写一个Application继承SAFApp,并初始化DBManager。

      /**         * @author Tony Shen         *         */         public class TestApp extends Application{                @Override              public void onCreate() {                 super.onCreate();                 DBManager.initialize(this);              }           }

db的domain使用是也是基于注解

      /**         *          * 表示sqlite中autocomplete表的属性         * @author Tony Shen         *          */        @Table(name="autocomplete")        public class Autocomplete extends DBDomain{              @Column(name="key_words",length=20,notNull=true)            public String KEY_WORDS;              @Column(name="key_type",length=20,notNull=true)            public String KEY_TYPE;              @Column(name="key_reference",length=80)            public String KEY_REFERENCE;        }

db的操作很简单

      Autocomplete auto = new Autocomplete();        auto.KEY_TYPE = "1";        auto.KEY_WORDS = "testtest";        auto.save(); // 插入第一条记录          Autocomplete auto2 = new Autocomplete();        auto2.KEY_TYPE = "0";        auto2.KEY_WORDS = "haha";        auto2.save(); // 插入第二条记录          Autocomplete auto3 = new Autocomplete().get(1); // 获取Autocomplete的第一条记录        if (auto3!=null) {             Log.i("+++++++++++++++","auto3.KEY_WORDS="+auto3.KEY_WORDS);        } else {             Log.i("+++++++++++++++","auto3 is null!");        }

查询结果集

 List list = new Autocomplete().executeQuery("select * from autocomplete where KEY_WORDS = 'testtest'");  Log.i("+++++++++++++++","list.size()="+list.size());  // 根据sql条件查询

List list2 = new Autocomplete().executeQuery("select * from autocomplete where KEY_WORDS = ? and Id = ?","testtest","1"); Log.i("+++++++++++++++","list2.size()="+list2.size()); // 表示查询select * from autocomplete where KEY_WORDS = 'testtest' and Id = '1'

Router

类似于rails的router功能,Activity之间、Fragment之间可以轻易实现相互跳转,并传递参数。 使用Activity跳转必须在Application中做好router的映射。 我们会做这样的映射,表示从某个Activity跳转到另一个Activity需要传递user、password2个参数

      Router.getInstance().setContext(getApplicationContext()); // 这一步是必须的,用于初始化Router        Router.getInstance().map("user/:user/password/:password", SecondActivity.class);

有时候,activity跳转还会有动画效果,那么我们可以这么做

      RouterOptions options = new RouterOptions();        options.enterAnim = R.anim.slide_right_in;        options.exitAnim = R.anim.slide_left_out;        Router.getInstance().map("user/:user/password/:password", SecondActivity.class, options);

在Application中定义好映射,activity之间跳转只需在activity中写下如下的代码,即可跳转到相应的Activity,并传递参数

            Router.getInstance().open("user/fengzhizi715/password/715");

如果在跳转前需要先做判断,看看是否满足跳转的条件,doCheck()返回false表示不跳转,true表示进行跳转到下一个activity

      Router.getInstance().open("user/fengzhizi715/password/715",new RouterChecker(){                 public boolean doCheck() {                   return true;               }        });

单独跳转到某个网页,调用系统电话,调用手机上的地图app打开地图等无须在Application中定义跳转映射。

      Router.getInstance().openURI("http://www.g.cn");          Router.getInstance().openURI("tel://18662430000");          Router.getInstance().openURI("geo:0,0?q=31,121");

Fragment之间的跳转也无须在Application中定义跳转映射。直接在某个Fragment写下如下的代码

          Router.getInstance().openFragment(new FragmentOptions(getFragmentManager(),new Fragment2()), R.id.content_frame);

当然在Fragment之间跳转可以传递参数

 Router.getInstance().openFragment("user/fengzhizi715/password/715",new FragmentOptions(getFragmentManager(),new Fragment2()), R.id.content_frame);

Utils

包含了很多常用的工具类,比如日期操作、字符串操作、SAFUtil里包含各种乱七八糟的常用类等等。

项目主页:http://www.open-open.com/lib/view/home/1410570918820