扫码 项目总结-V1.0.0

入职新公司,许久没有写过文章了,上周二入职,到本周五,总算是把一个项目做完了。今天周日,还有一些遗留细节改完,有一点时间来作个总结。
新项目的业务需求很简单,主要功能就是扫描二维码并显示结果的一个二维码扫描工具。

扫码的业务核心功能我两天就完成了,但是需要在核心功能的基础上,接入Firebase和Admob,这里就涉及到了新的东西了,前前后后在公司的框架下搞了一周半,到昨天才基本上开发完成。一下是本人关于此项目的一些总结。
文章结构:


image.png

一.扫码使用到的核心框架

https://github.com/bingoogolapple/BGAQRCode-Android
基于ZXing开发,文档非常清楚,并且此框架包含了丰富的附加功能,包括可以自定义扫码框样式,自定义扫码界面,监测环境亮度改变,扫描bitmap(本地图片)二维码等等,非常实用。
使用过程中的注意点:

1.1 相机权限请求

在AndroidManifest.,xml中声明权限:



    
...

在扫码界面,检查是否拥有权限并进行请求:

    fun checkAndRequestPermission() {
        //请求权限
        if (ContextCompat.checkSelfPermission(
                this,
                Manifest.permission.CAMERA
            ) == PackageManager.PERMISSION_GRANTED
        ) {
            isPermissionAuth = true
        } else { //否则去请求相机权限
            ActivityCompat.requestPermissions(
                this,
                arrayOf(Manifest.permission.CAMERA),
                100
            )
        }
    }

onRequestPermissionsResult中判断是否请求成功,并启动相机

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if(requestCode==CAMERA_REQUSET_CODE){
            if(grantResults[0]==PackageManager.PERMISSION_GRANTED)
            {
                isPermissionAuth = true
                //请求权限成功后 显示相机
                scanView.startCamera()
                scanView.startSpot()
            }else{
                Log.d("权限请求","onRequestPermissionsResult: 申请权限已拒绝")
            }
        }
    }

这种权限请求方式有点问题,建议封装一下,可以一次方便地请求多个权限

1.2 在适当的地方关闭和启动相机

我是在onResume中启动相机,在onStop中关闭相机,在onDestroy中销毁

    override fun onResume() {
        super.onResume()
        if (isPermissionAuth) {
            //有权限则启动相机
            scanView.startCamera()
            scanView.showScanRect()
            scanView.startSpot()
        }
    }
    override fun onStop() {
        super.onStop()
        scanView.stopCamera()
    }

    override fun onDestroy() {
        super.onDestroy()
        scanView.onDestroy()
    }

启动相机后要调用startSpot(),才开始识别二维码/条形码

1.3 其他注意点

框架提供的onCameraAmbientBrightnessChanged监测了环境亮度改变,如果你的需求是根据环境亮度改变显示打开手电筒按钮,最好做一个延迟处理。环境的亮度随时都在发生改变,也就是说这个方法其实是一直在被调用的。
举个例子:
亮度值1秒内变化如下:12,14,44,37,21,55,40,41,11。再规定亮度值小于50时为黑暗,需要打开手电筒。那包括误差的影响,我们可以看到在以上的环境亮度变化中有一次为55也就是光亮,但是其实只是一瞬间,然后又变暗了,这时变亮的一瞬间是不用隐藏手电筒的。 为了不让我们的app那么灵敏,我采用的方式是,用一个变量记录连续为光亮的次数,当连续光亮大于10次时,才判断为光亮环境,隐藏手电筒按钮。

BGAQRCode-Android框架提供了两个扫码组件:

image.png

简单说一下区别:
ZXing共功能更丰富一点,不光可以扫码,还可以生成二维码。
ZBar基于C语言,运行速度更快,但不可以生成二维码。

