簡單的android框架:SAF

jopen 10年前發布 | 40K 次閱讀 SAF Android開發 移動開發

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里包含各種亂七八糟的常用類等等。

項目主頁:http://www.baiduhome.net/lib/view/home/1410570918820

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!