• 从头开始,手写android应用框架(一)


    前言

    搭建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 activityList = new ArrayList();//用于存放所有启动的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{
            public void getData(T result); 
        }
        public static  void post(String param, String urlAddress, HttpData 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  void get(String param, String urlAddress, HttpData 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() {
         @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

     

     

  • 相关阅读:
    新手开抖店——一定忽略的“发货”问题,违规必扣保证金!
    ThreadLocal使用及原理
    2023 年全国大学生数学建模竞赛题D 题 圈养湖羊的空间利用率思路详解+Python源码(二)
    【web-渗透测试方法】(15.4)测试会话管理机制
    sql编写踩坑总结-join篇
    服务器内存故障预测居然可以这样做!
    【IoT】生产制造:锅仔片上机做 SMT 加工吗?
    tcp网络编程——2
    大学物理---质点运动学
    零基础学python之元组
  • 原文地址:https://www.cnblogs.com/kiba/p/17262561.html