这段时间一直在写项目,现在来整理一下这段时间开发中遇到的一些坑吧

Okhttp客户端保持session

很多时候我们使用http客户端都需要保持session,起初我以为okhttp应该会自动保存session,但是在实际使用过程中发现它并不会自动保存session,我们需要使用一个库来解决这个问题

在gradle的依赖中加入如下依赖

1
implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.2.0'

像这样使用它

1
2
3
4
5
6
7
8
9
 val cookieManager = CookieManager()
.apply { setCookiePolicy(CookiePolicy.ACCEPT_ALL) }


val client = OkHttpClient.Builder()
.connectTimeout(10000L, TimeUnit.MILLISECONDS)
.readTimeout(10000L, TimeUnit.MILLISECONDS)
.cookieJar(JavaNetCookieJar(cookieManager))
.build()

这样编写代码就可以获得一个保存session的OkhttpClient了

补充

虽然这样可以保持session,但是在实际使用中依然存在不少问题,例如无法持久化

可以使用下面的方案来解决该问题

先要加入以下源

1
maven { url "https://jitpack.io" }

1
implementation 'com.github.franmontiel:PersistentCookieJar:v1.0.1'

这个库可以自动持久化cookie,只需要一个context参数即可

1
2
3
4
5
6
7
var cookieJar: ClearableCookieJar =
PersistentCookieJar(SetCookieCache(), SharedPrefsCookiePersistor(MyApplication.getContext()))
val client = OkHttpClient.Builder()
.connectTimeout(10000L, TimeUnit.MILLISECONDS)
.readTimeout(10000L, TimeUnit.MILLISECONDS)
.cookieJar(cookieJar) //保持session
.build()

解析json返回格式

1
implementation group: 'com.squareup.retrofit2', name: 'converter-gson', version: '2.4.0'

在创建retrofit的时候使用.addConverterFactory(GsonConverterFactory.create())即可

使状态栏透明

方法很简单,只需在onCreate中加入以下代码

1
2
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
window.statusBarColor = Color.TRANSPARENT

或是在对应的style中加入

1
2
3
4
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowTranslucentNavigation">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>

在style中更改theme属性之后,在Androidmanifest中为activity设置主题即可

其中

  • 第一行代码可以让状态栏透明
  • 第二行让虚拟导航栏透明(有返回 home 和最近任务按钮的那一栏)
  • 第三行是5.0之前让状态栏透明的方法
  • 如果出现显示错误可以尝试添加代码
    1
    <item name="android:fitsSystemWindows">true</item>
  • 或手动更改布局文件来解决

隐藏ActionBar的另一种方法

现在很多方法都是直接将style中的parent改成NoActionBar,但是这样一来会有个问题,你重新创建一个style之后,如果你的主题颜色发生更改,则你必须要更改两个主题的颜色

可以通过继承原style解决这个问题

1
2
3
4
5
6
7
8
9
10
11
<style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="AppThemeNoActionBar" parent="AppTheme">
<!-- Customize your theme here. -->
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>

AppThemeNoActionBar的parent为AppTheme,他的主题颜色与AppTheme的相同,所不同的是这个主题可以隐藏ActionBar

同样的,你也可以用这样的方法创建一个style来实现状态栏透明

实现一个提示内容会自动上移的EditText

效果

要实现这个效果很简单,只需要使用md库内的Text Fields

文档

切换到某个Fragment里才在ActionBar显示菜单按钮

首先你需要在onCreateView里加入以下代码

1
setHasOptionsMenu(true)  // 调用这个才会显示菜单

然后重写方法

1
2
3
4
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.community_new_menu,menu)
super.onCreateOptionsMenu(menu, inflater)
}

这里注意一下,这个方法与activity的有点不同,除了这个地方以外其他的都和activity的一样

使用VierPager和TabLayout快速实现侧滑切换fragment

或许有更简单的方法

首先写一个Adapter,继承androidx.fragment.app.FragmentPagerAdapter并实现抽象方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

class CommunityViewPagerAdapter(private val pageList: List<Fragment>, manager: FragmentManager) :
FragmentPagerAdapter(manager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {

override fun getCount(): Int {
return pageList.size
}

override fun getItem(position: Int): Fragment {
return pageList[position]
}

override fun getPageTitle(position: Int): CharSequence? {
// 自己写吧
}

}

只需要创建一个Adapter实例并使用setupWithViewPager即可

1
2
3

fragment_community_viewPager.adapter = CommunityViewPagerAdapter(fragment, activity!!.supportFragmentManager)
fragment_community_tabLayout.setupWithViewPager(fragment_community_viewPager)

其中fragment_community_viewPager是用于显示fragment的ViewPager对象,fragment是一个存储Fragment实例的类

activity!!.supportFragmentManager是一个supportFragmentManager对象,这里是在fragment内使用,使用时需要灵活替换

一个好用的http客户端-Retrofit

不解释了,翻文档吧

贴个官方示例给大家看看这库有多好用

1
2
3
4
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}

之后你就可以发送http请求了

1
2
3
4
5
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();

GitHubService service = retrofit.create(GitHubService.class);

