本篇涉及內容:html
3 Http請求框架(volley,推薦使用okHttp,RxJava+Rxandroid+retrofit等) 4 JSON解析和構建框架(Gson,fastJson,不要用jackson由於比較大,除非須要用嵌套的需求) 6 JWT 9 消息推送(好比友盟) 10 用戶反饋(好比友盟) 11 數據統計(好比友盟) 12 更新(好比友盟) 13 數據備份和恢復 14 點贊、評論、收藏模塊 15 About界面(版權申明+經常使用軟件設置+版本更新+國際化等) 16 在線crash log上報(好比騰訊的bugly) 17 快速開發框架(這裏推薦使用butterknife和eventbus) 18 內存泄漏檢測工具(leakcanary) 19 圖片加載庫(Glide) 20 加密解密(RSA,MD5,DES) 23 listview/recyclerview的基礎adapter 24 定製搜索框 25 工具類(好比sharepreference,File,ScreenDesity,Sql,字符串處理,dpsp互轉等等) 31 各類新式的Material design兼容控件 32 界面滑動衝突的問題(橫豎衝突、同向衝突) 33 離線登陸功能 34 bitmap緩存策略 35 最後項目要發佈了,那麼久須要混淆和打包了,前者關於混淆網上有不少相關的文章,這裏須要注意的是不少你所使用的第三方庫都須要在混淆的時候給剔除,由於他們是基於反射機制的。這點須要你在使用每一個第三方庫的時候多看他們的說明書多加當心。其次,混淆後必定要打個包從新迴歸測試一下,以避免出現因混淆而致使的問題。
如何設計開發一款Android App【各個模塊須要的技術】java
局部設計mysql
Setting功能設置策略 react
PreferenceActivity & PreferenceFragmentandroid
Ref: Android 首選項框架及PreferenceScreen, PreferenceActivity, PreferenceFragment的用法與分析git
在Android1.0的時候,官方就有了PreferenceActivity,這個類繼承自ListActivity,是一個抽象類,其持有PreferenceManager對象的引用。github
在Android3.0時,因爲Fragment的出現,官方同步支持了PreferenceFragment。sql
同時,PreferenceActivity中的一些API也被相應標記爲過期,官方給出的解釋是:數據庫
由於PreferenceActivity這個類僅僅只被容許展現一個單獨的偏好設置,而這些如今都能在PreferenceFragment中找到了,編程
其實從中能夠看出google官方的思想是用碎片化來達到解藕
【貌似沒什麼可寫的,就是個簡單的相似json的東西】
主題風格選擇
ref: https://github.com/dersoncheng/MultipleTheme【有代碼,有講解】
1. attr.xml中定義:哪些屬性是能夠隨着主題而改變的。
2. style.xml中爲上述這些屬性 根據主題的不一樣而設置不一樣的值。
3. layout中的控件相應屬性設置爲變量:android:textColor = "?attr/main_textcolor"
4. 在基類的onCreate方法裏添加切換主題模式的邏輯代碼(至關於默認主題設置)。
public class BaseActivity extends Activity{ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if(SharedPreferencesMgr.getInt("theme", 0) == 1) { setTheme(R.style.theme_2); } else { setTheme(R.style.theme_1); } } }
5. 設置按鍵觸發事件切換主題。
btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(SharedPreferencesMgr.getInt("theme", 0) == 1) { SharedPreferencesMgr.setInt("theme", 0); setTheme(R.style.theme_1); } else { SharedPreferencesMgr.setInt("theme", 1); setTheme(R.style.theme_2); }
});
6. 針對切換主題模式時須要對頁面ui當即更新,須要使用框架裏的封裝控件。
這裏是動態更新主題,還須要進一步理解原理。
或者,不妨使用一些框架,例如:Android 主題動態切換框架:Prism ----> viewpager的大翻身經驗效果demo
多語言選擇
傳統方案:Android多語言項目的實現
進入到res目錄下,建立對應語言的values目錄,命名規則爲values-語言的縮寫-語言的簡稱,例如「values-zh-rCN」,表達的意思爲:「中文-簡體中文」。
import java.util.Locale;
public class BaseActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 1.初始化PreferenceUtil PreferenceUtil.init(this); // 2.根據上次的語言設置,從新設置語言 switchLanguage(PreferenceUtil.getString("language", "zh")); // --> } protected void switchLanguage(String language) { //設置應用語言類型 Resources resources = getResources(); Configuration config = resources.getConfiguration(); DisplayMetrics dm = resources.getDisplayMetrics();
if (language.equals("en")) { config.locale = Locale.ENGLISH; } else { config.locale = Locale.SIMPLIFIED_CHINESE; } resources.updateConfiguration(config, dm); //保存設置語言的類型 PreferenceUtil.commitString("language", language); } }
對象關係映射(Object Relational Mapping,簡稱ORM)是經過使用描述對象和數據庫之間映射的元數據,將面嚮對象語言程序中的對象自動持久化到關係數據庫中。
爲何要用ORM?
由於JDBC很差用!
通常狀況下,當預料到數據庫會複雜到某個程度,就有必要引入數據庫的ORM框架,這樣能夠大大下降開發和維護的成本。
用JDBC的API編程訪問數據庫,代碼量較大,特別是訪問字段較多的表的時候,代碼顯得繁瑣、累贅,容易出錯,例如:
public void addAccount(final Account account) throws DAOException { final Connection conn=getConnection(); PreparedStatement pstmt=con.prepareStatment("insert into account value(?,?,?,?,?,?,?,?,?)"); pstmt.setString(1,account.getUserName()); pstmt.setInt(2,account.getPassWord()); pstmt.setString(3,account.getSex()); pstmt.setString(4,account.getQq()); ...... pstmt.execute(); conn.Close(); }
ORM實現的效果以下:
public Double calcAmount(String customerid, double amount) { // 根據客戶ID得到客戶記錄 Customer customer = CustomerManager.getCustomer(custmerid); // 根據客戶等級得到打折規則 Promotion promotion = PromotionManager.getPromotion(customer.getLevel()); // 累積客戶總消費額,並保存累計結果 customer.setSumAmount(customer.getSumAmount().add(amount); CustomerManager.save(customer); // 返回打折後的金額 return amount.multiply(protomtion.getRatio()); }
選一個ORM試試!
ref: 最好的5個Android ORM框架
ref: Android ORM框架選擇的一些總結
OrmLite 不是 Android 平臺專用的ORM框架,它是Java ORM。支持JDBC鏈接,Spring以及Android平臺。語法中普遍使用了註解(Annotation)。
SugarORM 是 Android 平臺專用ORM。提供簡單易學的APIs。能夠很容易的處理1對1和1對多的關係型數據,並經過3個函數save(), delete() 和 find() (或者 findById()) 來簡化CRUD基本操做。
GreenDao是一個很快的解決方案,它可以支持數千條記錄的CRUD每秒,和OrmLite相比,GreenDAO要快幾乎4.5倍。
Active Record(活動目錄)是Yii、Rails等框架中對ORM實現的典型命名方式。Active Android 幫助你以面向對象的方式來操做SQLite。
Realm 是一個將可使用的Android ORM,基於C++編寫,直接運行在你的設備硬件上(不須要被解釋),所以運行很快。它同時是開源跨平臺的,iOS的代碼能夠在GitHub找到,你還能夠找到Objective-C以及Swift編寫的Realm使用實例。
ObjectBox 速度方面碾壓SQLite.
需求:
1, 標題欄:項目中基本每一個acctivity都會有相同的標題欄,返回事件相同,標題文字都居中,標題欄側邊按鈕有些界面有,有些界面沒有
2, 加載界面統一
3, 聯網獲取失敗界面顯示
4, 再按一次返回鍵退出
5, activity管理
Goto: https://www.jianshu.com/p/b2458fc96a65【頁尾有代碼】
Goto: BaseActivity 裏到底應該寫哪些內容?【更爲詳細】
Application,與Activity、Service同樣是Android框架的一個系統組件,當Android程序啓動時系統會建立一個Application對象,用來存儲系統的一些信息。
Android系統自動會爲每一個程序運行時建立一個Application類的對象且只建立一個,因此Application能夠說是單例(singleton)模式的一個類。
啓動Application時,系統會建立一個PID,即進程ID,全部的Activity都會在此進程上運行。
那麼咱們在Application建立的時候初始化全局變量,同一個應用的全部Activity均可以取到這些全局變量的值,換句話說,咱們在某一個Activity中改變了這些全局變量的值,那麼在同一個應用的其餘Activity中值就會改變。
Application對象的生命週期是整個程序中最長的,它的生命週期就等於這個程序的生命週期。
由於它是全局的單例的,因此在不一樣的Activity、Service中得到的對象都是同一個對象。
1. 想要在系統啓動時,首先初始化一些東西,可自定製Application類。
2. 有時候咱們獲取Context並不太容易,可是context又是必須的,定義本身的Application,讓你任什麼時候候均可以獲取到想要的全局Context。
3. 能夠經過Application來進行一些,如:數據傳遞、數據共享和數據緩存等操做。這種全局變量方法相對靜態類更有保障,直到應用的全部Activity所有被destory掉以後纔會被釋放掉。
定義:
import android.app.Application; import android.content.Context;
public class MyApplication extends Application { private static Context context;
@Override public void onCreate() { super.onCreate(); context = getApplicationContext(); } /** * 獲取全局的Context * @return */ public static Context getContext(){ return context; } }
配置:告知系統加載咱們自定義的Application類在AndroidManifest.xml
中
<application android:name="com.cml.example.MyApplication" ...> </application>
接下來在項目的任何地方你只須要調用MyApplication.getContext()就能夠獲得你想要的context了。
實戰:
第一個activity設置一個值,讓另一個activity也能get到。
public class FirstActivity extends Activity { private CustomApplication app; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); app = (CustomApplication) getApplication(); // 得到CustomApplication對象 Log.i("FirstActivity", "初始值=====" + app.getValue()); // 獲取進程中的全局變量值,看是不是初始化值 app.setValue("Harvey Ren"); // 從新設置值 Log.i("FirstActivity", "修改後=====" + app.getValue()); // 再次獲取進程中的全局變量值,看是否被修改 Intent intent = new Intent(); intent.setClass(this, SecondActivity.class); startActivity(intent); } }
第二個activity得到該變化。
public class SecondActivity extends Activity { private CustomApplication app; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); app = (CustomApplication) getApplication(); // 獲取應用程序 Log.i("SecondActivity", "當前值=====" + app.getValue()); // 獲取全局值 } }
AndroidManifest.xml
中作一點改變。
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.test" android:versionCode="1" android:versionName="1.0"> <uses-sdk android:minSdkVersion="8" /> <application android:icon="@drawable/icon" android:label="@string/app_name" android:name="CustomApplication"> <!-- 將咱們之前一直用的默認Application設置成自定義的CustomApplication --> <activity android:name=".FirstActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".SecondActivity" android:label="@string/app_name"> </activity> </application> </manifest>
Ref: getApplication()和getApplicationContext()區別
相同:
兩個方法獲取的是同一個對象。
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
Application application = getApplication(); Log.i("WY", "打印getApplication:" + application);
Context pContext = getApplicationContext(); Log.i("WY", "打印getApplicationContext:" + pContext); } }
區別:
getApplication()是用來獲取Application實例的,可是該方法只在Activity和Service中才能調用;
在一些其餘的地方,好比說當咱們在BroadcastReceiver中也想獲取Application實例,這時就須要使用getApplicationContext()方法
總體設計
資源:Android Architecture Blueprints【Google官方推薦代碼框架,適合coding from scratch】
From: Android項目開發如何設計總體架構?【思惟的逐漸深刻,值得進一步研究】
框架代碼:https://github.com/ShonLin/QuickDevFramework【我的的一個框架總結】
FLUX是Facebook提出的一種架構,有點小複雜,此處做爲開篇,停留在簡單的瞭解層面。
本篇重點先放在傳統的架構的理解和使用。
推薦用最新的Android Flux來架構你的Android程序,Facebook提出的架構,文檔比較全,數據流老是單向的。用過MVC, MVP後,我仍是是比較認同Flux的,並且以前公司用的架構模式跟Flux也比較像。
參考文章:
數據流老是單向的
一個單向的數據流 是 Flux 架構的核心,也是它簡單易學的緣由。就以下面討論的,在進行應用測試的時候,它提供了很是大的幫助。
應用被分紅三個主要部分:
View: 應用的界面。這裏建立響應用戶操做的action。
Dispatcher: 中心樞紐,傳遞全部的action,負責把它們運達每一個Store。
Store: 維護一個特定application domain的狀態。它們根據當前狀態響應action,執行業務邏輯,同時在完成的時候發出一個change事件。這個事件用於view更新其界面。
讓咱們從最簡單的架構開始。
瞭解其演化歷史,才能瞭解其本質。
注意這裏的IPresenter還有IView。
Presenter層中的業務邏輯,也比較容易作單元測試,作代碼複用,作進一步的抽象和分離。
拋出一個高級例子:
Ref: https://github.com/BaronZ88/MinimalistWeather
MVP+RxJava在實際項目中的應用,MVP中RxJava生命週期的管理
但咱們仍是從入門例子開始:
Ref: MVP模式在Android項目中的使用【閱讀筆記】
View與Model並不直接交互,而是使用Presenter做爲View與Model之間的橋樑。
其中Presenter中同時持有View層以及Model層的Interface的引用,而View層持有Presenter層Interface的引用。
這就是MVP模式的整個核心過程:
好處:
減小了Model與View層之間的耦合度。
---------------------------------------------------- view ----------------------------------------------------
需求定義
新聞列表模塊主要是展現從網絡獲取的新聞列表信息,View層的接口大概須要以下方法:
(1)加載數據的過程當中須要提示「正在加載」的反饋信息給用戶。
(2)加載成功後,將加載獲得的數據填充到RecyclerView展現給用戶。
(3)加載成功後,須要將「正在加載」反饋信息取消掉。
(4)若加載數據失敗,如無網絡鏈接,則須要給用戶提示信息。
View層的接口定義
先是定義套路中的四個接口。
public interface NewsView { void showProgress(); void addNews(List<NewsBean> newsList); void hideProgress(); void showLoadFailMsg(); }
actvity / fragment中的接口實現
public class NewsListFragment extends Fragment implements NewsView, SwipeRefreshLayout.OnRefreshListener {
public static NewsListFragment newInstance(int type) {...}
@Override public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mNewsPresenter = new NewsPresenterImpl(this); // ---->
mType = getArguments().getInt("type");
}
@Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {...}
private RecyclerView.OnScrollListener mOnScrollListener = new RecyclerView.OnScrollListener() {...}; private NewsAdapter.OnItemClickListener mOnItemClickListener = new NewsAdapter.OnItemClickListener() {...}
@Override public void showProgress() { mSwipeRefreshWidget.setRefreshing(true); }
@Override public void addNews(List<NewsBean> newsList) { // 加載成功後,將數據展現給用戶 mAdapter.isShowFooter(true); if(mData == null) { mData = new ArrayList<NewsBean>(); } mData.addAll(newsList); if(pageIndex == 0) { mAdapter.setmDate(mData); // <-------- } else { //若是沒有更多數據了,則隱藏footer佈局 if(newsList == null || newsList.size() == 0) { mAdapter.isShowFooter(false); } mAdapter.notifyDataSetChanged(); } pageIndex += Urls.PAZE_SIZE; }
@Override public void hideProgress() { mSwipeRefreshWidget.setRefreshing(false); }
@Override public void showLoadFailMsg() { if(pageIndex == 0) { mAdapter.isShowFooter(false); mAdapter.notifyDataSetChanged(); } Snackbar.make(getActivity().findViewById(R.id.drawer_layout), getString(R.string.load_fail), Snackbar.LENGTH_SHORT).show(); }
---------------------------------------------------------------------
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); if (newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItem + 1 == mAdapter.getItemCount() && mAdapter.isShowFooter())
{ LogUtils.d(TAG, "loading more data"); mNewsPresenter.loadNews(mType, pageIndex + Urls.PAZE_SIZE); // 加載更多 } }
@Override public void onRefresh() { pageIndex = 0; if(mData != null) { mData.clear(); } mNewsPresenter.loadNews(mType, pageIndex); }
}
加入一級大目錄:
public class NewsFragment extends Fragment { @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {...} private void setupViewPager(ViewPager mViewPager) { //Fragment中嵌套使用Fragment必定要使用getChildFragmentManager(),不然會有問題 MyPagerAdapter adapter = new MyPagerAdapter(getChildFragmentManager()); adapter.addFragment(NewsListFragment.newInstance(NEWS_TYPE_TOP), getString(R.string.top)); adapter.addFragment(NewsListFragment.newInstance(NEWS_TYPE_NBA), getString(R.string.nba)); adapter.addFragment(NewsListFragment.newInstance(NEWS_TYPE_CARS), getString(R.string.cars)); adapter.addFragment(NewsListFragment.newInstance(NEWS_TYPE_JOKES), getString(R.string.jokes)); mViewPager.setAdapter(adapter); } public static class MyPagerAdapter extends FragmentPagerAdapter {...} }
---------------------------------------------------- model ----------------------------------------------------
public interface NewsModel { void loadNews(String url, int type, OnLoadNewsListListener listener); // 內部使用的是回調的方法 voidloadNewsDetail(String docid, OnLoadNewsDetailListener listener); }
利用OkHttp實現了網絡通訊。
將網絡請求進行封裝class OkHttpUtils,能夠減小不少的代碼量,而且後期若是我不想用okhttp了,想換成其它的庫,修改起來也方便。
【注意,這裏使用了回調的方法】
public class NewsModelImpl implements NewsModel { @Override public void loadNews(String url, final int type, final OnLoadNewsListListener listener) { OkHttpUtils.ResultCallback<String> loadNewsCallback = new OkHttpUtils.ResultCallback<String>() { @Override public void onSuccess(String response) { <---- 回調 List<NewsBean> newsBeanList = NewsJsonUtils.readJsonNewsBeans(response, getID(type)); listener.onSuccess(newsBeanList); } @Override public void onFailure(Exception e) { <---- 回調 listener.onFailure("load news list failure.", e); } };
OkHttpUtils.get(url, loadNewsCallback); } @Override public void loadNewsDetail(final String docid, final OnLoadNewsDetailListener listener) { String url = getDetailUrl(docid); OkHttpUtils.ResultCallback<String> loadNewsCallback = new OkHttpUtils.ResultCallback<String>() { @Override public void onSuccess(String response) { <---- 回調 NewsDetailBean newsDetailBean = NewsJsonUtils.readJsonNewsDetailBeans(response, docid); listener.onSuccess(newsDetailBean); } @Override public void onFailure(Exception e) { <---- 回調 listener.onFailure("load news detail info failure.", e); } };
OkHttpUtils.get(url, loadNewsCallback); } private String getID(int type) {...} private String getDetailUrl(String docId) {...} }
public interface NewsPresenter { void loadNews(int type, int page); }
NewsPresenterImpl的構造函數中須要傳入View層的接口對象NewView,而且須要建立一個NewsModel對象。Presenter的具體實現:
public class NewsPresenterImpl implements NewsPresenter, OnLoadNewsListListener { private static final String TAG = "NewsPresenterImpl"; private NewsView mNewsView; private NewsModel mNewsModel; public NewsPresenterImpl(NewsView newsView) { this.mNewsView = newsView; this.mNewsModel = new NewsModelImpl(); // 至關於造了一個工具,提供網絡通訊服務 } @Override public void loadNews(final int type, final int pageIndex) { String url = getUrl(type, pageIndex); LogUtils.d(TAG, url); //只有第一頁的或者刷新的時候才顯示刷新進度條 if(pageIndex == 0) { mNewsView.showProgress(); } mNewsModel.loadNews(url, type, this); // Jeff: 可見,只有presenter會使用這個包裹回調方法的loadNews,view是不知道的,但view會調用presenter的loadNews } /** * 根據類別和頁面索引建立url * @param type * @param pageIndex * @return */ private String getUrl(int type, int pageIndex) { StringBuffer sb = new StringBuffer(); switch (type) { case NewsFragment.NEWS_TYPE_TOP: sb.append(Urls.TOP_URL).append(Urls.TOP_ID); break; case NewsFragment.NEWS_TYPE_NBA: sb.append(Urls.COMMON_URL).append(Urls.NBA_ID); break; case NewsFragment.NEWS_TYPE_CARS: sb.append(Urls.COMMON_URL).append(Urls.CAR_ID); break; case NewsFragment.NEWS_TYPE_JOKES: sb.append(Urls.COMMON_URL).append(Urls.JOKE_ID); break; default: sb.append(Urls.TOP_URL).append(Urls.TOP_ID); break; } sb.append("/").append(pageIndex).append(Urls.END_URL); return sb.toString(); } @Override public void onSuccess(List<NewsBean> list) { mNewsView.hideProgress(); mNewsView.addNews(list); } @Override public void onFailure(String msg, Exception e) { mNewsView.hideProgress(); mNewsView.showLoadFailMsg(); } }
再回首view中的使用體驗:
public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); if (newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItem + 1 == mAdapter.getItemCount() && mAdapter.isShowFooter())
{ LogUtils.d(TAG, "loading more data"); mNewsPresenter.loadNews(mType, pageIndex + Urls.PAZE_SIZE); // 加載更多 } } public void onRefresh() { pageIndex = 0; if(mData != null) { mData.clear(); } mNewsPresenter.loadNews(mType, pageIndex); }
以上就是MVP的整個過程。
Ref: 理解RESTful架構
Fielding將他對互聯網軟件的架構原則,定名爲REST,即Representational State Transfer的縮寫。我對這個詞組的翻譯是"表現層狀態轉化"。
每種資源對應一個特定的URI。所謂"上網",就是與互聯網上一系列的"資源"互動,調用它的URI。
狀態轉化(State Transfer):GET用來獲取資源,POST用來新建資源(也能夠用於更新資源),PUT用來更新資源,DELETE用來刪除資源。
Ref: RESTful API 設計指南
RESTful API是目前比較成熟的一套互聯網應用程序的API設計理論。
Ref: 怎樣用通俗的語言解釋REST,以及RESTful?
就是用URL定位資源,用HTTP描述操做。
看Url就知道要什麼
看http method就知道幹什麼
看http status code就知道結果如何