入职新公司,许久没有写过文章了,上周二入职,到本周五,总算是把一个项目做完了。今天周日,还有一些遗留细节改完,有一点时间来作个总结。
新项目的业务需求很简单,主要功能就是扫描二维码并显示结果的一个二维码扫描工具。
扫码的业务核心功能我两天就完成了,但是需要在核心功能的基础上,接入Firebase和Admob,这里就涉及到了新的东西了,前前后后在公司的框架下搞了一周半,到昨天才基本上开发完成。一下是本人关于此项目的一些总结。
文章结构:
一.扫码使用到的核心框架
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
框架提供了两个扫码组件:
简单说一下区别:
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包含了非常多的内容,一下是一部分文档中的截图:
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
第一步:
第二步:这里分别在Foreground Layer 和 Backgroung Layer中选择两个svg文件
第三步:
这个项目总结就这么多吧,接下来2.0会增加更多的功能,比如选择本地图片扫码,扫码历史记录,生成二维码等等。目前对Firebase和Admob还不怎么了解,这里也正在写一个demo学习,可能也会写一篇新的文章吧,约束布局也地好好看一下,是真的好用。