Skip to content

tracyzmq/SAF

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SAF

SAF(Simple Android Framework)是一个简单的android框架,它为开发Android app提供了基础性组件。
SAF已经在多个项目中使用,包括今夜酒店特价app、锦江之星app、京东内部的多个app等等。这个项目第一次提交到google code是2012年的3月26号,我已经断断续续做了2年多了。2014年9月开始frankswu加入跟我一起开发SAF
目前google code上的工程暂停维护,迁移到github上。它刚刚到1.1.x版本,肯定会存在各种各样的问题。遇到任何问题欢迎跟我的qq联系,qq:63067756

saf的demo地址: https://github.com/frankswu/saf_android_demo

主要功能

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
          }
          
         @Override
		 public void onFail(RestException exception){
					
		  }
      });

同步调用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) {
         }
            
         @Override
		 public void onFail(RestException exception){
					
		 }
    
       });

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

      RestClient.post(urlString, map, new HttpResponseHandler(){

          @Override
          public void onSuccess(String content) {

          }
            
          @Override
		   public void onFail(RestException exception){
		    }
                                    
      });

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)));
        }
    }
    
    ....
}

OnItemClick

 
	@OnItemClick(id=R.id.listview)
	void itemClickListView(AdapterView parent, View view,
			int position, long id) {
		IncomeVO vo= (IncomeVO) listview.getItemAtPosition(position);				
		Intent i = new Intent(mContext,MyIncomeDetailActivity.class);
		Bundle bundle = new Bundle();
		bundle.putString("month", vo.month);
		bundle.putString("allIncome", Double.toString(vo.allIncome));
		i.putExtras(bundle);
		startActivity(i);
	}

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里包含各种乱七八糟的常用类等等。

About

SAF(Simple Android Framework)是一个简单的android框架,它为开发Android app提供了基础性组件。

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Java 100.0%