如果返回内容是json格式,你甚至可以直接让库帮你解析为对象,只需要在Retrofit.Builder()时加入以下代码

1
.addConverterFactory(GsonConverterFactory.create()) 

不过你需要一个依赖

1
2
 implementation group: 'com.squareup.retrofit2', name: 'converter-gson', version: '2.4.0'
implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.2.0'

快速将tensorflow lite导入到你的项目中

参考

获取相机图像信息

参考文章的方法大致还是没有什么问题的,只是使用camerax库的时候使用的是旧版,新版的创建Analysis方法

1
2
3
def camerax_version = '1.0.0-alpha06'
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
1
2
3
4
val executor = Executors.newSingleThreadExecutor()

val imageAnalysis = ImageAnalysis(analyzerConfig)
imageAnalysis.setAnalyzer(executor, TFImageAnalyzer(uiHandler))

不过说不定哪一天可能又改了呢

导入并运行模型

可以使用最新的稳定版tflite

1
implementation group: 'org.tensorflow', name: 'tensorflow-lite', version: '2.1.0'

在构建Interpreter的时候有细微不同

1
2
3
val options = Interpreter.Options()
options.setUseNNAPI(true)
val interpreter = Interpreter(model, options)

快速使用深度学习实现移动端图片分类

keras内置了很多常用的模型,你可以使用这些模型快速构建你的应用

链接

要训练自己的模型也很简单

所需依赖

1
2
3
4
5
6
7
8
9
import os
import json
import pandas as pd
from keras import backend as K
from keras.optimizers import Adam
from keras.preprocessing.image import ImageDataGenerator
from keras.callbacks import EarlyStopping, TensorBoard
import tensorflow as tf
from keras.applications.mobilenet_v2 import MobileNetV2

加载数据

以下是一个快速加载数据的代码段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
def generate(batch, shape, ptrain, pval):
"""Data generation and augmentation

# Arguments
batch: Integer, batch size.
size: Integer, image size.
ptrain: train dir.
pval: eval dir.

# Returns
train_generator: train set generator
validation_generator: validation set generator
count1: Integer, number of train set.
count2: Integer, number of test set.
"""

# Using the data Augmentation in traning data
datagen1 = ImageDataGenerator(
rescale=1. / 255,
shear_range=0.2,
zoom_range=0.2,
rotation_range=40,
width_shift_range=0.2,
height_shift_range=0.2,
horizontal_flip=True)

datagen2 = ImageDataGenerator(rescale=1. / 255)

train_generator = datagen1.flow_from_directory(
ptrain,
target_size=shape,
batch_size=batch,
class_mode='categorical')

validation_generator = datagen2.flow_from_directory(
pval,
target_size=shape,
batch_size=batch,
class_mode='categorical')

count1 = 0
for root, dirs, files in os.walk(ptrain):
for each in files:
count1 += 1

count2 = 0
for root, dirs, files in os.walk(pval):
for each in files:
count2 += 1

return train_generator, validation_generator, count1, count2

这段代码可以让你从文件夹加载图片并自动生成经过旋转,缩放的图片

数据集的文件夹格式应该是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-train
分类1
1.jpg
2.jpg
分类2
1.jpg
.
.
.
-validation
分类1
1.jpg
2.jpg
分类2
1.jpg
2.jpg
.
.

构建模型

模型就使用mobilenetv2

1
model = MobileNetV2(input_shape=shape, , classes=n_class)

n_class为分类的类别数量,weights=None可以让你从头开始训练你自己的模型,shape一般为(224,224,3),三个参数分别是图片长,宽,通道数(如果你的图片是黑白的就填入1)

优化器

1
opt = Adam(lr=0.001)

lr为学习速度,注意并不是越快越好,0.001即可

提前结束训练和可视化

1
2
earlystop = EarlyStopping(monitor='val_acc', patience=5, verbose=0, mode='auto')
board = TensorBoard()

编译模型

1
model.compile(loss='categorical_crossentropy', optimizer=opt, metrics=['accuracy'])

加载数据

1
train_generator, validation_generator, count1, count2 = generate(batch, shape[:2], train_dir,eval_dir)

batch是批次尺寸,具体请看链接

在获取了数据生成器之后我们还需要获取label列表,使用下面的函数来获取

1
2
3
4
5
6
7
def save_labels(generator):
labels = generator.class_indices.keys()
with open("save/labels.txt", "w", encoding="utf-8") as file:
lines = [line + '\n' for line in labels]
file.writelines(lines)
print("dump labels success")

将刚刚获取的生成器传入即可

训练模型

使用下面的代码来打印模型信息并开始训练

1
2
3
4
5
6
7
8
model.summary()
hist = model.fit_generator(
train_generator,
validation_data=validation_generator,
steps_per_epoch=count1 // batch,
validation_steps=count2 // batch,
epochs=epochs,
callbacks=[earlystop, board])

epochs是训练次数,一次训练会把数据全部过一遍

保存模型

一句话,就这么简单

1
model.save("model.h5")

转换模型

使用tensorflow的转换工具来转换

1
tflite_convert --output_file=litemodel.tflite --keras_model_file=model.h5

常见的模型都可以用这个方法来转换,但是部分模型似乎不行,本人也没找到好的方案