扫码成功后最好给个震动反馈

    override fun onScanQRCodeSuccess(result: String?) {
        //加个震动反馈
        //扫码成功 得到字符串
        val vibrator: Vibrator = this.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
        vibrator.vibrate(50)
...

二 关于约束布局ConstraintLayout和Kotlin语言

首先是约束布局ConstraintLayout,以前学习中一直有意或无意的忽略了这个约束布局,这次在项目中使用到,才发现它在屏幕适配方面真的是大显神威,而且当屏幕内组件比较多时,也不会出现多重的嵌套。它的基准线,互相约束,长宽比例这些,真的非常好用。建议所有界面都使用约束布局,学习成本也就两小时,何乐而不为呢。这里推荐一篇文章:
https://www.jianshu.com/p/f110b4fcfe93

然后是Kotlin语言,现在公司所有项目都是Kotlin语言,基于AndroidX。怎么说呢,感觉真的是个大趋势吧,在掌握JAVA的基础上学习Kotlin是真的非常快,可能半天就足够了。并且Kotlin真的是越用越顺手,这里推荐的自己的文章(郭神牛逼):
https://www.jianshu.com/p/a98d786cac92

三 关于Firebase和Admob(学习中...)

Firebase可以理解为APP后台管理系统吧,它包含了非常多的功能,比如事件打点,远程配置等等,关于怎么使用没有什么好说的,官方文档写的非常清楚:
https://firebase.google.cn/docs/android/setup
Firebase包含了非常多的内容,一下是一部分文档中的截图:

Firebase部分功能

Admob是谷歌提供的,开发者可以在应用中通过Admob显示广告,并获得收益,Admob可以单独使用也可以和Firebase一起配合使用,这里是文档:
https://firebase.google.cn/docs/admob/android/quick-start
Admob提过了多种广告类型,现在项目中主要只使用了原生广告。
在开发过程中,必须使用Admob的测试专用id,否则会被封号哈。
然后,使用些服务都是需要梯子的...

四 其他一些知识点

4.1 全局Activity声明周期监听

项目中有一个需求,就是短暂退出到界面后,返回到app,显示广告,这就需要对全局的activity生命周期进行监听,并在适当的时候显示广告界面。
这里使用到了Application.ActivityLifecycleCallbacks
新建一个类AppLifecycle实现接口Application.ActivityLifecycleCallbacks,

class AppLifecycle : Application.ActivityLifecycleCallbacks {
    override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
        Log.d("全局生命周期回调", activity.javaClass.simpleName + "Created")
    }
    override fun onActivityStarted(activity: Activity) {}

    override fun onActivityResumed(activity: Activity) {
        Log.d("全局生命周期回调", activity.javaClass.simpleName + "Resumed")
    }

    override fun onActivityPaused(activity: Activity) {
        Log.d("全局生命周期回调", activity.javaClass.simpleName + "Paused")
    }
    override fun onActivityStopped(activity: Activity) {}
    override fun onActivityDestroyed(activity: Activity) {}
    override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
}

然后在Application的onCreate中注册回调:

    override fun onCreate() {
        super.onCreate()
        //注册全局活动生命周期回调
        registerActivityLifecycleCallbacks(AppLifecycle())
    }

整个app内所有的activity生命周期变化都会回调到这里。就可以在这里做一些事情什么的。

4.2 Gif的播放和监听

使用框架:https://github.com/koral--/android-gif-drawable
布局文件

    

监听和开始播放

        val gifDrawable = startPageGif.drawable as (GifDrawable)
        gifDrawable.loopCount = 1//gif 只播放一次
        gifDrawable.addAnimationListener {
//gif播放完毕 做什么什么事情...

        }
//开始播放gif
        gifDrawable.start()

