Skip to content

hybridappnest/AppNestAndroid

Repository files navigation

Android壳子

壳子已有功能
可配置的由web页面组成的首页
可打开web页面,支持带参数
可关闭web页面,并将参数带回
js调起图片选择,选择图片或视频,或拍照录制视频,并上传oss, 之后组织参数回传给web
二维码扫描
js图片视频预览
全局异常处理
原生权限请求
定位功能
壳子功能展望
原生登录页面(需要与具体业务结合)
推送厂商渠道接入(没有IM推送,为了服务端方便,可能还是要使用阿里推送等集成推送方案)
首页中我的tab原生页面(可以抽取简单页面结构,具体功能需要与业务结合)
设置页面基础功能(清楚缓存,关于App等)
我的tab json配置
首页底部tab json配置
登录页面web化,原生提供js方法写入用户数据(用户数据需要有一定的规则)
配置统一化
闪屏页广告资源下载

目录结构

.
├── README.md // 说明文档
├── setting.gradle //模块配置
└── app //app主干
└── camera //拍照相关
└── core //基础功能
└── imagepicker //图片选择
└── player //播放器组件
└── push //推送功能
└── tuikit //IM功能
└── versionPlugin //includePlugin依赖管理
     ├── src
          ├── main.java.com.ymy.plugin
                ├── BuildConfig.kt 具体App配置,包括包名,版本,三方库配置等

简介

使用H5页面搭建一个基础应用,包含于基础功能的js交互功能 支持功能

  • 首页多个H5页面
  • 打开独立H5页面
  • 支持js图片,视频选择上传
  • 支持js拍照,录像上传
  • 支持js图片,视频轮播预览
  • 包含原生网络请求框架(okhttp3 + retrofit)
  • 包含推送框架(厂商)
  • 包含依赖注入框架(koin)
  • 包含基础页面结构(支持ViewBinding,ViewModel等)
  • 全局异常捕获
  • 扫描功能(华为扫码库实现)
  • 权限检查及请求
  • 基础交互弹窗,提示Toast
  • 登录基础页面(逻辑需要自行添加)
注意:本项目使用gradle-7.0.2,要求Gradle JDK为11以上

一、H5首页tab配置

相关类HomeTabFragment,HomeBottomNavigationItem

在app目录下的src/main/assets中存放了main.json文件 在无接口配置的情况下,会通过读取本地配置,初始化App,其中的type字段对应模块定义如下

object HomeTabType {
    /**
     * 空行
     */
    const val empty = "empty"


    /**
     * web页面
     */
    const val web = "web"

    /**
     * 我的页面
     */
    const val mine = "mine"

    /**
     * IM
     */
    const val IM = "IM"
}

1、HomeTabFragment为首页展示的具体UI结构

其中mHomeBottomNavigationList控制展示的底部栏tab个数及相关样式

private val mHomeBottomNavigationList = mutableListOf<HomeBottomNavigationItem>(
    HomeBottomNavigationItem.NAV_1,
    HomeBottomNavigationItem.NAV_2,
    HomeBottomNavigationItem.NAV_3,
    HomeBottomNavigationItem.NAV_4,
    HomeBottomNavigationItem.NAV_5,
)

HomeBottomNavigationItem为封装tab图标,文字,颜色,页面Url等相关配置

sealed class HomeBottomNavigationItem constructor(
    val name: Int,
    val imageRes: Int,
    val imageResSelected: Int,
    var unRead: Int = 0,
    val textColorRes: Int = R.color.gray595F7E,
    val textColorResSelected: Int = R.color.blue32B8EC,
    var isSelected: Boolean = false,
    var url: String = "",
) 

mHomeBottomNavigationList 与 fragmentList 保持一一对应

2、WebViewFragment为具体的web页面容器

WebViewFragment构造方法支持参数如下

