美文网首页
扫码 项目总结-V1.0.0

扫码 项目总结-V1.0.0

作者: 番茄tomato | 来源:发表于2020-06-29 11:22 被阅读0次

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

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


    image.png

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

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

    1.1 相机权限请求

    在AndroidManifest.,xml中声明权限:

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.qr.scanlife">
        <uses-permission android:name="android.permission.CAMERA" />
    ...
    

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

        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<String>,
            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
    布局文件

        <pl.droidsonroids.gif.GifImageView
            android:id="@+id/startPageGif"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:src="@drawable/start_page_gif"
            />
    

    监听和开始播放

            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

          本文链接:https://www.haomeiwen.com/subject/cyiwfktx.html