4.3 应用内复制到剪贴板,分享和打开浏览和器

        resultCopyBtn.setOnClickListener {
            val clipboardManager = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
            //根据scanResult创建clipData
            val clipData = ClipData.newPlainText("Lable", scanResult)
            clipboardManager.setPrimaryClip(clipData)
            AnalyticsManager.instance.sendEvent(ActionEvent.COPY_RESULT)
            Toast.makeText(this, getString(R.string.copy_successful), Toast.LENGTH_SHORT).show()
        }

        resultShareBtn.setOnClickListener {
            var shareIntent = Intent()
            shareIntent.action = Intent.ACTION_SEND
            //确定要分享的类型为文本
            shareIntent.type = "text/plain"
            //传入分享内容scanResult
            shareIntent.putExtra(Intent.EXTRA_TEXT, scanResult)
            //分享选择框标题
            shareIntent =
                Intent.createChooser(shareIntent, getString(R.string.choose_an_app_to_share))
            startActivity(shareIntent)
            AnalyticsManager.instance.sendEvent(ActionEvent.SHARE_RESULT)
        }

        openUrlBtn.setOnClickListener {
            val builder = CustomTabsIntent.Builder()
            val customTabsIntent = builder.build()
            //根据scanResult生成url并传递
            customTabsIntent.launchUrl(this, Uri.parse(scanResult))
            AnalyticsManager.instance.sendEvent(ActionEvent.OPEN_URL)
        }

4.4 Kotlin时间的计算

这里有一个时间工具类TimeUnit(顺便学习Kotlin单例模式使用方法)
内提供了两个方法分别是:
获取当前时间:yyyy-MM-dd

计算两个时间之间相差的天数:
oldDate : yyyy-MM-dd
newDate : yyyy-MM-dd
返回:int

class TimeUnit {

    companion object {
        val instance: TimeUnit by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { TimeUnit() }
    }

    /**
     *
     * 获取当前时间
     * @return String yyyy-MM-dd
     *
     * */

    @SuppressLint("SimpleDateFormat")
    fun getNowTime(): String {
        return if (android.os.Build.VERSION.SDK_INT >= 24) {
            SimpleDateFormat("yyyy-MM-dd").format(Date())
        } else {
            var tms = Calendar.getInstance()
            tms.get(Calendar.YEAR).toString() + tms.get(Calendar.MONTH).toString() + tms.get(
                Calendar.DAY_OF_MONTH
            ).toString()
        }

    }


    /**
     *
     * 通过文件夹日期名 计算日志是否在有限期时间内
     * 适配跨年/闰年
     * @param oldDate 首次启动日期 yyyy-MM-dd
     * @param newDate 当前日期 yyyy-MM-dd
     */
    @SuppressLint("SimpleDateFormat")
    fun calculateDays(oldDate: String, newDate: String): Int {
        val c1 = Calendar.getInstance()
        val c2 = Calendar.getInstance()
        val format= SimpleDateFormat("yyyy-MM-dd")


        c1.time = format.parse(oldDate)
        c2.time = format.parse(newDate)
        val year1 = c1.get(Calendar.YEAR)
        val year2 = c2.get(Calendar.YEAR)
        when {
            year2 > year1 -> {
                //如果跨年了
                val day1 = c1.get(Calendar.DAY_OF_YEAR)
                val day2 = c2.get(Calendar.DAY_OF_YEAR)
                var days = 0
                for (i in year1..year2) {
                    days += if (i == year1) {
                        if (GregorianCalendar().isLeapYear(year1)) {
                            366 - day1
                        } else {
                            365 - day1
                        }
                    } else if (i == year2) {
                        day2
                    } else {
                        if (GregorianCalendar().isLeapYear(year1)) {
                            366
                        } else {
                            365
                        }
                    }
                }
                return days
            }
            year2 == year1 -> {
                //如果没跨年
                return c2.get(Calendar.DAY_OF_YEAR) - c1.get(Calendar.DAY_OF_YEAR)
            }
            else -> {
                return -1
            }
        }
    }

}

4.4 使用Android Studio的Image Asset生成Icon

UI给了我两个svg,说这就是应用图标,最开始不知该怎么用,其实Android Studio早就提供了一个非常好用的Image Asset来生成Icon


svg.png

第一步:


image.png

第二步:这里分别在Foreground Layer 和 Backgroung Layer中选择两个svg文件


image.png

第三步:
image.png

这个项目总结就这么多吧,接下来2.0会增加更多的功能,比如选择本地图片扫码,扫码历史记录,生成二维码等等。目前对Firebase和Admob还不怎么了解,这里也正在写一个demo学习,可能也会写一篇新的文章吧,约束布局也地好好看一下,是真的好用。

你可能感兴趣的:(扫码 项目总结-V1.0.0)