参数 功能
url 页面地址
titile 展示用titile文案
showTitileBar 是否展示原生头部
showBackIcon 是否展示原生回退按钮
addTopPadding 是否适配刘海屏
showRefreshBar 是否展示下拉刷新
pushData js调起新页面时的携带数据
fun newInstance(
    url: String,
    title: String = "",
    showTitleBar: Boolean = true,
    showRightMenu: Boolean = false,
    showBackIcon: Boolean = true,
    addTopPadding: Boolean = false,
    showRefreshBar: Boolean = false,
    pushData: String = "",
): WebViewFragment

3、 js交互(JSBridge)

支持的js事件

参数 功能
js_fun_setTitle 展示用titile文案
js_fun_setOptionMenu 设置功能按钮
js_fun_postNotification 发送意图通知
js_fun_popWindow 推出页面
js_fun_pushWindow 打开页面
js_fun_init web初始化js

js_fun_postNotification包含的js事件

/**
 * js调用通知的事件
 */
object JSNotificationAction {

    /**
     * 展示大图
     */
    const val jsshowGallery = "showGallery"

    /**
     * 弹出toast
     */
    const val jsShowToast = "showToast"


    /**
     * 获取用户数据,收到和回写一致
     */
    const val jsFetchUserInfo = "fetchUserInfo"

    /**
     * 获取用户数据,收到和回写一致
     */
    const val jsSignature = "signature"

    /**
     * 获取用户权限
     */
    const val jscheckPermission = "checkPermission"

    /**
     * js操作页面自动锁屏
     */
    const val jsAutolockScreen = "autolockScreen"

    /**
     * js事件标记当前页面为返回时的目标页面(应用场景:当有一个页面需要打开多个webView后续页面,处理完成后回到目标页面时使用)
     */
    const val jspopTo = "popTo"

    /**
     * js标记当前页面需要在onResume是刷新
     */
    const val jsneedRefreshOnResume = "needRefreshOnResume"

    /**
     * 刷新页面数据用
     */
    const val jsNeedReloadData = "needReloadData"
}

二、权限请求

DBXPermissionUtils中向外暴露了一个方法requestPermission

/**
 * 权限请求
 * @param context Context 上下文对象
 * @param notice String 提示文案
 * @param permissions Array<String> 权限集合
 * @param actionGranted Function0<Unit> 请求权限成功后执行的代码块
 * @param actionDenied Function0<Unit> 请求权限失败后执行的代码块
 */
fun requestPermission(
    context: Context,
    notice: String,
    permissions: Array<String>,
    actionGranted: () -> Unit,
    actionDenied: () -> Unit,
)

三、图片选择,拍照,上传

因图片选择等事件都需要原生Activity支持,所有做了一个中间页面JSBridgeActivity,在其内部完成需要Activity支持的事件处理

1、图片选择由ImagePicker负责

需要现在MainApplication中进行初始化

    ImagePreManager.init(this)

图片选择动作对入参进行了封装,直接调用ImagePickerExts中的方法即可实现

/**
 * Activity调起选择图片
 * @param act Activity 界面
 * @param maxCount Int 最大选择图片数
 * @param requestCode Int 请求code
 * @param mPhotoVideoPathList ArrayList<String> 已选中的图片地址
 * @param actionGrantedAfter Function0<Unit>? 因需要权限请求,该参数中传入的代码块会在请求权限成功后执行
 * @param showCamera Boolean 是否展示拍照按钮
 * @param showImage Boolean 是否展示图片
 * @param showVideo Boolean 是否展示视频
 * @param filterGif Boolean 是否过滤gif
 * @param setSingleType Boolean 是否只能单类型选中,即只能选图片或只能选视频
 * @param needWatermark Boolean 是否添加水印
 * @param actionDeniedAfter Function0<Unit>? 因需要权限请求,该参数中传入的代码块会在请求权限失败后执行
 */
fun startPickerImage(
    act: Activity,
    maxCount: Int,
    requestCode: Int,
    mPhotoVideoPathList: ArrayList<String> = arrayListOf(),
    actionGrantedAfter: (() -> Unit)? = null,
    showCamera: Boolean = true,
    showImage: Boolean = true,
    showVideo: Boolean = true,
    filterGif: Boolean = false,
    setSingleType: Boolean = false,
    needWatermark: Boolean = true,
    actionDeniedAfter: (() -> Unit)? = null,
) 

