人升开发日志#17 | 08/25 接入Firebase、Crashlytics

找到实习后,挺长一段时间没更新了。

《人升》的话,最近接入了Crashlytics,更新了金币系统等等。

回忆一下Crashlytics的接入过程~

为什么选择Crashlytics?

  • 不需要额外权限

    而国内很多崩溃统计向的都有不少权限要求,比如友盟统计必须依赖READ_PHONE_STATE等权限。

  • 国内也能访问

    是的,虽然Crashlytics属于Firebase,但是并没有用到Google的服务器。

  • 配置简单,不需要额外代码

    这一点我也很惊喜,只需要在Gradle文件里进行一些配置。项目代码甚至不需要任何变动。

  • 能直接在Crashlytics后台看到混淆前的堆栈信息

另外除了崩溃分析/管理功能外,其实还能在控制台看到活跃用户统计、用户行为分析、次日留存率、版本情况等等。

查看这些信息感觉很有助于维持更新兴趣~

总体来讲,感觉Crashlytics很适合独立开发者使用。

接入Firebase以及Crashlytics

Crashlytics已经被并入了Firebase体系,所以要先接入Firebase。

Fabric虽然还能直接加入Crashlytics,但是仅提供支持到2020年3月31号,官网也在建议用户迁移至Firebase。

接入Firebase可以参考官方教程

流程可以概述为:

  1. 创建Firebase项目

  2. 注册应用,上传签名的SHA码

    这两步在官网跟着流程走就可以,就不详细说明了。

  3. 下载 google-services.json放到项目的模块目录(一般是app文件夹里)中。

  4. 项目级gradle文件里加入Firebase和Crashlytics配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    buildscript {
    ...

    repositories {
    // 加上这两个仓库地址
    google()
    maven {
    url 'https://maven.fabric.io/public'
    }
    }
    dependencies {
    classpath 'com.google.gms:google-services:4.3.0' // Google 服务
    classpath 'io.fabric.tools:gradle:1.29.0' // Crashlytics 插件
    }
    }
  5. 模块级gradle文件里加入:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    apply plugin: 'com.google.gms.google-services'
    apply plugin: 'io.fabric'

    dependencies {
    ...

    /** firebase **/
    implementation 'com.google.firebase:firebase-core:17.0.0'
    implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'

    }

    到这一步为止,其实已经集成完毕了。

  6. (可选)如果你的应用开启了代码混淆的话,建议在proguard-rules.pro文件里加入以下规则,以便拿到混淆前的堆栈信息:

    1
    2
    3
    4
    5
    6
    7
    # Crashlytics
    -keepattributes *Annotation*
    -keepattributes SourceFile,LineNumberTable
    -keep public class * extends java.lang.Exception
    -keep class com.crashlytics.** { *; }
    -dontwarn com.crashlytics.**

  7. 测试一下崩溃吧

    在相应的地方可以用以下语句制造一个崩溃,然后过几分钟后,前往Firebase的后台查看崩溃信息吧~

    1
    Crashlytics.getInstance().crash() // 制造一个崩溃

更多玩法

更多Crashlytics的用法可以参考官方的页面,比如可以记录try-catch住的异常,自定义一些key-value信息方便分析崩溃等等。

人升开发日志#15 | 06/16 夜间模式

收到多次需要夜间模式的反馈,就研究了一下要怎么实现~

本以为需要手动替换Theme,实际上Android已经提供了相应的功能。

实际使用之后才发现,Fragment能实现到和Activity在使用上分辨不出的效果。

依赖

首先是依赖,需要使用appcompat:(我这里用的androidx库,support库同理)

1
implementation 'androidx.appcompat:appcompat:1.1.0-beta01'
阅读更多

[Android]多语言、Google授权登陆

以此文总结一下《LifeUp》上架 GooglePlay 的全过程~

多语言

App默认会根据系统语言加载不同的字符串资源文件,这是我们实现多语言的基础。

新建资源文件

首先是建立相应的资源文件,可以手动建立,也可以用 Android Studio 的选项建立:

右键 res 文件夹选择File–>New–>Android resource file选择 Locale

可以见到下图

