简单的android框架:SAF
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里包含各种乱七八糟的常用类等等。