其中的拍照由CameraV2Activity页面完成功能,已封装了对应权限的请求功能

ImagePicker中的拍照视频默认由系统浏览器完成功能,但是为了统一处理需要交给CameraV2Activity完成工作,所以需要在MainApplication设置ImagePicker对应动作的回调,以调用CameraV2Activity页面

    private fun initImagePicker() {
    ImagePicker.getInstance().setCameraViewCallBack { _, type, callBack ->
        CameraV2Activity.invoke(this,
            if (type == ImagePicker.CameraView.type_all) {
                JCameraView.BUTTON_STATE_BOTH
            } else {
                JCameraView.BUTTON_STATE_ONLY_CAPTURE
            }, object : IUIKitCallBack {
                override fun onSuccess(data: Any) {
                    if (data is String) {
                        callBack.onSuccess(ImagePicker.CameraViewCallBack.type_photo, data)
                    } else if (data is Intent) {
                        val videoPath = data.getStringExtra(TUIKitConstants.CAMERA_VIDEO_PATH)
                        callBack.onSuccess(ImagePicker.CameraViewCallBack.type_video, videoPath)
                    }
                }

                override fun onError(module: String, errCode: Int, errMsg: String) {
                    callBack.onError(module, errCode, errMsg)
                }
            })
    }
}

2、上传模块(UploadViewModel)

其对外只提供一个上传方法,以回调的方式返回结果

    interface CallBack {
    fun onSuccess(ossTag: String, resultList: ArrayList<String>)
    fun showLoading(show: Boolean)
    fun onError(errorMsg: String)
}

fun uploadFile(fileList: ArrayList<String>, path: String, callBack: CallBack) {
    callBack.showLoading(true)
    OSSManager().startUploadFileList(fileList, path, callBack)
}

OSSManager中以协程封装了多文件上传的功能,具体的oss地址配置也在该类中

为了方便web页面,对上传的图片和视频进行了统一处理,视频要添加对应的封面图 WebUploadMediaHelper该类会对上传的图片视频文件进行甄别,如果是视频则对其进行截图,并在按照原有的文件顺序,对新的图片视频数据进行排序返回

四、我的页面

1、原生的我的tab页面

头部用户头像等,具体数据需要使用时具体写入

tab下的list功能可以通过json进行配置,目前定义的动作包括下面的几种

/**
 * 我的tab中可以执行的动作
 */
object MineListAction {
    /**
     * 空行
     */
    const val empty = "empty"

    /**
     * 设置中心
     */
    const val settingCenter = "settingCenter"

    /**
     * 扫码
     */
    const val QRCodeScanner = "QRCodeScanner"

    /**
     * 安全中心
     */
    const val about = "about"

    /**
     * web页面
     */
    const val web = "web"
}

json中可以配置的数据包括

/**
 * 我的tab下的list
 * @property name String 显示名称
 * @property iconRes Int 图标资源id
 * @property iconUrl String 图标url
 * @property action String 动作
 * @property redDot Int 未读红点数
 * @property url String 跳转url
 * @constructor
 */
data class MineItemBean(
    val name: String = "",
    val iconRes: Int = -1,
    val iconUrl: String = "",
    val action: String = MineListAction.empty,
    var redDot: Int = 0,
    val url: String = "",
) : Serializable

五、IM功能组件化

拆分IM功能到独立module中,使用单独的IMManager(ContentProvider)进行在App启动时的初始化工作

具体配置,更新中。。。

六、闪屏页替换

要自定义闪屏页图片的,可以通过替换app module中的splash_default.webp文件实现,一张图片的不同尺寸,在对应尺寸的mipmap目录下

如需要自定义闪屏页动画等功能,请自行修改SplashActivity

加入讨论群

微信 QQ
微信 QQ

LICENSE

Apache License Version 2.0