选择你要新建的语言,并且改文件名为strings即可。

实际上,就是新建一个value-(语言缩写)的文件夹(简体中文的话就是value-zh),然后在其中放上string.xml文件。

提取代码、Layout中的字符串

点击相应的字符串,按下ALT+ENTER,然后选择 Extract string resouces

然后输入字符串资源的名称,勾选相应的资源文件:

按下确定后,AS就会提取该字符串到资源文件内了,同时代码会被替换成:

Context getString(resouceId)

在Layout中操作同理,利用这个操作可以替换掉App内大部分的静态的字符串。

那么需要动态赋值的字符串该怎么办呢?

阅读更多

Toolbar减少标题和 Navigation icon 的间距

Toolbar 默认配置下,标题和Navigation icon(比如返回按钮)之间的间隔会迷之过长,

可以通过配置toolbar的属性调整:

1
app:contentInsetStartWithNavigation="0dp"

这个间隔的设计好像是配合没有Navigation icon的情况的,默认值为16dp。

有Navigation icon的情况下应该手动调整。

完整的Toolbar配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">

<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:contentInsetStartWithNavigation="0dp"
app:popupTheme="@style/AppTheme.PopupOverlay" />

</com.google.android.material.appbar.AppBarLayout>

桌面小部件-IntentService-Oreo的那些事

简介

之前在《人升》的桌面小部件,实现ListView中的点击事件监听的方式是:

使用fillInIntent发送广播到Widget类中,并在onReceive方法中拦截,处理业务逻辑。

但是,

Widget的本质是个广播接收器,不适宜在里面处理耗时操作。

(完成团队事项的时候需要发送网络请求,普通事项需要更改数据库,都可以视为是耗时操作。)

所以,我决定改用IntentService处理完成事项的业务逻辑。

IntentService的特点是后台运行、自动销毁、异步运行。

首先尝试直接用fillInIntent启动服务失败了。

然后改成了先发送广播,然后在Widget类中,并在onReceive方法拦截再启动IntentService:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
override fun onReceive(context: Context, intent: Intent) {
super.onReceive(context, intent)

if (...) {
...
} else if (intent.action == FINISH_TASK) {
// 将耗时操作交给IntentService完成
val finishIntent = Intent(context, FinishTaskIntentService::class.java)
if (intent.extras != null) {
finishIntent.putExtras(intent.extras!!)
}

try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(finishIntent)
} else {
context.startService(finishIntent)
}
} catch (e: Exception) {
e.printStackTrace()
ToastUtils.showShortToast("完成事项似乎出现了一些问题,请尝试刷新下。", LifeUpApplication.getLifeUpApplication())
}
}
}

然后就遇到了坑。

阅读更多

实现Snackbar弹出时任意View闪躲

简介

Snackbar 是一个底部弹出消息的控件,类似Toast。

基本使用:

1
2
3
Snackbar.make(view, message_text, duration)
.setAction(action_text, click_listener)
.show();

我们知道,在根布局是CoordinatorLayout,并且设置CoordinatorLayout的behavior之后,可以实现Snackbar弹出的时候,fab(浮动按钮)会自动向上移动防止被遮挡。

阅读更多

人升开发日志#15 | 04/02 用Fragment改造设置页面

之前使用Fragment都是配合ViewPager或者配合TabLayout+ViewPager,基本没用过FragmentManager。

实际使用之后才发现,Fragment能实现到和Activity在使用上分辨不出的效果。

布局文件

其实我们要实现的效果很简单,一个主Fragment显示各个设置的大类(比如显示设置、小部件设置等),点击之后切换到不同的Fragment显示,然后那些Fragment只能回退到主Fragment,主Fragment再回退就是结束Activity。

大概这样:

Fragment的布局文件正常就好。

Activity要怎么样呢?我们的Activity本身是不需要任何内容的,只需要充当一个容器的作用就行:

activity_setting.xml

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/background_color"
android:fitsSystemWindows="true"
tools:context=".activities.SettingActivity">

</LinearLayout>
阅读更多

人升开发日志#14 | 03/21 几个小问题的解决

ViewPager + Fragments 导致的选项菜单错乱解决

