簡單的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) {
}
@Subscribe可以使用枚舉
/**
* 使用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
@OnClick 可以在Activity、Fragment、Dialog、View中使用,支持多個組件綁定同一個方法。
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里包含各種亂七八糟的常用類等等。