前言
搭建android项目框架前,我们需要先定义要框架的结构,因为android框架本身的结构就很复杂,如果一开始没定义好结构,那么后续的使用就会事倍功半。
结构如下:
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;
- }
- });
简单的输入参数和url后,就可以在匿名类的重写函数中获得返回值。
编写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框架一就已经介绍完了。
代码已经传到Github上了,欢迎大家下载。
下篇文章介绍AspectJX实现AOP的几个实用注解。
Github地址:https://github.com/kiba518/AndroidFramework2.0/
----------------------------------------------------------------------------------------------------
注:此文章为原创,任何形式的转载都请联系作者获得授权并注明出处!
若您觉得这篇文章还不错,请点击下方的【推荐】,非常感谢!
https://www.cnblogs.com/kiba/p/17262561.html
