前言
結構如下:
com.kiba.framework
——activity 儲存所有的活動
——base 儲存baseActivity
——fragment儲存所有的Fragment
——base 儲存baseFragment
——service儲存所有的service
——utils儲存所有的工具類
——dto儲存所有的傳入傳出實體
——model儲存所有的實體類
——model_db儲存所有的資料庫實體類(框架使用ormlit)
建立專案
我們先建立一個專案,File—New—New Project,選擇BasicActivity。
然後建立一個utils資料夾。
新增LogUtils,DateUtils,DecimalUtil檔案,就是簡單的日誌輸出,日期,字串工具。(寫法很多,可以上網任意搜尋)。
然後建立一個異常捕獲檔案——CrashExceptionHandler,用於輸入未捕獲異常日誌(寫法很多,可以上網任意搜尋)。
然後開啟app下的gradle,引入我們常用的包。
網路請求:okhttp。
json處理:gson和fastjson。
黃油刀註解:ButterKnife。
內建資料庫管理:ormlite。
許可權請求:rxpermissions。
圖片處理:glide。
程式碼如下:
//okhttp implementation "com.squareup.okhttp3:okhttp:4.9.0" //gson implementation 'com.google.code.gson:gson:2.8.6' //fastjson implementation group: 'com.alibaba', name: 'fastjson', version: '1.2.83' //解決超過65546程式碼的問題 implementation 'com.android.support:multidex:1.0.2' implementation "com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4" //ButterKnife implementation 'com.jakewharton:butterknife:10.2.3' annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.3' // 資料庫ormlite implementation 'com.j256.ormlite:ormlite-android:5.0' implementation 'com.j256.ormlite:ormlite-core:5.0' //許可權請求rxpermissions implementation 'com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar' //圖片處理glide implementation 'com.github.bumptech.glide:glide:4.14.2' annotationProcessor 'com.github.bumptech.glide:compiler:4.14.2'//執行時 編譯時 處理註解
然後在新增一些json和http的utils(寫法很多,可以上網任意搜尋)。
然後建立MyApplication的java檔案,程式碼如下:
public class MyApplication extends Application { public static Context context;//全域性上下文 public static List<Activity> activityList = new ArrayList<Activity>();//用於存放所有啟動的Activity的集合 public static ApplicationInfo applicationInfo; @Override public void onCreate() { SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Log.d("專案啟動", "專案啟動: " + DateUtils.getTime()); super.onCreate(); context = getApplicationContext(); PackageManager packageManager = getApplicationContext().getPackageManager(); try { packageManager = getApplicationContext().getPackageManager(); applicationInfo = packageManager.getApplicationInfo(getPackageName(), 0); } catch (PackageManager.NameNotFoundException e) { applicationInfo = null; LogUtils.LogHelperError("獲取applicationInfo報錯", e); } CrashExceptionHandler.getInstance().init(this); //解決4.x執行崩潰的問題 MultiDex.install(this); } private boolean isDebug() { return BuildConfig.DEBUG; } public static String GetProperties(String propertyName) { Properties props = new Properties(); String serviceUrl = null; try { InputStream in =context.getAssets().open("appConfig.properties"); props.load(in); String vaule = props.getProperty(propertyName); serviceUrl = new String(vaule.getBytes("ISO-8859-1"), "gbk"); } catch (IOException e) { e.printStackTrace(); AlertDialog.Builder dialog = new AlertDialog.Builder(context); dialog.setTitle("錯誤"); dialog.setMessage("讀取配置檔案失敗"); dialog.setCancelable(false); removeALLActivity(); } return serviceUrl; } /** * 銷燬所有的Activity */ public static void removeALLActivity() { //透過迴圈,把集合中的所有Activity銷燬 for (Activity activity : activityList) { if (!activity.isFinishing()) { activity.finish(); } } MyApplication.activityList.clear(); } }
然後註冊CrashException和MultiDex。
然後找到AndroidManifest.xml,註冊application,並開啟大堆記憶體,如下:
HTTP請求
http請求是我們最常用的工具,下面我們編寫一個簡單的請求工具。
先建立一個資料夾dto,然後在建立一個base,一個user資料夾。
編寫簡單的請求和返回實體如下:
然後編寫HttpUtils程式碼如下:
public class HttpUtils { private static final OkHttpClient client = new OkHttpClient.Builder() .sslSocketFactory(SSLSocketClient.getSSLSocketFactory(), SSLSocketClient.getX509TrustManager()) .hostnameVerifier(SSLSocketClient.getHostnameVerifier()) .connectTimeout(10, TimeUnit.SECONDS)//設定連線超時時間 .readTimeout(20, TimeUnit.SECONDS)//設定讀取超時時間 .build(); private static void getRequest(String url, ICallback callback) throws IOException { new Thread() { @Override public void run() { Request request = new Request.Builder() .url(url) .build(); try (Response response = client.newCall(request).execute()) { String result = response.body().string(); callback.Call(result); } catch (IOException e) { e.printStackTrace(); Log.d("http異常", e.getMessage()); callback.Call(e.getMessage()); } } }.start(); } private static final MediaType mediaType = MediaType.get("application/json; charset=utf-8"); private static void postRequest(String url, String param, ICallback callback) throws IOException { new Thread() { @Override public void run() { RequestBody body = RequestBody.create(mediaType, param); Request request = new Request.Builder() .url(url) .post(body) .build(); try (Response response = client.newCall(request).execute()) { String result = response.body().string(); callback.Call(result); } catch (IOException e) { e.printStackTrace(); BaseResult baseResult=new BaseResult(); baseResult.code=-1; callback.Call(JsonUtils.Serialize(baseResult)); } } }.start(); } private interface ICallback { void Call(String con); } public interface HttpData<T>{ public void getData(T result); } public static <T> void post(String param, String urlAddress, HttpData<T> httpData) { try { HttpUtils.postRequest(urlAddress, param, con -> { runOnUiThread(() -> { BaseResult baseResult = JsonUtils.Deserialize(BaseResult.class, con); if (null != baseResult && baseResult.code == 1) { Class thisClass = httpData.getClass(); Type[] superClassType = thisClass.getGenericInterfaces(); ParameterizedType pt = (ParameterizedType) superClassType[0]; Type[] genTypeArr = pt.getActualTypeArguments(); Type genType = genTypeArr[0]; Class c1= (Class) genTypeArr[0]; T result = (T)JsonUtils.Deserialize(c1, con); httpData.getData(result); } else { if (null != baseResult) { ToastUtils.showToast("資料獲取失敗:" + baseResult.msg); } else { ToastUtils.showToast("資料獲取失敗"); } } }); }); } catch (IOException e) { e.printStackTrace(); } } BaseResult baseResult = (BaseResult)JsonUtils.Deserialize(BaseResult.class, "con"); public static <T> void get(String param, String urlAddress, HttpData<T> httpData) { try { HttpUtils.getRequest(urlAddress, con -> { runOnUiThread(() -> { BaseResult baseResult = (BaseResult)JsonUtils.Deserialize(Object.class, con); if (null != baseResult && baseResult.code == 1) { Class thisClass = httpData.getClass(); Type[] superClassType = thisClass.getGenericInterfaces(); ParameterizedType pt = (ParameterizedType) superClassType[0]; Type[] genTypeArr = pt.getActualTypeArguments(); Type genType = genTypeArr[0]; Class c1= (Class) genTypeArr[0]; T result = (T)JsonUtils.Deserialize(c1, con); httpData.getData(result); } else { if (null != baseResult) { ToastUtils.showToast("資料獲取失敗:" + baseResult.msg); } else { ToastUtils.showToast("資料獲取失敗"); } } }); }); } catch (IOException e) { e.printStackTrace(); } } }
這裡透過泛型反射直接找到了要序列化的型別,減少了呼叫時的程式碼編寫,呼叫程式碼如下:
HttpUtils.get("url","引數", new HttpHelper.HttpData<LoginCommandResult>() { @Override public void getData(LoginCommandResult result) { int code = result.code; } });
編寫Activity與Fragment
應用的頁面切換是以Fragment的替換為主,以儘量少建立Activity為中心思想,框架實現返回按鈕切換fragment。
Activity於Fragment的編寫思路如下:
首先編寫Base檔案,Base檔案這裡採取二級模式,BaseActivity加KActivity、BaseFragment加KFragment。
KBase檔案實現生命週期,Base檔案實現通用函式。
KActivity程式碼簡介:
/** * @return 獲取佈局的id */ protected int getLayoutId() { return -1; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); int layoutId = getLayoutId(); if (layoutId != -1) { this.rootView = View.inflate(this, layoutId, null); setContentView(this.rootView); } else { throw new MissingResourceException("未使用getLayoutId()函式初始化view",this.getClass().getName(),"未初始化view"); } if (savedInstanceState != null) { loadActivitySavedData(savedInstanceState); } }
Base檔案裡將設定佈局檔案給提取出來了,並設定從Bundle裡恢復資料的操作。
繼承Base檔案的Activity實現如下:
public class MainActivity extends BaseActivity { @Override protected int getLayoutId() { return R.layout.activity_main; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } /** * 選單、返回鍵響應 */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { //moveTaskToBack(true); } return true; } }
繼承Base檔案的Fragment實現如下:
public class MainFragment extends BaseFragment { @Override protected int getLayoutId() { return R.layout.fragment_main; } @Override protected void onCreate() { } }
可以看到,在類裡,使用getLayoutId來指定佈局XML檔案,這樣即可清晰的知道佈局檔名,又便於閱讀。
PS:Android是支援多個Activity或Fragment使用同一個XML的,但本框架中,拒絕這個特性,要求佈局檔案與類檔案是一對一的關係。
gradle配置
app.gradle
開啟app的gradle,首先在defaultConfig下增加指定cpu。
ndk { abiFilters 'armeabi', 'armeabi-v7a', 'x86' }
然後在android下面關閉lint檢測。
//不在googlePlay上線,關閉lint檢測 lintOptions { checkReleaseBuilds false abortOnError false }
project.gradle
我用的新版本AS建的專案,所以預設的程式碼是這樣的。
plugins { id 'com.android.application' version '7.3.1' apply false id 'com.android.library' version '7.3.1' apply false }
這裡我們直接將生成的配置刪除,貼上上我們比較熟悉的gradle配置模式,程式碼如下:
buildscript { repositories { maven { url 'https://jitpack.io' } maven { url "https://oss.jfrog.org/libs-snapshot" }//rxjava google() mavenCentral() jcenter() // Warning: this repository is going to shut down soon } dependencies { classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3' classpath 'com.android.tools.build:gradle:7.1.2' } } allprojects { repositories { maven { url 'https://jitpack.io' } maven { url "https://oss.jfrog.org/libs-snapshot" }//rxjava google() mavenCentral() jcenter() // Warning: this repository is going to shut down soon } } tasks.withType(JavaCompile) { options.encoding = "UTF-8" } task clean(type: Delete) { delete rootProject.buildDir }
要注意的是,新版的settings.gradle也變化了,如果只修改build.gradle編譯會拋異常。
生成的setting.gradle程式碼如下:
pluginManagement { repositories { gradlePluginPortal() google() mavenCentral() } } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() } } rootProject.name = "framework" include ':app'
修改程式碼如下:
rootProject.name = "framework"
include ':app'
首頁佈局
結構搭建好後,我們使用LinkageRecyclerView元件,實現一個簡單的雙列表佈局,介面如下:
結語
最後我們看一下專案結構,如下圖:
如上圖,一個簡單的,有序的,支援activity恢復資料,支援fragment返回的框架就搭建完成了。
----------------------------------------------------------------------------------------------------
到此,手寫Android框架一就已經介紹完了。
下篇文章介紹AspectJX實現AOP的幾個實用註解。
Github地址:https://github.com/kiba518/AndroidFramework2.0/
----------------------------------------------------------------------------------------------------
注:此文章為原創,任何形式的轉載都請聯絡作者獲得授權並註明出處!
若您覺得這篇文章還不錯,請點選下方的【推薦】,非常感謝!
https://www.cnblogs.com/kiba/p/17262561.html