这段时间一直在写项目,现在来整理一下这段时间开发中遇到的一些坑吧
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) .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" > <item name ="colorPrimary" > @color/colorPrimary</item > <item name ="colorPrimaryDark" > @color/colorPrimaryDark</item > <item name ="colorAccent" > @color/colorAccent</item > </style > <style name ="AppThemeNoActionBar" parent ="AppTheme" > <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 2 3 4 override fun onCreateOptionsMenu (menu: Menu , inflater: MenuInflater ) { inflater.inflate(R.menu.community_new_menu,menu) super .onCreateOptionsMenu(menu, inflater) }
这里注意一下,这个方法与activity的有点不同,除了这个地方以外其他的都和activity的一样
或许有更简单的方法
首先写一个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 osimport jsonimport pandas as pdfrom keras import backend as Kfrom keras.optimizers import Adamfrom keras.preprocessing.image import ImageDataGeneratorfrom keras.callbacks import EarlyStopping, TensorBoardimport tensorflow as tffrom 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. """ 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)
优化器
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是训练次数,一次训练会把数据全部过一遍
保存模型
一句话,就这么简单
转换模型 使用tensorflow的转换工具来转换
1 tflite_convert --output_file=litemodel.tflite --keras_model_file=model.h5
常见的模型都可以用这个方法来转换,但是部分模型似乎不行,本人也没找到好的方案