原先的Toolbar实现

在Fragment中获取Toolbar这个View,然后调用Activity的setSupportActionBar(toolbar)方法。

然后实现Option菜单的方法:在Fragment中设置setHasOptionsMenu(true),然后重写fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater)方法。

出现的问题:似乎是因为ViewPager的缓存机制,导致切换Fragments切换的时候可能出现菜单错乱的情况。

解决:把Toolbar当做控件使用

MainActivity.kt

1
2
3
4
5
6
7
8
9
10
11
12
13
fun initToolBar(toolbar: Toolbar) {
//setSupportActionBar(toolbar) 注释掉这条

currentToolbar = toolbar
val toggle = ActionBarDrawerToggle(
this, drawer_layout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close)
drawer_layout.addDrawerListener(toggle)
nav_view.setNavigationItemSelectedListener(this)
}

fun getCurrentToolbar(): Toolbar? {
return currentToolbar
}

Fragment.kt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private fun initToolbar(view: View) {
//设置toolbar
mToolbar = view.findViewById(R.id.toolbar)
(activity as MainActivity).initToolBar(mToolbar)

// 填充菜单
mToolbar?.inflateMenu(R.menu.main)
mToolbar?.setOnMenuItemClickListener { item ->
when (item.itemId) {
R.id.action_sort -> {
// do something
true
}
else -> true
}
}
}
阅读更多

人升开发日志#13 | 03/21 迁移至AndroidX

AndroidX是 Google 发布的对原来的支持库整理后的新一代支持库。并且原来的支持库的最后版本将停留在“28.0.0”,迁移是迟早的事情。

主要动机还是想要用一个框架,结果它只支持AndroidX,就这样决定开始迁移了。

利用Android Studio迁移

需求:Android Studio版本3.2以上

操作:Refactor -> Migrate to Androidx

IDE会询问是否要备份一份当前的项目,并且会告知可能需要你手动解决一些项目Error。

继续操作,IDE会搜索所有的要改变的依赖路径,然后点击DO REFACTOR按钮吧。

阅读更多

VectorDrawable(xml)转换回SVG

Android 用的矢量图是 VectorDrawable(xml格式)。

如果我们想对已经转换成 VectorDrawable 的矢量图进行修改的话,

最好先转换回 SVG 格式再使用 inkscape 之类的矢量图图形编辑工具进行修改。

步骤

  1. 将头部的:

    1
    2
    <?xml version="1.0" encoding="utf-8"?>
    <vector xmlns:android="http://schemas.android.com/apk/res/android"

    替换成

    1
    <svg xmlns="http://www.w3.org/2000/svg"

    闭标签也做相应修改。

  2. android:width替换成width

  3. android:height替换成height

  4. android:pathData替换成d

  5. android:fillColor替换成fill

    如果没有android:fillcolor的话,要加上fill="#ffffff"

  6. android:viewportHeight="24" android:viewportWidth="24"替换成viewBox="0 0 24 24"的形式。

例子

Vector Drawable

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">

<path
android:fillColor="#ffffff"
android:pathData="M12,3L2,12h3v8h2.5v-0.8c0-1.5,3-2.2,4.5-2.2s4.5,0.8,4.5,2.2V20H19v-8h3L12,3zM12,15.2c1.2,0-2.2-1-2.2-2.2 s1-2.2,2.2-2.2s2.2,1,2.2,2.2S13.2,15.2,12,15.2z" />
<path android:pathData="M0,0h24v24H0V0z" />
</vector>

SVG

1
2
3
4
5
6
7
8
9
10
<svg xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24" >

<path
fill="#ffffff"
d="M12,3L2,12h3v8h2.5v-0.8c0-1.5,3-2.2,4.5-2.2s4.5,0.8,4.5,2.2V20H19v8h3L12,3zM12,15.2c1.2,0-2.2-1-2.2-2.2 s1-2.2,2.2-2.2s2.2,1,2.2,2.2S13.2,15.2,12,15.2z" />
<path d="M0,0h24v24H0V0z" fill="none"/>
</svg>

参考

https://stackoverflow.com/questions/44948396/convert-vectordrawable-to-svg