(1)配置adb环境变量:
①配置adb环境变量:正规方法
②配置adb环境变量:更简单方法-将SDK中对应的路径目录下的adb.exe,AdbWinApi.dll, ③AdbWinUsbApi.dll复制到C:\Users\Administrator下
(2)配置adb在高级设置中,在cmd窗口中和AS工具中Terminal直接使用adb命令
(3)学习链接: 一份超全超详细的 ADB 用法大全
(4)Android 进阶第一篇——善用工具
Android version history维基百科
Android Studio常用快捷键
(1)下载某个版本对应的源码
(2)指定版本compileSdkVersion
android {
compileSdkVersion 26
}
Android之Build类.(Android获取手机配置信息 )
android.os.Build 常用常量
Android必知必会-App 常用图标尺寸规范汇总
(1)包括:USB调试;不锁定屏幕、不使用锁屏;显示布局边界;调试GPU过度绘制;GPU呈现模式分析;不保留活动;启用严格模式,应用在主线程执行长时间操作闪烁屏幕;
(2)学习链接:Android调试系列之开发者选项常用功能
public void rgba() throws Exception {
System.out.println("透明度 | 十六进制");
System.out.println("---- | ----");
for (double i = 1; i >= 0; i -= 0.01) {
i = Math.round(i * 100) / 100.0d;
int alpha = (int) Math.round(i * 255);
String hex = Integer.toHexString(alpha).toUpperCase();
if (hex.length() == 1) hex = "0" + hex;
int percent = (int) (i * 100);
System.out.println(String.format("%d%% | %s", percent, hex));
}
}
// 透明度 | 十六进制
100% | FF 89% | E3 79% | C9 69% | B0 59% | 96
99% | FC 88% | E0 78% | C7 68% | AD 58% | 94
98% | FA 87% | DE 77% | C4 67% | AB 57% | 91
97% | F7 86% | DB 76% | C2 66% | A8 56% | 8F
96% | F5 85% | D9 75% | BF 65% | A6 55% | 8C
95% | F2 84% | D6 74% | BD 64% | A3 54% | 8A
94% | F0 83% | D4 73% | BA 63% | A1 53% | 87
93% | ED 82% | D1 72% | B8 62% | 9E 52% | 85
92% | EB 81% | CF 71% | B5 61% | 9C 51% | 82
91% | E8 80% | CC 70% | B3 60% | 99 50% | 80
90% | E6
49% | 7D 39% | 63 28% | 4A 19% | 30 9% | 17
48% | 7A 38% | 61 28% | 47 18% | 2E 8% | 14
47% | 78 37% | 5E 27% | 45 17% | 2B 7% | 12
46% | 75 36% | 5C 26% | 42 16% | 29 6% | 0F
45% | 73 35% | 59 25% | 40 15% | 26 5% | 0D
44% | 70 34% | 57 24% | 3D 14% | 24 4% | 0A
43% | 6E 33% | 54 23% | 3B 13% | 21 3% | 08
42% | 6B 32% | 52 22% | 38 12% | 1F 2% | 05
41% | 69 31% | 4F 21% | 36 11% | 1C 1% | 03
40% | 66 30% | 4D 20% | 33 10% | 1A 0% | 00
透明度百分比和十六进制对应关系计算方法
安卓开发插件推荐
Android强制弹出,隐藏输入法
Android Activity切换动画常用实现方式
Android四大组件——Activity跳转动画、淡出淡入、滑出滑入、自定义退出进入
完美解决系列—解决包重复
JDK安装与环境变量配置
Android 防止多次重复点击的三种方法
Android:一张图片占用多少内存
(1)break:break用于完全结束一个循环,跳出循环体执行循环后面的语句。
(2)continue:continue是跳过当次循环中剩下的语句,执行下一次循环。
(3)return:结束本方法的执行,返回。
(4)参考链接:
循环结构中break、continue、return和exit的区别
for循环的简介及break和continue的区别
(1)compileSdkVersion:告诉Gradle用哪个Android SDK版本编译你的应用,需要强调的是修改 compileSdkVersion 不会改变运行时的行为。当你修改了compileSdkVersion 的时候,可能会出现新的编译警告、编译错误,但新的compileSdkVersion不会被包含到APK 中:它纯粹只是在编译的时候使用。强烈推荐总是使用最新的 SDK 进行编译。
(2)minSdkVersion:则是应用可以运行的最低要求。
(3)targetSdkVersion:是Android提供向前兼容的主要依据,在应用的targetSdkVersion没有更新之前系统不会应用最新的行为变化。例如targetSdkVersion对应5.0版本,不会出现6.0的特性。
(4)例子(车载电视-不适用动态权限,把targetSdkVersion调至22)
ext {
compileSdkVersion = 25
buildToolsVersion = "25.0.0"
minSdkVersion = 14
targetSdkVersion = 22
}
(5)学习链接
manifest-----uses-sdk:各Android平台版本支持的API级别,包括各版本亮点
(1)抓包方法
csdn博客-如何使用Fiddler对Android应用进行抓包
Android平台HTTPS抓包全方案
(2)Fiddler显示IP
Fiddler显示IP的设置方法
(3)手机上怎么安装Fiddler证书
①打开浏览器,输入你电脑的ip+端口号,例如:http://192.168.1.103:8080/
②设置–>安全
③设置–>安全–>信任的凭证–>用户,是否有Fiddler证书。
④安装步骤,清除所有凭证—>从存储设备安装–>信任的凭证查询。
(4)过滤域名配置
(5)只抓某个域名
(6)问题解决
Fiddler实现手机抓包- 解决”creation of the root certificate was not successful
[Fiddler]No root certificate was found问题解决方案
关于安卓7.0及以上的Https请求抓包问题android:networkSecurityConfig
关于android:networkSecurityConfig的添加
(1)脑图XMind 8免费激活方法
脑图XMind 8免费激活方法
(2)Android apk反编译查看源码、反编译资源文件
Android apk反编译查看源码、反编译资源文件
Android 监听apk安装替换卸载广播
Java 正则表达式
求一个java中正则表达式,匹配所有标点符号,但除去‘-’和‘_’的
(1)效果
(2)学习链接
Android UI控件Switch的使用方法
Android View 仿iOS SwitchButton [Material Design]
TinyPng官网
TinyPng——接口扩展程序(我所用的最好的图片压缩)
教你一分钟实现动态模糊效果
./是当前目录,../是父级目录,/是根目录
位运算(&、|、^、~、>>、<<)
Android应用跳转手机QQ方式(聊天和加群)
Android tools属性引用
Android随笔之——PackageManager详解
Android客户端和服务端如何使用Token和Session
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
}
(1)Gradle之将Android项目开源到JCenter
Gradle之将Android项目开源到JCenter
(2)打包成aar并上传到Nexus搭建的maven仓库
Android 中打包成aar并上传到Nexus搭建的maven仓库
将library以及它依赖的module一起打包成一个完整aar
// transitive的值为true,则远程依赖项的依赖项也将嵌入到最终的aar中。默认值为false
configurations.embed.transitive = true
dependencies {
embed fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
annotationProcessor "androidx.lifecycle:lifecycle-compiler:2.0.0"
// 只编译,不嵌入最终aar包
// 不建议使用implementation,因为该依赖可能与application的依赖版本不一致,使用implementation可能会导致R类找不到的问题
compileOnly (name:'GDTSDK.unionNormal.4.150.1020',ext:'aar')
compileOnly (name:'Baidu_MobAds_SDK-release',ext:'aar')
// 只有用embed(name:'aa1', ext:'aar')才能打包进去
// embed files("libs\aa1.aar")和embed fileTree(dir: 'libs', include: '*'),这些通用的语法都不报错,但打包不进去
embed (name:'android-gif-drawable-1.2.6',ext:'aar')
embed (name:'open_ad_sdk',ext:'aar')
embed (name:'torch-adcore-5.14.3146',ext:'aar')
api 'io.reactivex.rxjava2:rxandroid:2.1.0'
api 'io.reactivex.rxjava2:rxjava:2.2.6'
api 'com.github.bumptech.glide:glide:3.7.0'
}
Android网页WebView图片文件上传的问题
(1)查看App下的Mapping.txt
…/app/build/outputs/mapping/normal下找到Mapping.txt
Android小技巧—查看代码混淆后的日志
(2)查看sdk下的proguardMapping.txt
(1)问题
(2)解决
①点击Help—>Edit Custom VM Options
②然后在打开的文件中添加一句
-Dfile.encoding=UTF-8
③然后重启Android Studio
(3)参考链接
Android Studio升级3.6 Build窗口出现中文乱码问题解决
解决android studio 3.6 中文乱码的问题
(1)案例
private NestedScrollView mScrollView;
private Rect mScrollBounds = new Rect();
private AdNativeExpressActivityHolder mAdActivityHolder2 = null;
// 滑动监听
private NestedScrollView.OnScrollChangeListener mOnScrollChangeListener = (nestedScrollView, scrollX, scrollY, oldScrollX, oldScrollY) -> {
// 可以先判断ScrollView中的mViewAdDivider2是不是在屏幕中可见
mScrollView.getHitRect(mScrollBounds);
if (mAdActivityHolder2 == null && mViewAdDivider2.getLocalVisibleRect(mScrollBounds)) {
initAd();
}
};
(2)学习链接
Android View的可见性检查方法
彻底解决INSTALL_FAILED_UPDATE_INCOMPATIBLE的安装错误
android.view.WindowManager$BadTokenException崩溃的4种情形
android.view.WindowLeaked: MainActivity has leaked window
com.android.internal.policy.PhoneWindow$DecorView that was originally added here
at android.view.ViewRootImpl.(ViewRootImpl.java:464)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:306)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:90)
at android.app.Dialog.show(Dialog.java:325)
(1)原因:Dialog没有销毁前,它依附的Activity却销毁了,导致窗体泄露。
(2)解决方法:在Dialog调用show()时,判断当前依附的Activity是否已经销毁,在没有销毁时,才调用show()方法。
(1)AndroidStudio新版本这个按钮叫ToggleView,可以根据找到详细报错信息
(2)看一下Error:Execution failed for task ‘:app:processDebugManifest’.> Manifest merger failed with multiple errors, see logs这句话的上面显示的error日志,按照日志分析去找答案。
(3)参考链接
Manifest merger failed with multiple errors, see ?
(1)根据找到详细报错信息分析是清单文件有错
(2)问题分析:在715行和659行重复定义了FileProvider
(3)问题解决:将resource的内容都配置到一个文件中:file_paths.xml,删除一个fileProvider
(1)截图
(2)原因/解决方法:在清单文件里面配置重复了,删掉一个,再同步工程。
(1)问题的截图,如何查看具体的错误报告
(2)查看具体的错误报告,找到问题关键所在
Error:Execution failed for task ':app:processInternalDebugManifest'.
> Manifest merger failed : uses-sdk:minSdkVersion 11 cannot be smaller than version 14 declared in library [fm.jiecao:jiecaovideoplayer:4.5_preview1] E:\workspace\XWorld\app\build\intermediates\exploded-aar\fm.jiecao\jiecaovideoplayer\4.5_preview1\AndroidManifest.xml
Suggestion: use tools:overrideLibrary="fm.jiecao.jcvideoplayer_lib" to force usage
(3)原因:引入的第三方库最低支持版本高于我的项目的最低支持版本,异常中的信息显示:我的项目的最低支持版本为14,而第三方库的最低支持版本为15,所以抛出了这个异常。
(4)解决方法
①将二者改成同一个就没有问题
②在AndroidManifest.xml文件中标签中添加:uses-sdk tools:overrideLibrary=“xxx.xxx.xxx”,为了项目中的AndroidManifest.xml和第三方库的AndroidManifest.xml合并时可以忽略最低版本限制。
// 其中的xxx.xxx.xxx为第三方库包名
// 如果存在多个库有此异常,则用逗号分割它们,例如:
// 举例:
(5)学习链接
Manifest merger failed with multiple errors, see logs问题处理思路
如何解决Suggestion: use tools:overrideLibrary="" to force usage
(6)小总结:遇到问题,关键是思路,如何排查问题的关键所在,针对关键问题找到解决方法。
引用两个jar包中重复类冲突了;
(1)如果jar包是混淆过后,报错Program type already present: com.example.a.a.a.a.a,是因为有多个类被混淆成com.example.a.a.a.a.a,需要将jar包删除一个,或者修改jar包中的类名,或者查看jar包是否正确免混淆了这个类;
(2)二进制文件依赖项包含一个库,该库也作为直接依赖项包含在您的应用中。例如,您的应用声明直接依赖于库A和库B,但库A已在其二进制文件中包含库B。要解决此问题,请取消将库B作为直接依赖项。
com.bytedance.a.a -> a.a.a.a:
void b(java.lang.String,java.lang.String,java.lang.Throwable) -> a
void a(java.lang.String,java.lang.String,java.lang.Throwable) -> b
修复重复类错误
这两个jar包冲突,报错:Program type already present: com.tencent.a.a.a.a.a,应如何解决?
(1)问题描述:take photo -> 拍照 -> 确定 -> 截图 -> 保存,此时返回给onActivityResult的resultCode是0,截图无效。我查看图片储存的情况,拍完照是有存储好的,但截图后没有存储,图片变成了0byte。
(2)原因:裁减时打开图片的原路径uri与输出图片保存路径uri相同,产生冲突,导致裁减完成后图片的大小变成0Byte。
private void cropImageUri(Uri imageUri){
Intent intent = new Intent("com.android.camera.action.CROP");
// 裁减时打开图片的原路径uri
intent.setDataAndType(imageUri, "image/*");
// crop=true 有这句才能出来最后的裁剪页面.
intent.putExtra("crop", "true");
// 这两项为裁剪框的比例
intent.putExtra("aspectX", aspectX);
intent.putExtra("aspectY", aspectY);
// 图片输出大小
intent.putExtra("outputX", outputX);
intent.putExtra("outputY", outputY);
// 裁剪时是否保留图片的比例,这里的比例是1:1
intent.putExtra("scale", true);
// 输出图片保存路径的uri
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
intent.putExtra("return-data", false);
// 返回图片格式
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
// 如果小于要求输出大小,就放大
intent.putExtra("noFaceDetection", true);
// 如果小于要求输出大小,就放大
intent.putExtra("scaleUpIfNeeded", true);
startActivityForResult(intent, requestCode);
}
(3)解决问题:i图片的原路径和输出图片保存路径必须是不同的(可以修改图片的命名)
intent.setDataAndType(imageUri, “image/*”); // imageUri图片的原路径
intent.putExtra(“output”, saveUri); //saveUri输出图片保存路径
(4)学习链接
拍照-裁剪,resultCode返回0
解决android有些机型截图返回 resultCode = 0的问题
(1)截图情况
(2)原因分析
A:一个类如果显式的定义了带参构造函数,那么默认无参构造函数自动失效;
B:一个类只要有父类,那么在它实例化的时候,一定是从顶级的父类开始创建。当我们用子类的无参构造函数创建子类对象时,会去先递归调用父类的无参构造方法,这时候如果某个类的父类没有无参构造方法就会出错啦。
(3)参考链接
there is no default constructor available in …
(1)Android studio 在打包时报错如下:
Lint found fatal errors while assembling a release target.
To proceed, either fix the issues identified by lint, or modify your build script as follows:
...
android {
lintOptions {
checkReleaseBuilds false
// Or, if you prefer, you can continue to check for errors in release builds, but continue the build even when errors are found:
abortOnError false
}
}
(2)解决方法
// Android Studio里,每个model下都有一个build.gradle。我们需要把如下代码添加到各个model下的build.gradle
android {
compileSdkVersion 24
buildToolsVersion '26.0.2'
lintOptions {
abortOnError false
}
}
(1)问题描述
Could not find com.android.tools.build:aapt2:3.2.0-alpha14-4748712.
Searched in the following locations:
file:/D:/android_sdk/extras/m2repository/com/android/tools/build/aapt2/3.2.0-alpha14-4748712/aapt2-3.2.0-alpha14-4748712.pom
file:/D:/android_sdk/extras/m2repository/com/android/tools/build/aapt2/3.2.0-alpha14-4748712/aapt2-3.2.0-alpha14-4748712-windows.jar
file:/D:/android_sdk/extras/google/m2repository/com/android/tools/build/aapt2/3.2.0-alpha14-4748712/aapt2-3.2.0-alpha14-4748712.pom
file:/D:/android_sdk/extras/google/m2repository/com/android/tools/build/aapt2/3.2.0-alpha14-4748712/aapt2-3.2.0-alpha14-4748712-windows.jar
(2)在最上级的builde.gralde增加谷歌库 解决问题
allprojects {
repositories {
google() // 新增的
jcenter()
}
}
引用了一些第三方库及资源,报了一个错误:found an invalid color,发现是.9图片造成的,找了下项目的.9图片,发现有一张并未绘制黑边,然后随便加了一点就编译就通过了。
参考链接:解决android studio "found an invalid color"的问题
关于AS用点9图时遇到的错误的解决方法Error:Execution failed for task ‘:app:mergeDebugResources’
(1)问题分析:启动 Android Studio,不停的Indexing
(2)解决方案
File > Invalidate Caches/Restart
(1)问题分析:AndroidStudio 编译时出现如下问题 SSL peer shut down incorrectly 或者某些jar包下载不下来,一般是因为墙的原因导致的。这时候我们就需要配置镜像来解决这个问题。
(2)解决方案:(为了提高jar包的下载速度也可以配置)配置的方法就是在根build.gradle中添加镜像仓库,一般我们选择阿里的http://maven.aliyun.com/nexus/content/groups/public/。注意要将jcenter放到最后一个,因为他就是那个下载慢,或者报错的罪魁祸首,
buildscript {
repositories {
google()
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
}
}
allprojects {
repositories {
google()
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
(3)参考链接
Android Studio SSL peer shut down incorrectly 问题
(1)错误信息
FAILURE: Build failed with an exception.
* Where:
Build file 'F:\git\i***\build.gradle' line: 1
* What went wrong:
A problem occurred evaluating project ':**'.
> ASCII
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
(2)原因分析
依赖的Gradle插件内部含有中文,因此报错,所以在开发Gradle插件时不可以使用中文注释。
(1)问题报错信息如下:
(2)描述:通过clean,ReBulid无效,重新拉取代码重新运行无效,升级了AS也无效等等。
(3)解决方案1:最后找到了一篇文章error: failed linking references,重点如下截图,解决了此问题。通过clean、ReBulid,重新拉取代码重新运行,升级了AS都无效的原因应该是都使用了缓存的依赖,无法生成新的R文件,需要将.gradle的caches删除,ReBulid后才能生成新R文件。
(1)问题报错信息如下:
(2)解决方案1
(2)解决方案2
(3)解决方案3:将项目根部build以及主module下的build2个文件夹删除,再进行Invalidate Caches/Restart操作,重启AS,再次编译Project。
(4)解决方案4
如果是接入sdk出错,可以先git stash恢复代码再运行项目;运行正常后再git stash pop将sdk弹出,继续运行,通常是可以通过的。
(5)解决方案5
参考### 2.15(3)的将.gradle的caches删除。
(1)出错内容
启动android studio调试工程出现“default activity not found”的报错。
(2)解决办法1
AndroidManifest.xml文件中没有声明相应Activity,所以最好直接用AS快速创建Activity。
(3)解决办法2
1、关闭as
2、找到androidstudio的安装路径(C:\Users\xxx\.AndroidStudio3.2)
3、进入AndroidStudio3.2\system\caches
4、删除里面文件
5、重启as
(4)解决办法3
(5)学习链接
android studio 3.2 default activity not found解决办法
(1)出错截图
(2)分析原因:build.gradle(project:-----)
allprojects {
repositories {
jcenter()
maven {
url "https://maven.google.com"
}
google()
// ........
}
}
(3)修复问题:将maven-url置于前面,google() 居中,再到 jcenter()
allprojects {
repositories {
maven {
url "https://maven.google.com"
}
google()
jcenter()
// ........
}
}
(1)错误信息
# main(1)
java.lang.IllegalStateException
The application's PagerAdapter changed the adapter's contents without calling PagerAdapter#notifyDataSetChanged! Expected adapter item count: 1, found: 2147483647 Pager id: com..book:id/viewpager_banner Pager class: class com..SmoothScrollViewPager Problematic adapter: class com.AdBannerView$a
(2)报错的代码
public void showBanner(List bannerList) {
mBannerList = bannerList;
if (mBannerList != null && mBannerList.size() > 0) {
int size = mBannerList.size();
mViewPagerBanner.onShow();
} else {
mViewPagerBanner.setAdapter(null);
}
}
/**
* 通知BannerPagerAdapter刷新
*/
public void notifyDataSetChanged() {
if (mBannerPagerAdapter != null) {
mBannerPagerAdapter.notifyDataSetChanged();
}
}
(3)修改后的代码
public void showBanner(List bannerList) {
if (bannerList != null && bannerList.size() > 0) {
if (mBannerList == null) {
mBannerList = new ArrayList<>();
}
mBannerList.clear();
mBannerList.addAll(bannerList);
} else {
mViewPagerBanner.setAdapter(null);
}
}
/**
* 通知BannerPagerAdapter刷新
*
* @param bannerList
*/
public void notifyDataSetChanged(List bannerList) {
if (bannerList != null && bannerList.size() > 0 && mBannerPagerAdapter != null) {
if (mBannerList == null) {
mBannerList = new ArrayList<>();
}
mBannerList.clear();
mBannerList.addAll(bannerList);
mBannerPagerAdapter.notifyDataSetChanged();
}
}
(4)原因分析
因为mBannerList强引用持有,当mBannerList的数量改变时,没有及时调用notifyDataSetChanged()报错。但是我需要使用mBannerList内部的对象强引用,所以可以通过mBannerList.addAll(bannerList),内部的对象仍然是强引用。
# main(1)
java.lang.IndexOutOfBoundsException
Inconsistency detected. Invalid item position 8(offset:8).state:9
com.chuangyue.baselib.widget.recyclerview.VerticalRecyclerView{d2e3002 VFED..... .F...... 0,0-720,1136}, adapter:com.novel..me.a.d.a@30ab3a2,layout:com.baselib.widget.recyclerview.VerticalRecyclerView$1@f960833,
context:com.novel..me.ui.activity.RequestResourceActivity@3a5ff45
1 android.support.v7.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5923)
2 android.support.v7.widget.GapWorker.prefetchPositionWithDeadline(GapWorker.java:286)
3 android.support.v7.widget.GapWorker.flushTaskWithDeadline(GapWorker.java:343)
4 android.support.v7.widget.GapWorker.flushTasksWithDeadline(GapWorker.java:359)
5 android.support.v7.widget.GapWorker.prefetch(GapWorker.java:366)
6 android.support.v7.widget.GapWorker.run(GapWorker.java:397)
7 android.os.Handler.handleCallback(Handler.java:815)
8 android.os.Handler.dispatchMessage(Handler.java:104)
9 android.os.Looper.loop(Looper.java:207)
10 android.app.ActivityThread.main(ActivityThread.java:5856)
11 java.lang.reflect.Method.invoke(Native Method)
12 com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1026)
13 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:887)
# main(2)
java.lang.IndexOutOfBoundsException
Inconsistency detected. Invalid item position 120(offset:120).state:121 com.chuangyue.baselib.widget.recyclerview.VerticalRecyclerView{ac3f757 VFED..... .F...... 0,0-840,1704},
layout:com..widget.recyclerview.VerticalRecyclerView$1@414df2d,
1 android.support.v7.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5923)
2 android.support.v7.widget.GapWorker.prefetchPositionWithDeadline(GapWorker.java:286)
3 android.support.v7.widget.GapWorker.flushTaskWithDeadline(GapWorker.java:343)
4 android.support.v7.widget.GapWorker.flushTasksWithDeadline(GapWorker.java:359)
5 android.support.v7.widget.GapWorker.prefetch(GapWorker.java:366)
6 android.support.v7.widget.GapWorker.run(GapWorker.java:397)
7 android.os.Handler.handleCallback(Handler.java:790)
8 android.os.Handler.dispatchMessage(Handler.java:99)
9 android.os.Looper.loop(Looper.java:197)
10 android.app.ActivityThread.main(ActivityThread.java:7022)
11 java.lang.reflect.Method.invoke(Native Method)
12 com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:515)
13 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:837)
在滑动过程中,删除或者插入数据,没及时通知RecyclerView更新缓存,mAdapter.getItemCount()获取当前数据数量<缓存的offsetPosition位置,就会抛异常。
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
+ "position " + position + "(offset:" + offsetPosition + ")."
+ "state:" + mState.getItemCount() + exceptionLabel());
}
ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
if (position < 0 || position >= mState.getItemCount()) {
throw new IndexOutOfBoundsException("Invalid item position " + position
+ "(" + position + "). Item count:" + mState.getItemCount() + exceptionLabel());
}
}
(1)禁止在滑动时删除、插入数据,尤其注意上拉刷新时removeFootView(这是X一年多RecyclerView没有根治此异常的原因)
private RefreshLayoutFooter mFooter;
private ViewGroup.LayoutParams mFooterParams;
private int mFooterHeight;
优化前:
/**
* 设置是否允许上拉加载更多
*
* @param removeView 是否remove FooterView
*/
private void setLoadMoreEnable(boolean enable, boolean removeView) {
mIsEnableLoadMore = enable;
if (!mIsEnableLoadMore) {
if (mIsAddedFooter && removeView && mRecyclerView != null) {
mRecyclerView.removeFootView(mFooter);
mIsAddedFooter = false;
}
} else {
if (!mIsAddedFooter && mRecyclerView != null) {
mRecyclerView.addFootView(mFooter);
mIsAddedFooter = true;
}
}
}
优化后:
/**
* 设置是否允许上拉加载更多
*
* @param removeView 是否remove FooterView
*/
private void setLoadMoreEnable(boolean enable, boolean removeView) {
mIsEnableLoadMore = enable;
if (!mIsEnableLoadMore) {
if (mIsAddedFooter && removeView && mRecyclerView != null) {
// removeFootView()会引起IndexOutOfBoundsException异常
mFooterParams.height = 0;
mFooter.setLayoutParams(mFooterParams);
mIsAddedFooter = false;
}
} else {
if (!mIsAddedFooter && mRecyclerView != null) {
mFooterParams.height = mFooterHeight;
mFooter.setLayoutParams(mFooterParams);
mIsAddedFooter = true;
}
}
}
(2)数据源,删除,增加时,notify及时同步
推荐1:
public void addItems(List items, boolean isClearData) {
if (mRequestItems == null) {
mRequestItems = new ArrayList<>();
}
if (isClearData) {
mRequestItems.clear();
notifyDataSetChanged();
}
mRequestItems.addAll(items);
notifyDataSetChanged();
}
推荐2:
public void setItems(ArrayList newArticles) {
// 获取刷新前数据长度
int oldSize = articles.size();
// 删除当前所有数据
articles.clear();
// 告诉回收器,所有的旧数据都不见了
adapter.notifyItemRangeRemoved(0, oldSize);
// 添加新数据
articles.addAll(newArticles);
// 告诉回收器,有多少新数据要插入
adapter.notifyItemRangeInserted(0, newArticles.size());
}
(3)刷新数据时禁止滑动,例如:弹出对话框禁止滑动、设置不可触摸、在数据刷新完毕后才操作置顶
①设置不可触摸:
private boolean mIsRefreshing=false;
mRecyclerView.setOnTouchListener(
new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (mIsRefreshing) {
return true;
} else {
return false;
}
}
}
);
// 当刷新时设置
// mIsRefreshing=true;
// 刷新完毕后还原为false
// mIsRefreshing=false;
②在数据刷新完毕后才操作置顶:
// 在拉取新数据前置顶,报错
private void getTopList(boolean isLoadMore) {
if (!isLoadMore && mRefreshLayout != null) {
mRefreshLayout.scrollToPosition(0);
}
if (getPresenter() != null) {
getPresenter().getTopList(mCurTopListId, isLoadMore);
}
}
// 在拉取新数据后,更新列表后,再操作置顶
@Override
public void updateAdapter(List datas, boolean isClearData) {
if (getPresenter() != null && mAdapter != null) {
mAdapter.addItems(getPresenter().setItems(datas, isClearData), isClearData);
mAdapter.notifyDataSetChanged();
// 处理切换左侧子榜单时列表未滑动到顶部的bug
if (isClearData && mRefreshLayout != null) {
VerticalRecyclerView recyclerView = mRefreshLayout.getRecyclerView();
if (recyclerView != null && recyclerView.canScrollVertically(-1)) {
recyclerView.scrollToPosition(0);
}
}
}
}
(4)继承LinearLayoutManager,重写onLayoutChildren并try-catch异常
public class WrapContentLinearLayoutManager extends LinearLayoutManager {
public WrapContentLinearLayoutManager(Context context) {
super(context);
}
public WrapContentLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
public WrapContentLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
super.onLayoutChildren(recycler, state);
} catch (IndexOutOfBoundsException e) {
e.printStackTrace();
}
}
}
mRecyclerView.setLayoutManager(new WrapContentLinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
RecyclerView系列(4)—XRexyclerView的坑,java.lang.IndexOutOfBoundsException: Inconsistency detected
RecyclerView和java.lang.IndexOutOfBoundsException:检测到不一致。三星设备中无效的视图支架适配器positionViewHolder
Android 解决java.lang.IndexOutOfBoundsException: Inconsistency detected错误
private static final int MAX_INPUT_COUNT = 14;
editText.setFilters(new EditTextInputFilter[]{new EditTextInputFilter.LengthFilter(MAX_INPUT_COUNT)});
(2)第一步解决:当字符串的长度超过了MAX_INPUT_COUNT,截取0—MAX_INPUT_COUNT
if (!TextUtils.isEmpty(content)) {
if (content.length() > MAX_INPUT_COUNT) {
content = content.substring(0, MAX_INPUT_COUNT);
}
mEtContent.setText(content);
mEtContent.setSelection(content.length());
}
(3)结果:当输入content = “ddddd若干单价金额绝对金”,还是出现此异常。备注:自定义的EditTextInputFilter限制字母长度为1汉字为2计算。
(4)原因2:content.length()==14,但是Selection的位置经过EditTextInputFilter过滤后按照字母长度为1汉字为2计算,结果是:5 + 9*2 = 23。
(5)第二步解决:
private void dealEditSelection(String content) {
if (!TextUtils.isEmpty(content)) {
StringBuilder subBuilder = new StringBuilder();
int destLen = 0;
for (int i = 0; i < content.length(); i++) {
String subStr = content.substring(i, i + 1);
destLen += subStr.getBytes().length > 1 ? 2 : 1;
if (destLen <= MAX_INPUT_COUNT) {
subBuilder.append(subStr);
} else {
break;
}
}
String destContent = subBuilder.toString();
mEtContent.setText(destContent);
mEtContent.setSelection(destContent.length());
}
}
(6)参考链接
记一次EditText设置默认选中setSelection的一个bug
(1)前言为保证用户数据和设备的安全,Google针对下一代 Android 系统(Android P) 的应用程序,将要求默认使用加密连接,这意味着 Android P 将禁止 App 使用所有未加密的连接,因此运行 Android P 系统的安卓设备无论是接收或者发送流量,未来都不能明码传输,需要使用下一代(Transport Layer Security)传输层安全协议,而 Android Nougat 和 Oreo 则不受影响。
(2)问题
因此在Android P 使用HttpUrlConnection进行http请求会出现以下异常
W/System.err: java.io.IOException: Cleartext HTTP traffic to **** not permitted
使用OKHttp请求则出现
java.net.UnknownServiceException: CLEARTEXT communication ** not permitted by network security policy
(3)分析
在Android P系统的设备上,如果应用使用的是非加密的明文流量的http网络请求,则会导致该应用无法进行网络请求,https则不会受影响,同样地,如果应用嵌套了webview,webview也只能使用https请求。
(4)解决方法
①APP改用https请求
②targetSdkVersion 降到27以下
③更改网络安全配置
// 首先,在res文件夹下创建一个xml文件夹,然后创建一个network_security_config.xml文件,文件内容如下:
// 接着,在AndroidManifest.xml文件下的application标签增加以下属性:
④在AndroidManifest.xml配置文件的application标签中直接插入
android:usesCleartextTraffic="true"
(5)学习链接
Android高版本联网失败报错:Cleartext HTTP traffic to xxx not permitted解决方法
版本问题遇到的 Caused by: java.lang.IllegalArgumentException
(1)报错日志
io.reactivex.exceptions.UndeliverableException: The exception could not be delivered to the consumer because it has already canceled/disposed the flow or the exception has nowhere to go to begin with
(2)解决方法
public class ReaderApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 在Application中设置Java全局异常监控
RxJavaPlugins.setErrorHandler(Throwable::printStackTrace);
}
}
(3)参考链接
io.reactivex.exceptions.UndeliverableException: The exception could not be delivered to the consumer because it has already canceled/disposed the flow or the exception has nowhere to go to begin with.
(1)错误
java.lang.IllegalAccessError: Field 'com.lm.presenter.LmViewPresenter.view' is inaccessible to class 'com.lm.presenter.SplashPresenter' (declaration of 'com.lm.presenter.SplashPresenter' appears in /data/data/com.luomi.sdkdemo/cache/wand/hotfix_pack_7.dex)
at com.lm.presenter.SplashPresenter.addAndSetImageView(SplashPresenter.java:291)
at com.lm.presenter.SplashPresenter.access$500(SplashPresenter.java:47)
at com.lm.presenter.SplashPresenter$3.run(SplashPresenter.java:172)
at android.os.Handler.handleCallback(Handler.java:793)
at android.os.Handler.dispatchMessage(Handler.java:98)
at android.os.Looper.loop(Looper.java:176)
at android.app.ActivityThread.main(ActivityThread.java:6701)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:249)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:783)
(2)解决
①集成MSA的sdk在module的libs下报错,将sdk迁移到主工程的libs下解决了问题。
②原因是自定义的classloader加载的class中能找到这个类,但这个类自定义的不存在与我的classloader加载的dex中,而是在别的classloader中。因此把这个类放入dex中解决了问题。
(3)参考链接
记一次java.lang.IllegalAccessError错误
(1)错误
Pending exception java.lang.NoSuchMethodError: no static or non-static method
"Lcom/tencent/smtt/export/external/libwebp;.nativeGetInfo([B[I[I)I"
(2)解决
在混淆的时候也应该对新增/更新的模块进行-keep。
(3)参考链接
java.lang.NoSuchMethodError找不到类错误
(1)错误
java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.gcloud.jlgc-1/base.apk"],
nativeLibraryDirectories=[/data/app/com.gcloud.jlgc-1/lib/arm, /data/app/com.gcloud.jlgc-1/base.apk!/lib/armeabi-v7a,
/vendor/lib, /system/lib]]] couldn't find "libbdSpilWakeup.so"
(2)解决
在main下面新建jniLibs,并把.so文件复制到该目录下,并把jar放在lib目录下并添加到项目中
android {
defaultConfig {
ndk {
// 设置支持的SO库架构
abiFilters 'armeabi' ,'x86', 'armeabi-v7a', 'x86_64','arm64-v8a'
}
}
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
}
(3)参考链接
UnsatisfiedLinkError: dalvik.system.PathClassLoader
(1)错误
java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
(2)解决方案1:帮孩子找父母,然后断绝亲子关系
// 找出parent(直接parent.removeView不就可以了嘛,可是你会发现ViewParent没有removeView方法)
ViewParent parent = childView.getParent();
// 找出这个parent具体是什么类型,例如:FrameLayout
if(parent!=null)
Log.i("who_are_you",parent.getClass().toString());
}
// 直接强转,解绑
FrameLayout mFrameLayout = (FrameLayout)parent;
mFrameLayout.removeView(child_view);
(3)解决方案2:给孩子找个继父母,然后断绝亲子关系
if(childView.getParent() != null) {
((ViewGroup)childView.getParent()).removeView(childView);
}
(3)学习链接
java.lang.IllegalStateException:The specified child already has a parent异常万能解决方案:removeView
(1)错误
如果在使用androidx库时, 又不小心间接使用了其他的老库, 可能会遇到如下的报错:
Program type already present: android.support.v4.os.ResultReceiver
Error: Program type already present: android.support.v4.app.INotificationSideChannel
Error: Program type already present: xxxxxxxx(此处可能会有各种变形)
(2)解决方案
可以尝试在gradle.properties中添加:
android.useAndroidX=true
android.enableJetifier=true
(3)学习链接
使用androidx时Program type already present报错的一种解决尝试
(1)错误
Execution failed for task “:app:transformResourcesWithMergeJavaResForDebug”.
More than one file was found with OS independent path ‘META-INF/proguard/androidx-annotations.pro’
(2)解决方案
在build.gradle文件的android节点下增加packagingOptions,过滤掉提示重复的文件:
packagingOptions {
exclude 'META-INF/proguard/androidx-annotations.pro'
}
(3)学习链接
Android Studio编译失败:More than one file was found with OS independent path 'META-INF/proguard/androidx
(1)错误
java.lang.NoClassDefFoundError: Failed resolution of: Landroid/support/v4/animation/AnimatorCompatHelper
(2)原因
经过查找,项目中使用的RecycleView类,进入类里面发现AnimatorCompatHelper缺少引用,使用命令gradlew :app:dependencies查看库的依赖树发现RecycleView使用的是25.1.0,而其它support使用的是26.0.1,而导致无法发现AnimatorCompatHelper。
(3)解决方案
①删除其中一个recyclerview的引用;
②都改为版本一致的ecyclerview的引用;
③build.gradle里面加入如下代码块
configurations.all {
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
def requested = details.requested
if (requested.group == 'com.android.support') {
if (!requested.name.startsWith("multidex")) {
details.useVersion '26.0.1'
}
}
}
}
(4)学习链接
java.lang.NoClassDefFoundError: Failed resolution of: Landroid/support/v4/animation/AnimatorCompatHelper
java.lang.NoClassDefFoundError: Failed resolution of: Landroid/support/v4/animation/AnimatorCompatHelper
(1) 判断是否是完整的域名
public static boolean isCompleteUrl(String text) {
Pattern p = Pattern.compile("((http|ftp|https)://)(([a-zA-Z0-9\\._-]+\\.[a-zA-Z]{2,6})|([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}))(:[0-9]{1,4})*(/[a-zA-Z0-9\\&%_\\./-~-]*)?", Pattern.CASE_INSENSITIVE);
Matcher matcher = p.matcher(text);
return matcher.find();
}
(2)判断是否是缺少前缀的域名
/**
* 是否是缺少前缀的域名
*/
public static boolean isHalfCompleteUrl(String text) {
Pattern p = Pattern.compile("(([a-zA-Z0-9\\._-]+\\.[a-zA-Z]{2,6})|([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}))(:[0-9]{1,4})*(/[a-zA-Z0-9\\&%_\\./-~-]*)?", Pattern.CASE_INSENSITIVE);
Matcher matcher = p.matcher(text);
return matcher.find();
}
(3)参考链接
Android利用正则表达式如何匹配URL
Android正则表达式匹配URL
(1)日期格式化
/**
* 日期格式化
* @param date
* @return
*/
public static String formatDate(Long date) {
SimpleDateFormat format = new SimpleDateFormat("MM月dd日", Locale.getDefault());// yyyy-MM-dd HH:mm:ss
return format.format(new Date(date));
}
android 时间获取以及时间格式化
(2)获取系统时间
SimpleDateFormat formatter = new SimpleDateFormat ("yyyy年MM月dd日 HH:mm:ss ");
Date curDate = new Date(System.currentTimeMillis());//获取当前时间
String str = formatter.format(curDate);
Android–获取当前系统时间
(3)比较两个日期是否在同一天
/**
* 比较两个日期是否在同一天
* @param date1
* @param date2
* @return
*/
public static boolean isSameDate(Long date1, Long date2) {
Calendar cal1 = Calendar.getInstance();
cal1.setTimeInMillis(date1);
Calendar cal2 = Calendar.getInstance();
cal2.setTimeInMillis(date2);
boolean isSameYear = cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR);
boolean isSameMonth = isSameYear && cal1.get(Calendar.MONTH) == cal2.get(Calendar.MONTH);
boolean isSameDate = isSameMonth && cal1.get(Calendar.DAY_OF_MONTH) == cal2.get(Calendar.DAY_OF_MONTH);
return isSameDate;
}
(4)判断两天在同一周内
/**
* 判断date和当前日期是否在同一周内(开始是周日,结束是周六 )
* 注: 参考(2)获取系统时间先获取系统时间,并转化为Date
*
* Calendar类提供了一个获取日期在所属年份中是第几周的方法,对于上一年末的某一天
* 和新年初的某一天在同一周内也一样可以处理,例如2012-12-31和2013-01-01虽然在
* 不同的年份中,但是使用此方法依然判断二者属于同一周内
*/
public static boolean isSameWeekWithToday(Date date) {
if (date == null) {
return false;
}
// 0.先把Date类型的对象转换Calendar类型的对象
Calendar todayCal = Calendar.getInstance();
Calendar dateCal = Calendar.getInstance();
todayCal.setTime(new Date());
dateCal.setTime(date);
// 1.比较当前日期在年份中的周数是否相同
if (todayCal.get(Calendar.WEEK_OF_YEAR) == dateCal.get(Calendar.WEEK_OF_YEAR)) {
return true;
} else {
return false;
}
}
/**
* 判断是否包含SIM卡
*/
public static boolean hasSimCard() {
TelephonyManager telMgr = (TelephonyManager)
FungoApplication.getAppContext().getSystemService(Context.TELEPHONY_SERVICE);
boolean result = true;
switch (telMgr.getSimState()) {
case TelephonyManager.SIM_STATE_ABSENT:
// 没有SIM卡
result = false;
break;
case TelephonyManager.SIM_STATE_UNKNOWN:
String subscriberId = telMgr.getSubscriberId();
result = !TextUtils.isEmpty(subscriberId);
break;
default:
break;
}
return result;
}
如何通过代码判断手机中是否有SIM卡及各种状态
Android判断手机里是否有SIM卡
/**
* 匹配任何不可见字符,包括空格、制表符、换页符、回车符等,[\s]等价于[\f\n\r\t\v]
* 注意:这个已经包含\n \t 不要再去附加\n \t的情况
*
* @param s
*/
public static String removeSpace(String s) {
String regex = "\\s";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(s);
return matcher.replaceAll("");
}
Android开发之去掉空格
// 截取手机号码 方法一
String phonenum = "15718807588";
if(!TextUtils.isEmpty(phonenum) && phonenum.length() > 6) {
StringBuilder sb =new StringBuilder();
for (int i = 0; i < phonenum.length(); i++) {
char c = phonenum.charAt(i);
if (i >= 3 && i <= 6) {
sb.append('*');
} else {
sb.append(c);
}
}
ToastUtil.showImgMessage(context,sb.toString()+"1");
}
// 截取手机号码 方法二
phonenum = phonenum.substring(0, 3) + "****" + phonenum.substring(7, 11);
ToastUtil.showImgMessage(context,phonenum + "2");
// 方法三:用****替换手机号码中间4位
String mobile = "15718807588";
String maskNumber = mobile.substring(0,3)+"****"+mobile.substring(7,mobile.length());
assets下文件在打包生成apk的时候不会被编译,以文件原有的方式来保存,可以通过AssetManager来操作这些文件。避免了存储在res文件下的文件模糊、压缩等问题。
系统在编译的时候不会编译assets下的资源文件,所以我们不能通过R.xxx.ID的方式访问它们。那我么能不能通过该资源的绝对路径去访问它们呢?因为apk安装之后会放在/data/app/**.apk目录下,以apk形式存在,asset/res和被绑定在apk里,并不会解压到/data/data/YourApp目录下去,所以我们无法直接获取到assets的绝对路径,因为它们根本就没有。
ImageLoader.getInstance().displayImage(Uri.parse("file:///android_asset/ic_qrcode_default.png"), ivQrCode, ImageLoaderOptions.getOptionQrCode());
(1)访问assets目录下的资源文件
AssetManager.open(String filename),返回的是一个InputSteam类型的字节流,这里的filename必须是文件比如(aa.txt;img/semll.jpg),而不能是文件夹。
AssetManager assetManager = getResources().getAssets();
InputStream inputStream = assetManager.open("fungo_cst_src.txt");
(2)获取assets的文件及目录名
// 获取assets目录下的所有文件及目录名,content
//(当前的上下文如Activity,Service等ContextWrapper的子类的都可以)
String fileNames[] =context.getAssets().list(path);
(1)res 目录下面有很多文件,例如 drawable,mipmap,raw 等。res 下面除了 raw 文件不会被压缩外,其余文件都会被压缩。同时 res目录下的文件可以通过R 文件访问。
(2)asset 也是用来存储资源,但是 asset 文件内容只能通过路径或者 AssetManager 读取。
(1)String类的format()方法用于创建格式化的字符串以及连接多个字符串对象。熟悉C语言的同学应该记得c语言的sprintf()方法,两者有类似之处。format()方法有两种重载形式。
(2)format(String format, Object… args) 新字符串使用本地语言环境,制定字符串格式和参数生成格式化的新字符串。
(3)format(Locale locale, String format, Object… args) 使用指定的语言环境,制定字符串格式和参数生成格式化的字符串。
public static void main(String[] args) {
String str=null;
str=String.format("Hi,%s", "王力");
System.out.println(str);
str=String.format("Hi,%s:%s.%s", "王南","王力","王张");
System.out.println(str);
System.out.printf("字母a的大写是:%c %n", 'A');
System.out.printf("3>7的结果是:%b %n", 3>7);
System.out.printf("100的一半是:%d %n", 100/2);
System.out.printf("100的16进制数是:%x %n", 100);
System.out.printf("100的8进制数是:%o %n", 100);
System.out.printf("50元的书打8.5折扣是:%f 元%n", 50*0.85);
System.out.printf("上面价格的16进制数是:%a %n", 50*0.85);
System.out.printf("上面价格的指数表示:%e %n", 50*0.85);
System.out.printf("上面价格的指数和浮点数结果的长度较短的是:%g %n", 50*0.85);
System.out.printf("上面的折扣是%d%% %n", 85);
System.out.printf("字母A的散列码是:%h %n", 'A');
(3)结果
Hi,王力
Hi,王南:王力.王张
字母a的大写是:A
3>7的结果是:false
100的一半是:50
100的16进制数是:64
100的8进制数是:144
50元的书打8.5折扣是:42.500000 元
上面价格的16进制数是:0x1.54p5
上面价格的指数表示:4.250000e+01
上面价格的指数和浮点数结果的长度较短的是:42.5000
上面的折扣是85%
字母A的散列码是:41
发送 %d/9
mTvSend.setText(getString(R.string.local_document_send_limit, selectedDocuments.size()));
JAVA字符串格式化-String.format()的使用
以前写的那些方式都不是很好用。现在的这种方式通过广播的方式监听home键:
(1)首先是创建一个广播接受者;
(2)注册监听;
(3)完整代码:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//注册广播
registerReceiver(mHomeKeyEventReceiver, new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
}
/**
* 监听是否点击了home键将客户端推到后台
*/
private BroadcastReceiver mHomeKeyEventReceiver = new BroadcastReceiver() {
String SYSTEM_REASON = "reason";
String SYSTEM_HOME_KEY = "homekey";
String SYSTEM_HOME_KEY_LONG = "recentapps";
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {
String reason = intent.getStringExtra(SYSTEM_REASON);
if (TextUtils.equals(reason, SYSTEM_HOME_KEY)) {
//表示按了home键,程序到了后台
} else if(TextUtils.equals(reason, SYSTEM_HOME_KEY_LONG)){
//表示长按home键,显示最近使用的程序列表
}
}
}
};
}
/**
* Demo描述: 处理Back键按下事件
* 注意事项: 以下两种方法勿一起使用
*/
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
/**
* 方法1: 监听Back键按下事件
* 注意: super.onBackPressed()会自动调用finish()方法,关闭当前Activity. 若要屏蔽Back键盘,注释该行代码即可
*/
@Override
public void onBackPressed() {
super.onBackPressed();
System.out.println("按下了back键 onBackPressed()");
}
/**
* 方法2: 监听Back键按下事件
* 注意: 返回值表示:是否能完全处理该事件在此处返回false,所以会继续传播该事件.在具体项目中此处的返回值视情况而定.
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((keyCode == KeyEvent.KEYCODE_BACK)) {
System.out.println("按下了back键 onKeyDown()");
return false;
}else {
return super.onKeyDown(keyCode, event);
}
}
}
(1)方法1
LinearLayout layout=(LinearLayout)LayoutInflater.from(getApplicationContext()).inflate(R.layout.list_view_item_text, null);
LinearLayout layout=(LinearLayout)LayoutInflater.from(MainActivity.this).inflate(R.layout.list_view_item_text, null);
(2)方法2
LayoutInflater layoutInflater=(LayoutInflater) getApplicationContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LinearLayout layout=(LinearLayout)layoutInflater.inflate(R.layout.list_view_item_text, null);
(3)方法3
LinearLayout layout=(LinearLayout)getLayoutInflater().inflate(R.layout.list_view_item_text, null);
LinearLayout layout=(LinearLayout)MainActivity.this.getLayoutInflater().inflate(R.layout.list_view_item_text, null);
启动模式为singleTop时,系统会先检查栈顶是不是该Activity,如果不是的话,它会创建一个该Activity的实例,并启动onCreate函数。如果栈顶是,则不会再创建该Activity,不会执行onCreate函数,而是执行onNewIntent函数来重新启动。同理,当启动模式为singleTask时,若栈顶不是该Activity系统会在栈中寻找是否存在这个实例,如是的话就会把这个实例放在栈顶,并把它之前的实例清除掉,就会执行onNewIntent函数(道理和singleTop一样)。两种情况执行的顺序变为:onPause()–>onStop()----发送Intent—>onNewIntent()–>onRestart()–>onStart()–>onResume()。如代码所示:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
getIntent();// 获取的是第一次启动Activity传递的值
}
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
intent.getExtra().get---//获取新传递的值
}
(1)Activity.runOnUiThread(Runnable )
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, "hah", Toast.LENGTH_SHORT).show();
}
});
(2)子线程调用Handler的sendMessage(message)发送事件
//主线程
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//操作界面
myText.setText( 来自网络的信息);
super.handleMessage(msg);
}
};
//子线程
public class MyThread extends Thread {
public void run() {
// 耗时操作
loadNetWork();
Message msg = new Message();
mHandler.sendMessage(msg);//向Handler发送消息,
}
}
//子线程
public class MyThread extends Thread {
//Looper.getMainLooper()就表示放到主UI线程去处理
Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
//操作界面
myText.setText( 来自网络的信息);
super.handleMessage(msg);
}
};
public void run() {
// 耗时操作
loadNetWork();
Message msg = new Message();
mHandler.sendMessage(msg);//向Handler发送消息,
}
}
(3)View.post(Runnable r):从Runnable派生你的子类,重载run()方法。然后调用View.post(myRunnableObj)即可把你的Runnable对象增加到UI线程中运行。
public void onClick( View v ) {
new Thread( new Runnable() {
public void run() {
// 耗时操作
loadNetWork();
myText.( new Runnable() {
myText.setText( 来自网络的信息);
});
}
}).start();
}
(4)使用异步任务
//UI线程中执行
new DownloadImageTask().execute("https://www.baidu.com");
private class DownloadImageTask extends AsyncTask {
protected String doInBackground( String... url ) {
//后台耗时操作
return loadDataFormNetwork( url[0] );
}
protected void onPostExecute( String result ) {
//得到来自网络的信息刷新页面
myText.setText( result );
}
}
(5)RxJava切换子线程到主线程
(1)跳转的方法
public static void actionTo(Context context, String name, String url) {
String url = Uri.parse("svmplayer://player?name=" + name + "&url=" + url));
openThirdActivity(context, url, "android.intent.category.DEFAULT");
}
/**
* 跳转第三方应用
* 通过隐式Intent的跳转,在发出Intent之前必须通过resolveActivity检查,避免找不到合适的调用组件,造成ActivityNotFoundException的异常
*
* @param context
* @param url
*/
public static boolean openThirdActivity(Context context, String url, int name) {
if (context == null || TextUtils.isEmpty(url)) {
return false;
}
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
if (name == THIRD_ACTIVITY_BROWSABLE) {
intent.addCategory(Intent.CATEGORY_BROWSABLE);
}
if (context.getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null) {
try {
context.startActivity(intent);
return true;
} catch (Exception e) {
e.printStackTrace();
}
} else {
if (name == THIRD_ACTIVITY_BROWSABLE) {
ToastUtils.showToast(context, context.getString(R.string.toast_browser_uninstalled));
} else if (name == THIRD_ACTIVITY_WEIXIN) {
ToastUtils.showToast(context, context.getString(R.string.toast_weixin_uninstalled));
}
}
return false;
}
try {
if (intent != null) {
videoUrl = URLDecoder.decode(intent.getData().getQueryParameter("url"), "UTF-8");
title = URLDecoder.decode(intent.getData().getQueryParameter("name"), "UTF-8");
}
} catch (Exception e) {
Logger.e(e);
}
Android进阶之使用Scheme实现从网页启动APP
(1)首先是JS的一段代码
function javaCallJs(key, value){
document.getElementById("value").innerHTML = ("欢迎:" + value);
}
(2)然后是在java中调用JS中的方法
public abstract class InvokeJsBase {
private static final String rs = "javascript:javaCallJs(\"%s\", %s)";
public String invokeJs() {
String js = String.format(rs, key(), value());
return js;
}
abstract String key();
abstract String value();
}
import com.alibaba.fastjson.JSON;
/**
* 具体实现调用的方法
*/
public class InvokeCollectStatus extends InvokeJsBase {
public boolean value;
public InvokeCollectStatus(boolean value) {
this.value = value;
}
@Override
String key() {
return "statusKey";
}
@Override
String value() {
return JSON.toJSONString(this);
}
}
(3)以上代码就是调用了JS中一个叫javaCallJs(arg)的方法,并传入了一个key、value参数。
(1)配置Javascript接口
webView.addJavascriptInterface(new JSInterface (),"playerClient");
(2)实现Javascript接口类
public class ProgressWebView extends WebView {
private Context context;
public ProgressWebView(final Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
addJavascriptInterface(new JavaScriptinterface(), "playerClient");
}
class JavaScriptinterface {
@JavascriptInterface
public void showToast(String arg) {
Toast.makeText(MainActivity.this, arg, Toast.LENGTH_SHORT).show();
}
}
}
(3)JS中调用java代码
(4)window.playerClient.showToast(‘JS中传来的参数’)”中的"playerClient"在addJavascriptInterface()中指定的,并且JS向java传递了参数,类型为String。而showToast(String arg)会以Toast的形式弹出此参数。
WebView详解与简单实现Android与H5互调
Android和H5之间的交互
Hybrid混合开发
(1)工具类:参考超级影音或者云图手机电视的本地视频例子。
(2)参考例子
RxJava获取本地视频
android 获取本地视频文件以及缩略图
自定义SeekBar主题
一个功能强大的自定义SeekBar
Android自定义星星评分控件,高效
(1)检查APP是否已经安装
public static boolean isPkgInstalled(Context context, String pkgName) {
PackageManager packageManager = context.getPackageManager();
// 获取手机系统的所有APP包名,然后进行一一比较
List pinfo = packageManager.getInstalledPackages(0);
for (int i = 0; i < pinfo.size(); i++) {
if (pinfo.get(i).packageName.equalsIgnoreCase(pkgName))
return true;
}
return false;
}
(2)安装已经下载好的apk
private void install(String apkname) {
File dir = mActivity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
if (dir != null) {
String path = dir.getPath() + "/xiaoai.apk";
File file = new File(path);
if(file.exists()) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(new File(path)),"application/vnd.android.package-archive");
mActivity.startActivity(intent);
} else {
downApk();//安装包已经删除请重新下载
}
} else {
downApk();
}
}
(3)安装已经下载好的apk
private void openApk(String url) {
PackageManager packageManager = mActivity.getPackageManager();
Intent intent = packageManager.getLaunchIntentForPackage("com.fungo.loveshow");
startActivity(intent);
}
(4)打开另一个APP指定的Activity
/**
* 注意:
* 1.需要将目标Activity的android:exported="true"属性在所属应用AndroidMainfest里设置为true,
* 意思是当前Activity可以被外部应用访问。
* 2.需要在当前应用的AndroidMainfest里也声明目标Activity。
*/
public void exportActivity() {
Intent intent = new Intent();
//第一种方式
ComponentName cn = new ComponentName("com.example.fm", "com.example.fm.MainFragmentActivity");
intent.setComponent(cn);
//第二种方式
//intent.setClassName("com.example.fm", "com.example.fm.MainFragmentActivity");
intent.putExtra("test", "intent1");
startActivity(intent);
}
(5)学习链接
Android APP检测安装打开APK三步操作
Android通过App启动另一个APP
实现 DownloadManager 下载完 apk 自动提示安装的功能
让WebView支持下载,需要给WebView设置下载监听器 setDownloadListener,DownloadListener里面只有一个方法 onDownloadStart,每当有文件需要下载时,该方法就会被回调,下载的 URL 通过方法参数传递,我们可以在这里处理下载事件。
mWebView.setDownloadListener(new DownloadListener() {
@Override
public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimeType, long contentLength) {
// 处理下载事件
}
});
(1)跳转浏览器下载
mWebView.setDownloadListener(new DownloadListener() {
@Override
public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimeType, long contentLength) {
// 处理下载事件-使用14 隐式启动Activity
openThirdActivity(context, url, Intent.CATEGORY_BROWSABLE);
}
});
(2)使用系统的DownloadManager下载服务
(3)自定义下载任务
(4)三种方式让 Android WebView 支持文件下载
(1)一般很多高版本的新的API都会在兼容包里面找到替代的实现。比如:Notification,在v4兼容包里面有NotificationCompat类。5.0+出现的backgroundTint,minSdk小于5.0的话会包检测错误,v4兼容包DrawableCompat类。
(2)在高版本 SDK 中使用高版本 API,低版本 SDK 中自己实现。
// 在使用了高版本API的方法前面加一个@TargetApi(API版本号)。
// 在代码中判断版本号来控制不同的版本使用不同的代码。
@TargetApi(11)
public void text() {
if(Build.VERSION.SDK_INT >= 11){
// 使用 API 11 的方法
} else {
// 使用自己实现的方法
}
(1)用途-举例:将List转为json存储在本地,再取出转化为List使用
(2)主要方法
// 把JSON文本parse为JSONObject或者JSONArray
public static final Object parse(String text);
// 把JSON文本parse成JSONObject
public static final JSONObject parseObject(String text);
// 把JSON文本parse为JavaBean
public static final T parseObject(String text, Class clazz);
// 把JSON文本parse成JSONArray
public static final JSONArray parseArray(String text);
//把JSON文本parse成JavaBean集合
public static final List parseArray(String text, Class clazz);
// 将JavaBean序列化为JSON文本
public static final String toJSONString(Object object);
// 将JavaBean序列化为带格式的JSON文本
public static final String toJSONString(Object object, boolean prettyFormat);
// 将JavaBean转换为JSONObject或JSONArray。
public static final Object toJSON(Object javaObject);
(3)工具类—JsonUtils
(4)参考链接
FastJSON 简介及其Map/JSON/String 互转
Java 集合转换(数组、List、Set、Map相互转换)
Java数组转成list,list转数组
String和String类型的List互相转换
/**
* 集合List转化为字符串
*
* @param list
* @return
*/
public static String listToString(List list) {
if (list == null) {
return "";
}
StringBuilder result = new StringBuilder();
boolean first = true;
// 第一个前面不拼接","
for (String string : list) {
if (first) {
first = false;
} else {
result.append(",");
}
result.append(string);
}
return result.toString();
}
/**
* 字符串转化为集合List
*
* @param strs
* @return
*/
public static List stringToList(String strs) {
String str[] = strs.split(",");
return Arrays.asList(str);
}
(1)整除(/)、求余(%)
//求余
System.out.println(11%2); //结果--> 1
System.out.println(11%-2); //结果--> 1
System.out.println(-11%2); //结果-->-1
System.out.println(-11%-2); //结果-->-1
//求余的正负号说明: 主要是取决于前面一个数是正数还是负数,不管后面数。
System.out.println(115%5); //结果为-->0
System.out.println(115%2); //结果为-->1
//整除(商)
System.out.println(11/2); //结果--> 5
System.out.println(11/-2); //结果-->-5
System.out.println(-11/2); //结果-->-5
System.out.println(-11/-2); //结果--> 5
System.out.println(10/2); //结果--> 5
System.out.println(10.0/2); //结果--> 5.0
System.out.println(35/2); //结果--> 17
System.out.println(0.35/2); //结果--> 0.175
(2)四舍五入
//方式1
DecimalFormat df = new java.text.DecimalFormat("#.00");
df.format(你要格式化的数字);
//例:#.00 表示两位小数 #.0000四位小数 以此类推...
new java.text.DecimalFormat("#.00").format(3.1415926)
//方式2:%.2f %.表示小数点前任意位数、2表示两位小数、格式后的结果为f表示浮点型
double d = 3.1415926;
String result = String .format("%.2f");
(3)综合示例
@NonNull
public static String getPlayTimes(int data) {
String times;
int MILLION = 10000;
int BILLION = 100000000;
DecimalFormat df = new DecimalFormat("#.0");
if (data < MILLION) {
times = data + "";
} else if (data >= MILLION && data < BILLION){
times = df.format(Double.valueOf(data) / MILLION) + "万";
} else {
times = df.format(Double.valueOf(data) / BILLION) + "亿";
}
Logger.e("正在观看:" + data + " " + data + " " + times);
// 正在观看:48980 48980.0 4.9万
return times;
}
(4)参考链接
Android double保留两位小数:截取 和 四舍五入(展示流量)
Fragment的设计主要是把Activity界面包括其逻辑打碎成很多个独立的模块,这样便于模块的重用和更灵活地组装呈现多样的界面。
(1)在Activity中嵌套Fragment要用getSupportFragmentManager
如果用的是v4 support库, getSupportFragmentManager()是activity中的方法, 返回的是管理Activity中Fragments的FragmentManager。(getSupportFragmentManager()主要用于支持 3.0以下android系统API版本,3.0以上系统可以直接调用getFragmentManager())
public class VPFrgActivity extends BaseActivity {
private List listPagerViews = null;
private PagerAdapter pagerAdapter = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_vpfrg);
initViewPager();
}
private void initViewPager() {
listPagerViews = new ArrayList<>();
listPagerViews.add(new Frg_4());
listPagerViews.add(new Frg_5());
listPagerViews.add(new Frg_6());
ViewPager viewPager = findViewById(R.id.vp_frg);
pagerAdapter = new FragmentPagerAdapter(this.getSupportFragmentManager()) {
@Override
public Fragment getItem(int position) {
return listPagerViews.get(position);
}
@Override
public int getCount() {
return listPagerViews.size();
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
super.setPrimaryItem(container, position, object);
}
};
// viewPager.setOffscreenPageLimit(3); // 不设置缓存页面,获取 ViewPath 会变。ViewPgaer 会 destroyItem
viewPager.setAdapter(pagerAdapter);
}
}
(2)在Fragment中嵌套Fragment要用getChildFragmentManager
getChildFragmentManager()是Fragment中的方法, 返回的是管理当前Fragment内部子Fragments的manager。
public class Frg_2 extends BaseAndroidXFragment {
private static FragmentPagerAdapter pagerAdapter = null;
private View view = null;
private List listPagerViews = null;
public Frg_2() { }
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_frg_2, container, false);
view.findViewById(R.id.tv_frg_2).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(v.getContext(), "Frg_2 点击", Toast.LENGTH_SHORT).show();
}
});
initViewPager();
return view;
}
private void initViewPager() {
listPagerViews = new ArrayList<>();
listPagerViews.add(new Frg_4());
listPagerViews.add(new Frg_5());
listPagerViews.add(new Frg_6());
ViewPager viewPager2 = (ViewPager) view.findViewById(R.id.frg_2_vp);
pagerAdapter = new FragmentPagerAdapter(this.getChildFragmentManager()) {
@Override
public Fragment getItem(int position) {
return listPagerViews.get(position);
}
@Override
public int getCount() {
return listPagerViews.size();
}
@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
super.setPrimaryItem(container, position, object);
}
};
viewPager2.setAdapter(pagerAdapter);
}
}
(3)学习链接
安卓开发之详解getChildFragmentManager和getFragmentManager详解
Android Fragment使用(二) 嵌套Fragments (Nested Fragments) 的使用及常见错误
getParentFragment() in fragments is returning null when fragment is popped out from backstack
Fragment全解析系列(二):正确的使用姿势
Android仿今日头条多个fragment懒加载的实现
public abstract class LazyloadFragment extends Fragment {
/**
* 父容器
*/
private ViewGroup mViewRoot;
/**
* 是否已初始化View
*/
private boolean mIsCreateView = false;
/**
* 是否可见
*/
private boolean mIsVisible = false;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstancesTate) {
View rootView = inflater.inflate(R.layout.layout_view_root, container, false);
mViewRoot = rootView.findViewById(R.id.view_root);
mIsCreateView = true;
isCanLoadData();
return rootView;
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
// mIsVisible这个boolean值表示:该fragment的ui,用户是否可见,获取该标志记录下来
if (isVisibleToUser) {
mIsVisible = true;
isCanLoadData();
} else {
mIsVisible = false;
}
}
/**
* view初始化完成并且对用户可见
*/
private void isCanLoadData() {
if (mViewRoot != null && mIsCreateView && mIsVisible) {
initBaseView(mViewRoot);
lazyInitLoad();
// 防止重复加载数据
mIsCreateView = false;
mIsVisible = false;
}
}
/**
* 动态加载布局
*
* @param viewRoot
*/
private void initBaseView(ViewGroup viewRoot) {
viewRoot.removeAllViews();
View rootView = LayoutInflater.from(getActivity()).inflate(setContentView(), viewRoot, true);
initView(rootView);
}
/**
* 加载页面布局文件
*
* @return
*/
protected abstract int setContentView();
/**
* 让布局中的view与fragment中的变量建立起映射
* TextView tvInput = rootView.findViewById(R.id.tv_input);
*
* @param rootView
*/
protected abstract void initView(View rootView);
/**
* 加载数据
*/
protected abstract void lazyInitLoad();
}
参考云图项目的截图检测+上报逻辑。
RxScreenshotDetector:Android 截屏检测
公司多个项目使用:ConvenientBanner
Android图片轮播控件
(1) ListView的HeaderView/FooterView由于在Adapter里是被ArrayList、SparseArray强引用,不会被回收。适合使用不需要被回收的控件,例如:ViewFlipper。(2018/8/2)
ListView中Item为EditText获取与保存数据
(1)问题描述:如果list item里面包括button或者checkbox等控件,默认情况下listitem会失去焦点,导致无法响应item的事件。
(2)解决办法:在list item的布局文件中设置descendantFocusability属性。
(3)分析:
(4)翻译:该属性是当一个为view获取焦点时,定义viewGroup和其子控件两者之间的关系,属性的值有三种:
①beforeDescendants:viewgroup会优先其子类控件而获取到焦点;
②afterDescendants:viewgroup只有当其子类控件不需要获取焦点时才获取焦点;
③blocksDescendants:viewgroup会覆盖子类控件而直接获得焦点。
(5)学习链接
Android ListView的item点击无响应的解决方法
(1)addHeaderView和setAdapter的顺序问题
①异常提示:java.lang.IllegalStateException: Cannot add header view to list – setAdapter has already been called。
②原因:先调用setAdapter(),再调用addHeaderView(),并且该代码运行在Android4.3之前的系统版本
③解决方法:先调用addHeaderView(),再调用setAdapter()。
④源码分析参考:addHeaderView()异常 —— setAdapter has already been called
(2)ListView用法及其注意事项
Android:ListView.addHeaderView()用法及其注意事项
Android 中 ListView 的 setSelection 无效怎么办?
RecyclerView系列之(3):添加下拉刷新和上拉加载更多
一种优雅的方式实现RecyclerView条目多类型
public File getItem(int position) {
if (mFileSortBeans != null && mFileSortBeans.size() > position && position >= 0) {
FileSortBean fileSortBean = mFileSortBeans.get(position);
if (fileSortBean != null) {
return fileSortBean.getFile();
}
}
return null;
}
(1)列表的滚动一般分为两种:
手指按下 -> 手指拖拽列表移动 -> 手指停止拖拽 -> 抬起手指
手指按下 -> 手指快速拖拽后抬起手指 -> 列表继续滚动 -> 停止滚动
静止 -> 被迫拖拽移动 -> 静止
静止 -> 被迫拖拽移动 -> 自己滚动 -> 静止
(2)查看源码
public abstract static class OnScrollListener {
/**
* 滚动状态变化时回调
*
* @param recyclerView 当前滚动的view
* @param newState 目前的状态,newState有三种值:
* SCROLL_STATE_IDLE = 0:正在滚动
* SCROLL_STATE_DRAGGING = 1:正在被外部拖拽,一般为用户正在用手指滚动
* SCROLL_STATE_SETTLING = 2:自动滚动开
*/
public void onScrollStateChanged(RecyclerView recyclerView, int newState){}
/**
* 滚动时回调
*
* @param recyclerView 当前滚动的view
* @param dx 水平滚动距离,dy>0时为向上滚动
* @param dy 垂直滚动距离,dy<0时为向下滚动
*/
public void onScrolled(RecyclerView recyclerView, int dx, int dy){}
}
(3)判断是否已经到底部或者顶部
/**
* 垂直方向的判断
*/
public boolean canScrollVertically(int direction) {
final int offset = computeVerticalScrollOffset();
final int range = computeVerticalScrollRange() - computeVerticalScrollExtent();
if (range == 0) return false;
if (direction < 0) {
return offset > 0;
} else {
return offset < range - 1;
}
}
// 计算控件垂直方向的偏移值
computeVerticalScrollOffset
// 计算控件可视的区域
computeVerticalScrollExtent
// 计算控件垂直方向的滚动范围
computeVerticalScrollRange
// 水平方向的判断
public boolean canScrollHorizontally(int direction) {
final int offset = computeHorizontalScrollOffset();
final int range = computeHorizontalScrollRange() - computeHorizontalScrollExtent();
if (range == 0) return false;
if (direction < 0) {
return offset > 0;
} else {
return offset < range - 1;
}
}
// 设置正数判断是否滑动到底部,返回false表示不能往上滑动,即代表到底部了;
recyclerView.canScrollVertically(1);
// 设置负数判断是否滑动到顶部, 返回false表示不能往下滑动,即代表到顶部了;
recyclerView.canScrollVertically(-1);
(4)封装一个可以回调滚动状态和方向的RecyclerView
// 1、先建立事件监听的接口OnScrollCallback
public interface OnScrollCallback {
void onStateChanged(ScrollRecycler recycler, int state);
void onScrollUp(ScrollRecycler recycler, int dy);
void onScrollDown(ScrollRecycler recycler, int dy);
}
// 2、写一个类ScrollRecycler继承RecyclerView,在类中添加以下方法
public void setOnScrollCallback(final OnScrollCallback callback) {
if (callback == null) {
return;
}
addOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
callback.onStateChanged(ScrollRecycler.this, newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dy > 0) {
callback.onScrollDown(ScrollRecycler.this, dy);
} else {
callback.onScrollUp(ScrollRecycler.this, dy);
}
}
});
}
(5)参考链接
RecyclerView的滚动事件分析
(1)Java编程的逻辑—125页;(后续总结文章)
(2) Java异常类层次结构图:
(3)判断一个方法可能会出现异常的依据如下:
// ①方法中有throw语句;
// ②调用了其他方法,其他方法的括号后面用throws子句声明抛出某种异常。
(4)学习链接:JAVA 异常分类与理解
(1)基本方法
// 获取消耗的时间。
android.os.Process.getElapsedCpuTime()
// 获取该进程的ID
android.os.Process.myPid()
// 获取该线程的ID
android.os.Process.myTid()
// 获取该进程的用户ID
android.os.Process.myUid()
// 判断该进程是否支持多进程
android.os.Process.supportsProcesses
(2)参考链接
android如何打印当前的线程及进程
/**
* 设置目标 Activity(要启动的那个Activity)是透明的。
* 两个Activity,A和B;在A中启动B,因为B是透明的,看到的背景仍是 A,这样就解决了这个短暂的黑屏问题。
*/
(1)问题描述:在Android开发过程中,如果使用EditText弹出了输入法时,可能会遇到如下情况:在收起输入法时,手机屏幕被输入法挡住的部分会变为黑色,部分黑屏。
(2)思考与想法:寻找解决办法很久,但没找到。想到这不应该是自定义布局背景的原因,那么显示黑屏的部分就应该是从更深层次的view的背景引起的,那么使用自定义的view通过函数getRootView()函数来获取其上一层view并修改其背景色即可。
(3)举例
View llRoot = findViewById(R.id.ll_root);
llRoot.getRootView().setBackgroundResource(R.color.white);
(4)参考链接
关于Android收起输入法时会出现屏幕部分黑屏解决
private void setAnimationData() {
if (mFromFileSync) {
// 同步加载(修复进入App第一次加载延迟问题)
LottieComposition composition = LottieComposition.Factory.fromFileSync(mParent, Constants.ANIMATION_LOADING);
mAnimationView.setComposition(composition);
// 同时异步缓存(缓存STRONG_REF_CACHE使用static)
mAnimationView.setAnimation(Constants.ANIMATION_LOADING, LottieAnimationView.CacheStrategy.Strong);
} else {
// 异步加载(可以使用缓存)
mAnimationView.setAnimation(Constants.ANIMATION_LOADING, LottieAnimationView.CacheStrategy.Strong);
}
mAnimationView.loop(true);
}
官方—airbnb/lottie-android
QQ音乐技术团队—Lottie : 让动画如此简单
博客专家—Lottie的使用及原理浅析
Android Lottie动画的简单使用
lottie加载动画,第一次有延迟问题:第一次使用同步加载、同时设置异步加载缓存数据
/**
* 隐藏虚拟按键
* 在SplashActivity的onCreate()添加此方法
*/
private void hideBottomUIMenu() {
// 隐藏虚拟按键
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH && Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
View v = this.getWindow().getDecorView();
v.setSystemUiVisibility(View.GONE);
} else if (Build.VERSION.SDK_INT >= 19) {
View decorView = getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
decorView.setSystemUiVisibility(uiOptions);
}
}
android 启动页冷启动适配全面屏和虚拟键
(1)问题如图
(2)解决方案:检查build.gradle文件中buildtypes/debug 是否打开了混淆,关闭混淆。
(3)学习链接:Activity中所有的变量,在断点调试下都是No such instance field
(1)目前的工具的弊端
(2)Android-Debug-Database的特色
(3)如何使用
// debugCompile的作用:只在你debug编译时起作用,当你release的时候就不要使用
debugCompile 'com.amitshekhar.android:debug-db:1.0.0'
D/DebugDB: Open http://XXX.XXX.X.XXX:8080
(4)原理-参考链接
调试手机中数据库的福音:Android-Debug-Database
Github-Android-Debug-Database
(1)SharedPreferences对象获取
// Context类中的getSharedPreferences方法可以获取一个SharedPreferences对象
SharedPreferences mSp = mContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
// 其本质也是在文件不存在的情况下new一个File
private File makeFilename(File base, String name) {
if (name.indexOf(File.separatorChar) < 0) {
return new File(base, name);
}
throw new IllegalArgumentException( "File " + name + " contains a path separator");
}
(2)commit方法
/**
* commit方法是有一个boolean的返回值
* 当数据变化进行存储时是一个原子性的操作
* 当两个editor对象同时对一个共享的preferences参数进行操作时,永远都是最后一个调用commit方法的editor变更了最后的数据值
*/
boolean commit();
(3)apply方法
/**
* apply方法是没有返回值的
* 当两个editor同时对preferences对象编辑时,也是最后一个调用apply方法的对象编辑数据
* apply的提交操作也是原子性的
*/
void apply();
(4)commit和apply方法的区别
(5)学习链接
SharedPreferences中的commit和apply方法
(1)在XML中使用
android:drawableLeft="@drawable/icon"
(2)代码中动态设置
Drawable drawable = ContextCompat.getDrawable(context, R.drawable.ic_mark_big_new);
if (drawable != null) {
// 这一步必须要做,否则不会显示.
drawable.setBounds(0, 0, drawable.getMinimumWidth(), drawable.getMinimumHeight());
textview.setCompoundDrawables(null, null, drawable, null);
// 设置字体颜色
textView.setTextColor(getResources().getColor(R.color.gray_textcolor_shen));
// 设置图片和text之间的间距
textView.setCompoundDrawablePadding(DeviceUtils.dp2px(context, 4));
// 设置整个textView的间距
textView.setPadding(-5, 0, 0, 0);
// 设置整个textView透明度(包括:图片和text)
textView.setAlpha(0.64f);
}
(3)也或参考另一个函数
public void setCompoundDrawablesWithIntrinsicBounds (Drawable left,
Drawable top, Drawable right, Drawable bottom)
(4)学习链接
使用代码为textview设置drawableLeft
(1)最简单的布局方法
android:textStyle="bold"
(2)代码中设置字体粗体
TextView textView = (TextView)findViewById(R.id.textView);
// android中为textview动态设置字体为粗体
textView .setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
// 设置不为加粗
textView.setTypeface(Typeface.defaultFromStyle(Typeface.NORMAL));
public class Main2Activity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
TextView text4 = (TextView) findViewById(R.id.text4);
text4.setText("8、姓" + "\u3000" + "名");
}
}
(3)总结:
①7和8都是一个汉字的间隔。其中,在代码中设置text4.setText(“8、姓” + “\u3000” + “名”);的"\u3000"是一个汉字的间隔;
②4是稍大的空格;
③5是稍小的空格。
(4)参考链接
Android布局中的空格以及占一个汉字宽度的空格的实现
// XML文件保存在res/color/button_text.xml
// 1.代码中
Button btn = (Button)findViewById(R.id.btn);
Resources resource = (Resources)getBaseContext().getResources();
ColorStateList csl = (ColorStateList)resource.getColorStateList(R.color.button_text);
if(csl != null){
// 设置按钮文字颜色
btn.setTextColor(csl);
}
// 2.布局中
填填Android lineSpacingExtra 的坑,解决行间距兼容性问题
(1)将字符串中的多个关键字高亮&字体大小&是否加粗
public static SpannableStringBuilder highlightMultiKeywordSize(String original, Map keywordMap, int color, int size, boolean isBold) {
SpannableStringBuilder builder = new SpannableStringBuilder(original);
if (keywordMap == null) {
return builder;
}
for(Map.Entry entry : keywordMap.entrySet()){
if (!TextUtils.isEmpty(entry.getKey())) {
int start = original.indexOf(entry.getKey());
int end = start + entry.getKey().length();
if (start >= 0 && start <= end && start < original.length() && end <= original.length()) {
ForegroundColorSpan span = new ForegroundColorSpan(color);
builder.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
AbsoluteSizeSpan sizeSpan = new AbsoluteSizeSpan(size);
builder.setSpan(sizeSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
if (isBold) {
StyleSpan styleSpan = new StyleSpan(Typeface.BOLD);
builder.setSpan(styleSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (entry.getValue() != null) {
builder.setSpan(entry.getValue(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
}
return builder;
}
/**
* 获取高亮的字符串-点击事件HashMap
* @param context
* @return
*/
@NonNull
private Map getStringClickMap(Context context) {
String licenseAgreement = context.getString(R.string.qihoo_accounts_license_agreement_prompt_license_agreement);
String privacyProtection = context.getString(R.string.qihoo_accounts_license_agreement_prompt_privacy_protection);
Map keywordMap = new HashMap<>(4);
keywordMap.put(context.getString(R.string.privacy_dialog_quotes, licenseAgreement), new ClickableSpan() {
@Override
public void onClick(@NonNull View widget) {
ReaderWebActivity.actionStart(context, licenseAgreement, UrlConfigManager.getInstance().getUrl(UrlConfigManager.LICENSE_AGREEMENT_URL));
}
@Override
public void updateDrawState(@NonNull TextPaint ds) {
// 设置下划线 true显示、false不显示
ds.setUnderlineText(false);
}
});
return keywordMap;
}
(2)应用
/**
* 获取消息文案
* @param context
* @return
*/
private SpannableStringBuilder getMessageStr(Context context) {
Map spanMap = getStringClickMap(context);
int color = ContextCompat.getColor(context, R.color.orange);
int size = DeviceUtils.dp2px(context, 14);
String message = context.getString(R.string.privacy_dialog_message);
SpannableStringBuilder spanStrBuilder = StringUtils.highlightMultiKeywordSize(message, spanMap, color, size, true);
return spanStrBuilder;
}
public void setMessage(CharSequence msg) {
mTvMessage.setText(msg);
// 设置点击后的颜色为透明
mTvMessage.setHighlightColor(ContextCompat.getColor(mContext, R.color.transparent));
}
(3)学习链接
Android设置TextView中部分字体颜色和点击事件
(1)命名空间(namespace)
xmlns是XML文档中的一个概念:英文叫做XML namespace,中文翻译为XML命名空间。XML命名空间提供避免元素命名冲突的方法。打个比方:A学校有名学生叫做林小明,B学校也有名学生叫林小明,那我们如何识别这两名拥有相同名字的同学呢?这时候命名空间就派上用场了,A和B此时就可以被当成是命名空间了。也就是说,命名空间里面存放的是特定属性的集合。
(2)Android中常见的命名空间:android
// android:称作namespace-prefix,它是命名空间的名字
// http://schemas.android.com/apk/res/android看起来是一个URL,但是这个地址是不可访问的。实际上这是一个URI(统一资源标识符),所以它的值是固定不变的,相当于一个常量。
xmlns:android=”http://schemas.android.com/apk/res/android”
// 例子
(3)tools:只作用于开发阶段,当app被运行/打包时所有关于tools属性将都会被摒弃掉
xmlns:tools=”http://schemas.android.com/tools”
// 例子
视图窗口(Design)中查看时,看到的是标签顶部居中显示:
当我们运行到手机上时,确是这样的:
(4)自定义命名空间:app
xmlns:app="http://schemas.android.com/apk/res-auto"
(5)学习链接
Android中XML的命名空间、自定义属性
(1)my_include.xml
(2)activity_main.xml
(3)MainActivity.java
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//第一种情况:间接得到文本组件(会得到两个)
textView1=(TextView) findViewById(R.id.include1).findViewById(R.id.text);
textView2=(TextView) findViewById(R.id.include2).findViewById(R.id.text);
//第二种情况:直接得到文本组件
textView3=(TextView) findViewById(R.id.text);
//更改textView3的文字
textView3.setText("啦啦啦啦啦啦啦啦啦");
}
(4)结论:通过间接和直接的方式都可以获取include内部的组件。
(5)因此:在同一个xml文件中同时引用多个同一个layout文件时,笔者推荐大家使用间接findViewById的方式获取include内部的组件。
(6)参考链接
过安卓中标签findViewById时出现的bug及解决方案
(1)关系图示–备注:1:3的比例是按密度区分,不是按尺寸
(2)学习链接
android适配(一) 之dp、dip、dpi、px、sp简介及相关换算
一套效果图适配(Android和IOS)全尺寸和标注规范
移动应用(APP)UI设计规范之尺寸篇上
(1)view.post(Runnable action)
// 这是view自带的方法,如果你的子线程里可以得到要更新的view的话,可以用此方法进行更新。
// view还有一个方法view.postDelayed(Runnable action, long delayMillis)用来延迟发送。
textView.post(new Runnable() {
@Override
public void run() {
textView.setText("更新textView");
// 还可以更新其他的控件
imageView.setBackgroundResource(R.drawable.update);
}
});
(2)activity.runOnUiThread(Runnable action)
/**
* 如果没有上下文(context),可以用view.getContext()可以得到上下文;
* 跳过context直接用new Activity().runOnUiThread(Runnable action)来切换到主线程。
*/
public void updateUI(final Context context) {
((MainActivity) context).runOnUiThread(new Runnable() {
@Override
public void run() {
// 此时已在主线程中,可以更新UI了
}
});
}
(3)Handler机制
// 在子线程中
android.os.Handler mainHandler = new android.os.Handler(Looper.getMainLooper());
mainHandler.post(new Runnable() {
@Override
public void run() {
// 已在主线程中,可以更新UI
}
});
new android.os.Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
}
});
// 假设在主线程中
Handler myHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch(msg.what) {
case 0:
//更新UI等
break;
default:
break;
}
}
}
(4)使用AsyncTask,执行顺序依次为:onPreExecute, doInBackground, onPostExecute
private class MyAsyncTask extends AsyncTask {
protected String doInBackground(String... params) { // 子线程中执行,执行一些耗时操作,关键方法,在执行过程中可以调用publishProgress(Progress... values)来更新进度信息。
System.out.println("MyAsyncTask.doInBackground");
// 只是模拟了耗时操作
int count = 0;
for (int i = 0; i < 10; i++) {
try {
count++;
publishProgress((count % 100) * 10);
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return "耗时操作执行完毕";
}
@Override
protected void onProgressUpdate(Integer... values) { // 主线程中执行:在调用publishProgress(Progress... values)时,此方法被执行,直接将进度信息更新到UI组件中
super.onProgressUpdate(values);
progressBar.setProgress(values[0]);
textView.setText("loading..." + values[0] + "%");
}
@Override
protected void onPostExecute(String aVoid) { // 在主线程执行:当后台操作结束时,此方法将会被调用,计算结果将做为参数传递到此方法中
super.onPostExecute(aVoid);
textView.setText(aVoid);
}
}
(5)RxJava的主线程和子线程切换
/**
* 数据库获取我的群组列表未读消息
*
* @param list
* @param refreshInfoListener
*/
@Override
public void getMyGroupListUnRead(List list, IRefreshInfoListener refreshInfoListener) {
mRefreshUnReadObservable = Observable.create(new ObservableOnSubscribe>() {
@Override
public void subscribe(final ObservableEmitter> emitter) throws Exception {
List groupList = new ArrayList<>(list);
List list = IMSessionDAO.queryAllSessionList(IMSessionDAO.QUERY_TYPE.GROUP);
for (GroupInfo groupInfo : groupList) {
for (IMSessionBean imSession : list) {
if (TextUtils.equals(groupInfo.groupId, imSession.getSessionId())) {
groupInfo.unreadCount = imSession.getUnreadCount();
break;
}
}
}
emitter.onNext(groupList);
}
}).compose(InjectionUtils.schedulersTransformer())
.subscribe(new Consumer>() {
@Override
public void accept(List groupList) throws Exception {
if (groupList != null && groupList.size() > 0) {
refreshInfoListener.onSuccess(groupList);
}
}
});
}
(6)学习链接
快速切换到主线程更新UI的几种方法
(1)编辑app目录下的build.gradle文件,为其添加以下代码
android {
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
// 或者:
sourceSets.main {
jniLibs.srcDirs = ['libs']
jni.srcDirs = []
}
}
(1)组合逗号String
ArrayList collectIds = new ArrayList<>();
String str = collectIds.toString();
int len = str.length() - 1;
String ids = str.substring(1, len).replace(" ", "");//"keyids":”1,2,3”
(2)根据逗号分隔String
String str="112,123,123,123";//根据逗号分隔到List数组中
String str2=str.replace(" ", "")//去掉所用空格
List list= Arrays.asList(str2.split(","))
//list的结果就是[113,123,123,123]
(1)api指令
api指令完全等同于compile指令。
(2)implementation指令
这个指令的特点就是,对于使用了该命令编译的依赖,对该项目有依赖的项目将无法访问到使用该命令编译的依赖中的任何程序,也就是将该依赖隐藏在内部,而不对外部公开。
(3)主要区别
implementation不可以依赖传递,但是compile可以依赖传递。 谁长(单词多堪称人多)谁(排队)检查,(为了)安全才复杂。
(3)文不如图
备注:app implementation module1 ,module1 compile module2,app依然可以访问到module2的api。
(4)建议
在Google IO 相关话题的中提到了一个建议,就是依赖首先应该设置为implementation,如果没有错,那就用implementation;如果有错,那么使用api指令,这样会使编译速度有所增快。
(5)学习链接
implementation 和 compile区别
android gradle tools 3.X 中依赖,implementation 和compile区别
(1)将aar文件拷入app module下的libs文件夹下;
(2)在app的build.gradle文件中配置如下:
android {
}
repositories {
flatDir {
dirs 'libs'
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
// 在dependencies添加依赖并同步即可
implementation (name:'torch-adcore-5.4.3105',ext:'aar')
}
(3)注:repositories和android、dependencies处在同一级别的目录下。如不添加该配置,同步依赖会失败,提示 Failed to resolve。
(1)将aar文件拷入想要依赖的library module下的libs文件夹中;
(2)在module或project中声明aar文件路径:
以此例子: app implementation module1 ,module1 compile module2
// 方式1:在module1的build.gradle文件中配置如下:
repositories {
flatDir {
dirs 'libs'
}
}
// 在app的build.gradle文件中配置如下:
repositories {
flatDir {
dirs '../xxx/libs','libs' //将xxx替换为引入aar文件的module名
}
}
// 方式2:在app的build.gradle文件中统一配置如下:
allprojects {
repositories
flatDir {
dirs project(':xxx').file('libs') // 将xxx替换为引入aar文件的module名
}
}
}
(3)在依赖aar文件的library module下的build.gradle文件中添加依赖如下:
// 在module1中
api (name:'xxx',ext:'aar')
// 在app中
implementation(name: 'xxx', ext: 'aar')
(4)批量导入libs里的所有.aar类库
fileTree(dir: 'libs', include: '**/*.aar')
.each { File file ->
dependencies.add("api", [
name: file.name.lastIndexOf('.').with { it != -1 ? file.name[0..
(5)举例子
// 在adsdklib的build.gradle文件中
// 文件的指向
repositories{
flatDir{
dirs 'libs'
}
}
// 批量导入libs里的所有.aar类库
fileTree(dir: 'libs', include: '**/*.aar')
.each { File file ->
dependencies.add("api", [
name: file.name.lastIndexOf('.').with { it != -1 ? file.name[0..
// 在app的build.gradle文件中
repositories{
flatDir{
dirs '../adsdklib/libs'
// dirs project(':adsdklib').file('libs') // 等价于
}
}
(6)注:所有直接或间接依赖该library module的module中都应声明该aar文件所在libs目录的相对路径或在project的build.gradle文件中进行统一配置声明。否则会同步依赖失败,提示 Failed to resolve。
(7)学习链接
在Android Studio的不同Module中依赖aar的配置问题
android studio library 模块中正确引用aar
(2)在Activity间传数据,我们一般有两种方式:
// 1、直接用Intent的putExtra(), getStringExtra();
// 2、先new一个Bundle对象,用Bundle的putExtra().
(2)先看一下Intent对象相应函数的源代码:
public Intent putExtra(String name, String value) {
if (mExtras == null) {
mExtras = new Bundle();
}
mExtras.putString(name, value);
return this;
}
public String getStringExtra(String name) {
return mExtras == null ? null : mExtras.getString(name);
}
(3)Intent的相关操作的实现是基于Bundle的,Bundle操作数据相对于Intent有哪些优势呢?
如果要从ActivityA跳转到ActivityB和ActivityC,那么我要写两个Intent,如果传的数据相同,我两个Intent都要添加,
但是如果我用Bundle,直接一次性包装就可以了。
再有,如果ActivityA要传值给ActivityB,再从ActivityB传值到ActivityC,用Intent是多么的麻烦了,
但是用Bundle就很简洁,而且还可以在Activityb添加新的信息。
(1)问题描述:有一个需求,需要在网页中实现下拉刷新功能,由于两个控件都有滑动的事件,在向下滑动的时候滑动事件被SwipeRefreshLayout控件消费了,但是Webview本身是不滑动的,而是H5在滑动。可以通过桥获取H5传递的滑动距离,对SwipeRefreshLayout是否允许下拉刷新进行控制。
(2)具体内容
public class MyWebView extends WebView {
public MyWebView(Context context) {
super(context);
}
public MyWebView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
}
public MyWebView(Context context, AttributeSet attributeSet, int i) {
super(context, attributeSet, i);
}
/**
* 设置WebView滚动高度ScrollY
*/
public void setWebViewScrollY(int scrollY) {
if (mScrollListener != null) {
mScrollListener.onScrollY(scrollY);
}
}
public interface IScrollListener {
void onScrollY(int scrollY);;
}
private IScrollListener mScrollListener;
public void setOnScrollListener(IScrollListener listener) {
mScrollListener = listener;
}
}
mWebView.setOnScrollChangedListener(new NovelWebView.OnScrollChangedListener() {
@Override
public void onScrollY(int scrollY) {
if (scrollY == 0) {
mSwipeRefreshLayout.setEnabled(true);
} else {
mSwipeRefreshLayout.setEnabled(false);
}
}
});
public class WebViewInterface {
/**
* h5页面传递客户端滚动高度
*/
public static void setScrollY(WebView view, String scrollY) {
if (view == null) {
return;
}
((NovelWebView) view).setWebViewScrollY(getIntForString(scrollY));
}
/**
* 从字符串中获取数字
* @param strInput
* @return
*/
public static int getIntForString(String strInput) {
try {
// 匹配指定范围内的数字
String regEx = "[^0-9]";
// Pattern是一个正则表达式经编译后的表现模式
Pattern p = Pattern.compile(regEx);
// 一个Matcher对象是一个状态机器,它依据Pattern对象做为匹配模式对字符串展开匹配检查。
Matcher m = p.matcher(strInput);
// 将输入的字符串中非数字部分用空格取代并存入一个字符串
String string = m.replaceAll(" ").trim();
// 以空格为分割符在讲数字存入一个字符串数组中
String[] strArr = string.split(" ");
if (strArr.length > 0) {
return Integer.valueOf(strArr[0]);
}
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
}
(3)参考链接(但是本方法存在问题)
Android SwipeRefreshLayout嵌套Webview滑动冲突问题解决
(1)Snackbar是Android Support Design Library库中的一个控件,可以在屏幕底部快速弹出消息,比Toast更加好用。本文对原生Snackbar进行了修改,使其更加灵活。
(2)Android Snackbar花式使用指南
(1)shrinkResources true作用
在应用构建打包的过程中自动删除没有引用的资源,对依赖的库同样有效。shrinkResources必须和minifyEnabled配合使用达到减少包体积的作用,只有删除了无用的代码之后,才能知道哪些资源是无用的。
(2)保持资源
和混淆可以配置keep类似,资源同样也可以通过tools:keep来指定要保留的资源文件,通常通过res/raw/keep.xml指定要保持的资源,
同样可以通过 tools:discard 来从保持的资源中指定要删除的资源,如
tools:discard="@layout/unused2"
(3)解决问题方法:为了指定SDK中的图片资源不被删除,可以配置res/raw/keep.xml文件来指定要保持的资源文件。
(4)学习链接
官方文档-resource-shrinking
shrinkResources true引发的问题及解决
(1)类似下面的一个底部tab选项卡布局。中间的是凸出来一点。类似这样:
(2)实现方法
// 用layout_gravity来控制超出的部分显示位置
// 在根布局节点设置clipChildren=false,默认为true:是否限制子视图在其范围内
(3)注意
在merge标签中设置android:clipChildren="false"是无效的,必须在有效根布局中设置。
(4)学习链接
关于xml中clipChildren属性的用法
(1)src是图片内容(前景);background是背景;
(2)src就存放的是原图的大小,不会进行拉伸;background会根据ImageView组件给定的长宽进行拉伸;
(3)scaleType只对src起作用;background可设置透明度;
(4)通过代码和背景设置图片
// src
android:src="@drawable/logo"
iv.setImageResource(R.drawable.);
// background
android:background="@drawable/logo"
iv.setBackgroundResource(R.drawable.);
(5)getDrawable()与getBackground()
protected void onDraw(Canvas canvas) {
// src:getDrawable()
// background: getBackground()
}
(5)学习链接
Android src和background的区别
(1)attr_widget.xml
(2)RoundAngleFrameLayout.java
public class RoundAngleFrameLayout extends FrameLayout {
private float mTopLeftRadius = 0;
private float mTopRightRadius = 0;
private float mBottomLeftRadius = 0;
private float mBottomRightRadius = 0;
private int mRatioWidth = 0;
private int mRatioHeight = 0;
private Paint mRoundPaint;
private Paint mImagePaint;
public RoundAngleFrameLayout(Context context) {
this(context, null);
}
public RoundAngleFrameLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RoundAngleFrameLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
if (attrs != null) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.RoundAngleFrameLayout);
float radius = ta.getDimension(R.styleable.RoundAngleFrameLayout_round_radius, 0);
mTopLeftRadius = ta.getDimension(R.styleable.RoundAngleFrameLayout_topLeftRadius, radius);
mTopRightRadius = ta.getDimension(R.styleable.RoundAngleFrameLayout_topRightRadius, radius);
mBottomLeftRadius = ta.getDimension(R.styleable.RoundAngleFrameLayout_bottomLeftRadius, radius);
mBottomRightRadius = ta.getDimension(R.styleable.RoundAngleFrameLayout_bottomRightRadius, radius);
mRatioWidth = ta.getInt(R.styleable.RoundAngleFrameLayout_ratioWidth, 0);
mRatioHeight = ta.getInt(R.styleable.RoundAngleFrameLayout_ratioHeight, 0);
ta.recycle();
}
mRoundPaint = new Paint();
mRoundPaint.setColor(Color.WHITE);
mRoundPaint.setAntiAlias(true);
mRoundPaint.setStyle(Paint.Style.FILL);
mRoundPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
mImagePaint = new Paint();
mImagePaint.setXfermode(null);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mRatioWidth > 0 && mRatioHeight > 0) {
int originalWidth = View.MeasureSpec.getSize(widthMeasureSpec);
int calculatedHeight = originalWidth * mRatioHeight / mRatioWidth;
super.onMeasure(View.MeasureSpec.makeMeasureSpec(originalWidth, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(calculatedHeight, View.MeasureSpec.EXACTLY));
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
@Override
protected void dispatchDraw(Canvas canvas) {
canvas.saveLayer(new RectF(0, 0, canvas.getWidth(), canvas.getHeight()), mImagePaint, Canvas.ALL_SAVE_FLAG);
super.dispatchDraw(canvas);
drawTopLeft(canvas);
drawTopRight(canvas);
drawBottomLeft(canvas);
drawBottomRight(canvas);
canvas.restore();
}
private void drawTopLeft(Canvas canvas) {
if (mTopLeftRadius > 0) {
Path path = new Path();
path.moveTo(0, mTopLeftRadius);
path.lineTo(0, 0);
path.lineTo(mTopLeftRadius, 0);
path.arcTo(new RectF(0, 0, mTopLeftRadius * 2, mTopLeftRadius * 2), -90, -90);
path.close();
canvas.drawPath(path, mRoundPaint);
}
}
private void drawTopRight(Canvas canvas) {
if (mTopRightRadius > 0) {
int width = getWidth();
Path path = new Path();
path.moveTo(width - mTopRightRadius, 0);
path.lineTo(width, 0);
path.lineTo(width, mTopRightRadius);
path.arcTo(new RectF(width - 2 * mTopRightRadius, 0, width, mTopRightRadius * 2), 0, -90);
path.close();
canvas.drawPath(path, mRoundPaint);
}
}
private void drawBottomLeft(Canvas canvas) {
if (mBottomLeftRadius > 0) {
int height = getHeight();
Path path = new Path();
path.moveTo(0, height - mBottomLeftRadius);
path.lineTo(0, height);
path.lineTo(mBottomLeftRadius, height);
path.arcTo(new RectF(0, height - 2 * mBottomLeftRadius, mBottomLeftRadius * 2, height), 90, 90);
path.close();
canvas.drawPath(path, mRoundPaint);
}
}
private void drawBottomRight(Canvas canvas) {
if (mBottomRightRadius > 0) {
int height = getHeight();
int width = getWidth();
Path path = new Path();
path.moveTo(width - mBottomRightRadius, height);
path.lineTo(width, height);
path.lineTo(width, height - mBottomRightRadius);
path.arcTo(new RectF(width - 2 * mBottomRightRadius, height - 2 * mBottomRightRadius, width, height), 0, 90);
path.close();
canvas.drawPath(path, mRoundPaint);
}
}
}
(3)布局使用
(4)效果
(5)RoundAngleFrameLayout设置的前景,所以不可以直接设置背景,所以以下不生效
(1)控件参考:Android 圆角、圆形 ImageView 实现
(2)项目中的控件:RoundRectImageView
(1)工具类
public class RoundBitmapFillet {
// 圆角类型
@IntDef({TYPE.ALL, TYPE.TOP, TYPE.LEFT, TYPE.RIGHT, TYPE.BOTTOM})
@Retention(RetentionPolicy.SOURCE)
public @interface TYPE {
int ALL = 0;
int TOP = 1;
int LEFT = 2;
int RIGHT = 3;
int BOTTOM = 4;
}
/**
* 指定图片的切边,对图片进行圆角处理
*
* @param bitmap
* @param type
* @param radius
* @param width
* @param height
* @return
*/
public static Bitmap filletRoundBitmap(Bitmap bitmap, @TYPE int type, int radius, int width, int height) {
try {
// (1)先建立一个与图片大小相同的透明的Bitmap画板
Bitmap paintingBoard = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(paintingBoard);
// (2)在画板上画出一个想要的形状的区域
final Paint paint = new Paint();
if (TYPE.TOP == type) {
clipTop(canvas, paint, radius, width, height);
} else if (TYPE.LEFT == type) {
clipLeft(canvas, paint, radius, width, height);
} else if (TYPE.RIGHT == type) {
clipRight(canvas, paint, radius, width, height);
} else if (TYPE.BOTTOM == type) {
clipBottom(canvas, paint, radius, width, height);
} else {
clipAll(canvas, paint, radius, width, height);
}
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
// (3)把源图片帖上
Rect src = new Rect(0, 0, width, height);
canvas.drawBitmap(bitmap, src, src, paint);
return paintingBoard;
} catch (Exception exp) {
return bitmap;
}
}
private static void clipLeft(final Canvas canvas, final Paint paint, int offset, int width, int height) {
final Rect block = new Rect(offset, 0, width, height);
canvas.drawRect(block, paint);
final RectF rectF = new RectF(0, 0, offset * 2, height);
canvas.drawRoundRect(rectF, offset, offset, paint);
}
private static void clipRight(final Canvas canvas, final Paint paint, int offset, int width, int height) {
final Rect block = new Rect(0, 0, width - offset, height);
canvas.drawRect(block, paint);
final RectF rectF = new RectF(width - offset * 2, 0, width, height);
canvas.drawRoundRect(rectF, offset, offset, paint);
}
private static void clipTop(final Canvas canvas, final Paint paint, int offset, int width, int height) {
final Rect block = new Rect(0, offset, width, height);
canvas.drawRect(block, paint);
final RectF rectF = new RectF(0, 0, width, offset * 2);
canvas.drawRoundRect(rectF, offset, offset, paint);
}
private static void clipBottom(final Canvas canvas, final Paint paint, int offset, int width, int height) {
final Rect block = new Rect(0, 0, width, height - offset);
canvas.drawRect(block, paint);
final RectF rectF = new RectF(0, height - offset * 2, width, height);
canvas.drawRoundRect(rectF, offset, offset, paint);
}
private static void clipAll(final Canvas canvas, final Paint paint, int offset, int width, int height) {
final RectF rectF = new RectF(0, 0, width, height);
canvas.drawRoundRect(rectF, offset, offset, paint);
}
}
(2)使用方法
Bitmap bmOutput = RoundBitmapFillet.filletRoundBitmap(bmInput, RoundBitmapFillet.TYPE.ALL, mRadius, mDesWidth, mDesHeight);
(3)效果
(4)学习链接
【Android】图片切角,切指定的边。
Android利用canvas画各种图形(点、直线、弧、圆、椭圆、文字、矩形、多边形、曲线、圆角矩形)
(1)在布局中添加上Switch控件
// 以下是该控件的常用属性:
textOn:控件打开时显示的文字
textOff:控件关闭时显示的文字
thumb:控件开关的图片
track:控件开关的轨迹图片
typeface:设置字体类型
switchMinWidth:开关最小宽度
switchPadding:设置开关 与文字的空白距离
switchTextAppearance:设置文本的风格
checked:设置初始选中状态
splitTrack:是否设置一个间隙,让滑块与底部图片分隔(API 21及以上)
showText:设置是否显示开关上的文字(API 21及以上)
一般不会用该控件原本的样式,那么我们就需要自己修改样式了:
(2)资源文件相关
bg_thumb_on.xml
bg_thumb_off.xml
bg_track_on.xml
bg_track_off.xml
selector_switch_thumb.xml
selector_switch_track.xml
(3)MainActivity.class
public class MainActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final Switch aSwitch = (Switch) findViewById(R.id.s_v);
aSwitch.setChecked(false);
aSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
// 控制开关字体颜色
if (isChecked) { } else { }
}
});
}
}
(4)效果
(5)学习链接
Android UI控件Switch的使用方法
@Override
public int getItemPosition(@NonNull Object object) {
if (mBannerList != null && mBannerList.size() == 1) {
// 参考:https://www.jianshu.com/p/266861496508
// 解决只有一页时 notifyDataSetChanged() 页面不刷新问题的方法
return POSITION_NONE;
} else {
return super.getItemPosition(object);
}
}
ViewPager刷新问题详解
Android官网解析:PagerAdapter
/**
* 创建指定位置的页面视图
*/
@Override
public Object instantiateItem(@NonNull ViewGroup container, final int position) {
}
/**
* 销毁一个给定位置的页面
*/
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
}
/**
* 获取当前界面的ViewGroup
*/
@Override
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
}
ViewPager中获取当前界面的Fragment
ViewPager动态加载数据
(1)先通过/res/values/dimens.xml自定义数值,了解一下尺寸(Dimension)的不同用法:
16px
16dp
16sp
(4)学习链接
Android之尺寸getDimension、getDimensionPixelOffset 和 getDimensionPixelSize
(1)定义
User Agent中文名为用户代理,是Http协议中的一部分,属于头域的组成部分,User Agent也简称UA。
(2)是什么
它是一个特殊字符串头,是一种向访问网站提供你所使用的移动设备或者浏览器类型及版本、操作系统及版本、浏览器内核、等信息的标识。
(3)什么用
通过这个标识,用户所访问的网站可以显示不同的排版从而为用户提供更好的体验或者进行信息统计;例如用手机访问谷歌和电脑访问是不一样的,这些是谷歌根据访问者的UA来判断的。
(4)UserAgent的内容
User Agent:Mozilla/5.0 (Linux; Android 4.4.4; SAMSUNG-SM-N900A Build/tt) AppleWebKit/537.36 (KHTML, like Gecko)
Version/4.0 Chrome/33.0.0.0 Mobile Safari/537.36
(5)使用示例
String userAgent = DeviceUtils.getUserAgent();
if (TextUtils.isEmpty(userAgent)) {
// 考虑到初始化WebView耗时,最后才使用WebView
userAgent = new WebView(ReaderApplication.getGlobalContext()).getSettings().getUserAgentString();
}
/**
* 获取UserAgent
* @return
*/
public static String getUserAgent() {
String userAgent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
try {
// 注意:WebSettings使用系统默认的api
userAgent = WebSettings.getDefaultUserAgent(BaseApplication.getContext());
} catch (Exception e) {
userAgent = System.getProperty("http.agent");
}
} else {
userAgent = System.getProperty("http.agent");
}
if (TextUtils.isEmpty(userAgent)) {
return "";
}
StringBuilder sb = new StringBuilder();
for (int i = 0, length = userAgent.length(); i < length; i++) {
char c = userAgent.charAt(i);
if (c <= '\u001f' || c >= '\u007f') {
sb.append(String.format("\\u%04x", (int) c));
} else {
sb.append(c);
}
}
return sb.toString();
}
(6)学习链接
Android获取以及修改WebView的UserAgent
获取android默认的useragent
/**
* 获取MAC地址
* @param context
* @return
*/
public static String getMacAddress(Context context) {
String mac;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
mac = getMacDefault(context);
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
mac = getMacAddressM();
} else {
mac = getMacFromHardware();
}
return mac;
}
/**
* Android 6.0 之前(不包括6.0)
* 必须的权限
* @param context
* @return
*/
private static String getMacDefault(Context context) {
String mac = "02:00:00:00:00:00";
if (context == null) {
return mac;
}
WifiManager wifi = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
if (wifi == null) {
return mac;
}
WifiInfo info = null;
try {
info = wifi.getConnectionInfo();
} catch (Exception e) {
e.printStackTrace();
}
if (info == null) {
return mac;
}
mac = info.getMacAddress();
if (!TextUtils.isEmpty(mac)) {
mac = mac.toUpperCase(Locale.ENGLISH);
}
return mac;
}
/**
* Android 6.0(包括) - Android 7.0(不包括)
* @return
*/
private static String getMacAddressM() {
String wifiaddress = "02:00:00:00:00:00";
try {
wifiaddress = new BufferedReader(new FileReader(new File("/sys/class/net/wlan0/address"))).readLine();
} catch (IOException e) {
e.printStackTrace();
}
return wifiaddress;
}
/**
* 遍历循环所有的网络接口,找到接口是 wlan0
* 必须的权限
* @return
*/
private static String getMacFromHardware() {
try {
List all = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface nif : all) {
if (!nif.getName().equalsIgnoreCase("wlan0")) {
continue;
}
byte[] macBytes = nif.getHardwareAddress();
if (macBytes == null) {
return "";
}
StringBuilder res1 = new StringBuilder();
for (byte b : macBytes) {
res1.append(String.format("%02X:", b));
}
if (res1.length() > 0) {
res1.deleteCharAt(res1.length() - 1);
}
return res1.toString();
}
} catch (Exception e) {
e.printStackTrace();
}
return "02:00:00:00:00:00";
}
Android stdio 3.1 Messages窗口取消啦
(1)Make Project:编译Project下所有Module,一般是自上次编译后Project下有更新的文件,增量编译,不生成Apk。
(2)Make Selected Modules:编译指定的Module,一般是自上次编译后Module下有更新的文件,增量编译,不生成Apk。
(3)Clean Project:删除之前编译后的编译文件。部分版本的AS会自动重新编译整个Project,不生成Apk。
(4)Rebuild Project:先执行Clean操作,删除之前编译的编译文件和可执行文件,然后重新编译新的编译文件,不生成Apk。
(5)Build APK:前面4个选项都是编译,没有生成apk文件[旧版本AS,新版本的AS会自动生成debug.apk],如果想生成Apk,需要点击Build APK。
(6)Generate Signed APK:生成有签名的Apk(一般项目嵌入第三方,生成release包时必须混淆,否则无法生成Apk)。
(1)buildScript块的repositories主要是用来加载gradle脚本自身需要使用的资源插件。
比如在buildscript下dependencies依赖classpath ‘com.android.tools.build:gradle:2.2.2’,那需要在repositories中配置依赖jcenter()。
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.2'
}
}
(2)allprojects块的repositories是项目本身需要的依赖。
比如要依赖友盟maven库的com.umeng.umsdk:analytics:8.0.0库,那需要将maven { url ‘https://dl.bintray.com/umsdk/release’ }写在allprojects下,而不是buildscript中,不然找不到。
allprojects {
repositories {
google()
jcenter()
mavenCentral()
// 添加 Sensors Analytics maven 库地址
maven { url 'https://dl.bintray.com/zouyuhan/maven' }
// 友盟
maven { url 'https://dl.bintray.com/umsdk/release' }
}
// app
// 添加 com.sensorsdata.analytics.android 插件
apply plugin: 'com.sensorsdata.analytics.android'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// 友盟
implementation 'com.umeng.umsdk:analytics:8.0.0'
implementation 'com.umeng.umsdk:common:2.0.0'
}
(3)根级别的repositories主要是为了当前项目提供所需依赖包,比如log4j、spring-core等依赖包可从mavenCentral仓库获得。
(4)学习链接
buildscript和allprojects的作用和区别是什么?
(1)集成步骤
使用 Jenkins 与 Sonar 集成对代码进行持续检测
(2)
(1)步骤
(2)学习链接
Android Studio生成keystore签名文件
(1).keystore是Eclipse打包生成的签名;而.jks是Android studio生成的签名。都是用来打包的,并保证应用的唯一性!这就是他们的最大的区别!
(2)备注:很多第三方市场,我们上传apk的时候,他们只支持keystore,需要我们把.jks签名转化为.keystore!
(1)方法一:在文件夹中找到这个签名文件(.keystore或.jks),打开命令行窗口,执行
keytool -list -v -keystore debug.keystore
(2)方法二:如果电脑的命令配置不生效,直接进入java jdk的目录下,并将签名文件(.keystore或.jks)拷贝到此目录下,再执行命令。
(3)学习链接
Android获取签名文件sha1值
(1)你需要检查你的新旧apk所使用的签名文件是否是同一个。
(2)检查你的签名文件是否是发布版本,debug签名的应用程序不能在Android Market上发布,它会强制你使用自己的签名,debug.keystore在不同的机器上所生成的可能都不一样,就意味着如果你换了机器进行 apk 版本升级,那么将会出现上面那种程序不能覆盖安装的问题。相当于软件不具备升级功能,所以一定要使用正式发布版本的签名。
(3)检查清单文件中的两个属性:versionCode和versionName,发布新版本的时候会有可能会忘记修改这两项。如果没有修改会导致软件发布后用户无法接收到更新提示,也就影响软件的更新率。
Android 解决apk覆盖安装的时候,出现安装失败,与旧版本部兼容的问题
(1)signature version的区别
V1:可对签名后的文件,作适当修改,并重新压缩。
V2:不能对签名后的 APK作任何修改,包括重新解压。因为它是针对字节进行的签名,所以任何改动都会影响最终结果。
(2)v1和v2的签名正确用法
1:只勾选v1签名所有机型都能用,但是在7.0及以上不会使用更安全的验证方式;
2:只勾选V2签名7.0以下机型会在直接安装完后显示未安装,7.0及以上机型使用V2的方式验证成功安装;
3:同时勾选V1和V2对所有机型成功安装(建议使用).
(4)学习链接
Andriod Studio两种签名机制V1和V2的区别
Android V1、V2签名包和快速集成美团多渠道打包
jarsigner -verbose -keystore D:\project\xiaoshuting\keystore\haosou.keystore -signedjar C:\Users\Administrator\Desktop\sign\signed.apk C:\Users\Administrator\Desktop\com.xiaomi.appstore.signature.verification.apk qihoo
(1)在网络请求回调/异步回调更新UI/定时操作更新UI,通过(!ActivityUtils.assertActivityDestroyed(this))或者(getMvpView() != null)判断当前Activity是否被销毁,再做逻辑处理或者回调。(2018/7/24)
/**
* 弱引用Activity,判断Activity是否已经销毁
*
* @param activity
* @return true=Activity已经销毁,false=没有销毁
*/
public static boolean assertActivityDestroyed(Activity activity) {
if (activity == null) {
return true;
}
WeakReference weakReference = new WeakReference<>(activity);
return isActivityFinished(weakReference.get());
}
/**
* 直接判断Activity是否已经销毁
*
* @param activity
* @return true=Activity已经销毁,false=没有销毁
*/
public static boolean isActivityFinished(Activity activity) {
return activity == null || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && activity.isDestroyed()) || activity.isFinishing();
}
UCManager.getInstance().getAssetsInfoManager().fetchWalletInfo(new IGetMyWalletListener() {
@Override
public void onSuccess(WalletInfo walletInfo) {
if (ActivityUtils.assertActivityDestroyed(RechargeManagerActivity.this)) {
return;
}
}
@Override
public void onFail(String msg) {
if (ActivityUtils.assertActivityDestroyed(RechargeManagerActivity.this)) {
return;
}
ToastUtils.showToast(RechargeManagerActivity.this, R.string.recharge_get_assets_error);
}
});
(1)killProcess结束进程
android.os.Process.killProcess(android.os.Process.myPid());
android中所有的activity都在主进程中,在Androidmanifest.xml中可以设置成启动不同进程,Service不是一个单独的进程也不是一个线程。当你Kill掉当前程序的进程时也就是说整个程序的所有线程都会结束,Service也会停止,整个程序完全退出。
(2)exit结束java虚拟机
System.exit(0);
用来结束当前正在运行中的java虚拟机。如何status是非零参数,那么表示是非正常退出。当调用System.exit(0)时,杀死了整个进程,这时候活动所占的资源也会被释放。和return 相比有以下不同点:return是回到上一层,而System.exit(status)是回到最上层。进一步探索System.exit被调用后的行为,它在虚拟机在退出前会执行两个清除任务。第一,它会执行所有通过Runtime.addShutdownHook注册的shutdownhooks.它能有效的释放JVM之外的资源。第二,执行清除任务,运行相关的finalizer方法终结对象。在android手机中查看当前正在运行的进程时可以发现还可以查看"后台缓存的进程",你会发现很多退出了的程序还在后台缓存的进程中,如果不要让程序在后台缓存那么就可以用System.exit(0);来退出程序了,可以清除后台缓存的本进程,对于有些手机也是没有用的。这是退出Java程序的方法,也可以用来退出Android。
(3)根据Activity的生命周期,例如:从A->B->C->D,这时需要从D直接退出程序。
// 在D中使用下面的代码:
Intent intent = new Intent();
intent.setClass(D.this, A.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // 注意本行的FLAG设置
startActivity(intent);
finish(); // 关掉自己
// 在A中加入代码:
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
// 退出
if ((Intent.FLAG_ACTIVITY_CLEAR_TOP & intent.getFlags()) != 0) {
finish();
}
}
A的Manifest.xml配置成android:launchMode="singleTop"
总结:一般A是程序的入口点,从D起一个A的activity,加入标识Intent.FLAG_ACTIVITY_CLEAR_TOP这个过程中会把栈中B,C都清理掉。因为A是android:launchMode=“singleTop” ,不会调用onCreate(),而是响应onNewIntent()。这时候判断Intent.FLAG_ACTIVITY_CLEAR_TOP,然后把A finish()掉。 栈中A,B,C,D全部被清理,所以整个程序退出了。
学习链接:采用FLAG_ACTIVITY_CLEAR_TOP退出整个程序(多activity)
(4)finish()将活动推向后台
finish();
finish是Activity的类,仅仅针对Activity,当调用finish()时,只是将活动推向后台,并没有立即释放内存,活动的资源并没有被清理;
(5)比较常用的方法,使用Activity栈管理Activity
public class ActivityManager {
private static ActivityManager sInstance;
private Stack mActivityStack;
/**
* 单例
*
* @return
*/
public static ActivityManager getAppManager() {
if (sInstance == null) {
synchronized (ActivityManager.class) {
if (sInstance == null) {
sInstance = new ActivityManager();
}
}
}
return sInstance;
}
/**
* 添加Activity到堆栈
* @param activity
*/
public void addActivity(Activity activity) {
if (mActivityStack == null) {
mActivityStack = new Stack<>();
}
mActivityStack.add(activity);
}
/**
* 获取当前Activity(堆栈中最后一个压入的)
* @return
*/
public Activity currentActivity() {
if (mActivityStack == null) {
return null;
}
return mActivityStack.lastElement();
}
/**
* 结束指定的Activity
* @param activity
*/
public void finishActivity(Activity activity) {
if (mActivityStack == null || activity == null) {
return;
}
mActivityStack.remove(activity);
activity.finish();
}
/**
* 结束当前Activity(堆栈中最后一个压入的)
*/
public void finishActivity() {
if (mActivityStack == null) {
return;
}
// 获取到当前Activity
Activity activity = mActivityStack.lastElement();
// 结束指定Activity
finishActivity(activity);
}
/**
* 结束指定类名的Activity
* @param cls
*/
public void finishActivity(Class> cls) {
if (mActivityStack == null) {
return;
}
List activities = new ArrayList<>();
for (Activity activity : mActivityStack) {
if (activity.getClass().equals(cls)) {
// finishActivity(activity);
activities.add(activity);
}
}
// 结束所有类名相同activity
mActivityStack.removeAll(activities);
for (Activity activity : activities) {
finishActivity(activity);
}
}
/**
* 结束所有Activity
*/
public void finishAllActivity() {
if (mActivityStack == null) {
return;
}
for (int i = 0, size = mActivityStack.size(); i < size; i++) {
if (null != mActivityStack.get(i)) {
Activity activity = mActivityStack.get(i);
if (!activity.isFinishing()) {
activity.finish();
}
}
}
mActivityStack.clear();
}
/**
* 退出应用程序
* 这里关闭的是所有的Activity,没有关闭Activity之外的其他组件;
*
* android.os.Process.killProcess(android.os.Process.myPid())
* 杀死进程关闭了整个应用的所有资源,有时候是不合理的,通常是用堆栈管理Activity;
*
* System.exit(0)杀死了整个进程,这时候活动所占的资源也会被释放,它会执行所有通过Runtime.addShutdownHook注册的shutdown hooks.
* 它能有效的释放JVM之外的资源,执行清除任务,运行相关的finalizer方法终结对象,
*
* 而finish只是退出了Activity。
*/
public void AppExit() {
try {
finishAllActivity();
// DalvikVM的本地方法
// 杀死该应用进程:android.os.Process.killProcess(android.os.Process.myPid());
// System.exit(0):这些方法如果是放到主Activity就可以退出应用,如果不是主Activity就是退出当前的Activity
} catch (Exception e) {
e.printStackTrace();
}
}
}
(6)学习链接
Android 关闭应用程序的6种方法
(1)在底部控件设置bottom—>部分手机不生
(2)在父布局设置paddingBottom—>兼容所有手机
(1)设置主题XstSplashTheme
(2)XstSplashTheme
// values\xst_xtyles.xml
// v21\xst_xtyles.xml
// v28\xst_xtyles.xml
(3)splash_bg.xml:使用layer-list叠加的方式–学习shape和selector和layer-list的使用
-
(4)SplashActivity.java设置全屏显示+设置适配刘海屏全屏展示,主要是:华为小米等厂商的8.1刘海屏
(5)activity_splash.xml布局(使用logo图适配,不要使用带图案的全屏图)
(1)概念理解-layout属性
用于根据系统窗口(如状态栏)调整视图布局。 如果为true,保留actionbar,title,屏幕的底部虚拟按键,为系统窗口留出空间。如果为false,移除actionbar,title,屏幕的底部虚拟按键,将整个屏幕当作可用的空间。 仅在此视图处于非嵌入活动中时才会生效。
(2)android:fitsSystemWindows=“true”
(3)android:fitsSystemWindows=“false”
(4)小结:页面实现沉浸式状态栏(即状态栏隐藏),需要设置android:fitsSystemWindows=“false”;默认的页面则是android:fitsSystemWindows=“true”。
(5)学习链接:fitsSystemWindow作用
(1)开源库地址
niorgai/StatusBarCompat
(2)使用
allprojects {
repositories {
...
maven { url "https://jitpack.io" }
}
}
dependencies {
// After AndroidX
implementation ('com.github.niorgai:StatusBarCompat:2.3.0', {
exclude group: 'androidx.appcompat:appcompat'
exclude group: 'com.google.android.material:material'
})
// Before AndroidX
compile ('com.github.niorgai:StatusBarCompat:2.1.4', {
exclude group: 'com.android.support'
})
}
// 在BaseActivity设置,适配所有Activity
public abstract class BaseActivity extends SuperFragmentActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutId());
if (isShowStatusBar()) {
StatusBarCompat.setStatusBarColor(this, ContextCompat.getColor(this, R.color.global_theme_red));
}
}
protected boolean isShowStatusBar() {
return true;
}
}
// 在需要不设置默认颜色的子Activity,重写isShowStatusBar(),返回false
public class SplashActivity extends BaseActivity {
@Override
protected boolean isShowStatusBar() {
return false;
}
}
(1)对于AppCompat主题中,各个颜色属性的含义,可以参考下图
(2)在布局父布局中设置:android:fitsSystemWindows=“false”
(3)在Activity的super.onCreate(savedInstanceState)后设置setWindowStatus
public class WindowUtils {
/**
* 设置Window实现沉浸式状态栏
*/
public static void setWindowStatus(Window window) {
if (window == null) {
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(Color.TRANSPARENT);
}
}
}
public class MainActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
WindowUtils.setWindowStatus(getWindow());
initView();
}
}
(4)实现效果
(5)学习链接
Android沉浸式状态栏、变色状态栏、透明状态栏、修改状态栏颜色及透明
Android状态栏微技巧,带你真正理解沉浸式模式
(1)工具类
/**
* 状态栏工具类
*/
public class StatusBarUtils {
/**
* 设置ActionBar状态
* @param context
* @param layout
* @param isSetPadding
*/
public static void setActionBarParams(Context context, View layout, boolean isSetPadding) {
int statusBarHeight = (int) DeviceUtils.getStatusBarHeight(context);
ViewGroup.LayoutParams params = layout.getLayoutParams();
if (isSetPadding) {
// 设置内边距-状态栏高度
layout.setPadding(0, statusBarHeight, 0, 0);
}
// 设置ActionBar高度为原高度+状态栏高度
params.height += statusBarHeight;
layout.setLayoutParams(params);
}
/**
* 设置边距Padding
* @param view
* @param left
* @param top
* @param right
* @param bottom
*/
public static void setPaddingParams(View view, int left, int top, int right, int bottom) {
if (view != null) {
ViewGroup.LayoutParams params = view.getLayoutParams();
view.setPadding(left, top, right, bottom);
view.setLayoutParams(params);
}
}
/**
* 设置边距Margin
* @param view
* @param left
* @param top
* @param right
* @param bottom
*/
public static void setMarginsParams(View view, int left, int top, int right, int bottom) {
if (view != null && view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
p.setMargins(left, top, right, bottom);
view.requestLayout();
}
}
}
(2)在52中MainActivity设置WindowUtils.setWindowStatus(getWindow()),书架Fragment占领状态栏,保证标题栏正常显示,如下图:
public class BookShelfFragment extends BaseTabFragment {
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mRootView = inflater.inflate(R.layout.fragment_book_shelf, container, false);
setActionBarParams(mRootView);
}
/**
* 设置ActionBar状态
* @param view
*/
private void setActionBarParams(View view) {
if (!isShowStatusBar()) {
return;
}
View actionbarHead = view.findViewById(R.id.actionbar_head);
StatusBarUtils.setActionBarParams(getActivity(), actionbarHead, true);
}
}
(3)学习链接
在代码中设置leftMargin和rightMargin值
Android动态设置控件大小以及设定margin以及padding值
(1)基于ContentProvider这个对于绝大数开发来说,基本没用到的四大组件之一来做的。查看他的leakcanary-leaksentry模块的AndroidManifest.xml文件:
LeakSentryInstaller
internal class LeakSentryInstaller : ContentProvider() {
override fun onCreate(): Boolean {
CanaryLog.logger = DefaultCanaryLog()
val application = context!!.applicationContext as Application
InternalLeakSentry.install(application)
// 骚操作在这里,利用系统自动调用CP的onCreate方法来做初始化
return true
}
}
(2)好处:带来了 “免侵入” ,不需要业务人员写任何代码。因为一般启动的顺序是:
Application -> attachBaseContext ==>ContentProvider->onCreate ==>Application->onCreate ==>Activity->onCreate
,所以对于大多数场景,写在CP的初始化的实际是足够优先了。
(3)官网
LeakCanary Overview官网
LeakCanary Overview Github
(4)参考链接
LeakCanary2的免写"初始化代码"原理
(1)引用自定义资源。格式:@[package:]type/name
android:text="@string/hello"
(2)引用系统资源。格式:@android:type/name
android:textColor="@android:color/opaque_red"
(3)注意:@android:type/name是@[package:]type/name 的一个子类
注意:没在public.xml中声明的资源是google不推荐使用的。
@+id/资源ID名,新建一个资源ID
@id/资源ID名,应用现有已定义的资源ID,包括系统ID
@android:id/资源ID名,引用系统ID,其等效于@id/资源ID名
android:id="@+id/selectdlg"
android:id="@android:id/text1"
android:id="@id/button3"
(1)attr/: 引用应用内的属性资源
①在values/文件夹下建一个attrs文件,在这里保存style属性,通过?attr/来引用,比如在 attr.xml里定义了一个属性:
②在Theme下指定值:
③在layout中通过引用的方式去使用这个属性了:
android:background="?attr/fillColorAttr"
(4)?android: 引用系统内建的属性资源
style="?android:attr/progressBarStyleHorizontal"
style="?android:progressBarStyleHorizontal"
@android, ?attr/ 和 ?android 的区别
Android xml资源文件中@、@android:type、@*、?、@+含义和区别
Android根据资源名获取资源ID
当前新冠肺炎疫情正在全球蔓延,逝世人数众多。政府政府规定今年的 4 月 4 号为哀悼日,所有互联网项目能置灰的要跟随置灰处理。淘宝、京东等部分 App 都显示为灰色,对抗击新冠肺炎疫情斗争牺牲烈士和逝世同胞的深切哀悼。在 Android 开发中如何实现全局置灰是开发人员需要解决的技术问题。
最简单的实现方式是,在App内置2套切图及对应的2套色值资源文件等,根据全局置灰开关,选择加载不同的图片和色值。这种方案全局实现起来工作繁琐、工作量大。且会导致 App包增大很多,用户体验差,不是一种很好的实现方案。
实现灰度化的思路应该从Android 系统界面绘制原理出发寻找实现方案。系统是通过Paint将内容绘制到界面上的,Paint中可以设置ColorMatrix ,界面置灰可以通过通过使用颜色矩阵(ColorMatrix)来实现。
在Android中,系统使用一个颜色矩阵ColorMatrix来处理图像的色彩效果,通过这个类方便地改变矩阵值来处理颜色效果。ColorMatrix提供了以下关键方法进行对颜色的处理:
①setRotate(int axis, float degree) :设置颜色的色调,第一个参数,系统分别使用0、1、2 来代表 Red、Green、Blue 三种颜色的处理;而第二个参数,就是需要处理的值。
②setSaturation(float sat):设置颜色的饱和度,参数代表设置颜色饱和度的值,当饱和度为0时,图像就变成灰度图像。
③setScale(float rScale, float gScale, float bScale , float aScale):对于亮度进行处理。
实现去色重点用到了ColorMatrix类中的setSaturation方法,查看官网API对这个方法有这么一段说明:“Set the matrix to affect the saturation of colors. A value of 0 maps the color to gray-scale. 1 is identity。 ” 意思是去设置颜色矩阵的饱和度,0是灰色的,1是原图。通过调用setSaturation (0) 方法即可将图像置为灰色 。通过继承ImageView实现该方案验证,具体代码如下:
public class GrayImageView extends AppCompatImageView {
private Paint mPaint;
public GrayImageView(Context context) {
super(context, null);
}
public GrayImageView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
// 一个颜色矩阵 ColorMatrix,来处理图像的色彩效果。通过这个类,可以很方便地改变矩阵值来处理颜色效果
ColorMatrix colorMatrix = new ColorMatrix();
// 设置颜色的饱和度,参数代表设置颜色饱和度的值,当饱和度为0时,图像就变成灰度图像;1是原图。
colorMatrix.setSaturation(0);
mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
}
@Override
protected void onDraw(Canvas canvas) {
canvas.saveLayer(null, mPaint, Canvas.ALL_SAVE_FLAG);
super.onDraw(canvas);
}
}
从上面ImageView的置灰实现猜测是否所有的View都能给置灰,DecorView是Activity窗口的根视图,根视图通过ColorMatrix设置置灰应该可以在全部子元素有效。在BaseActivity 中添加代码如下:
private void putAsh() {
Paint paint = new Paint();
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.setSaturation(0f);
paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
// 一般选择硬件绘制还是软件绘制会调用下面的API:
getWindow().getDecorView().setLayerType(View.LAYER_TYPE_HARDWARE, paint);
}
在 Android 开发中,App 全局置灰如何实现?
App 黑白化实现探索2, 发现了一种更方便的方案
(1)isAssignableFrom
假设有两个类Class1和Class2, Class1.isAssignableFrom(Class2)表示:
①类Class1和Class2是否相同;
②Class1是否是Class2的父类或接口,调用者和参数都是java.lang.Class类型;
(2)instanceof
用来判断一个对象是否是一个类及其子类或接口及其子接口的的实例;
格式:object instanceof TypeName // 第一个参数是对象实例名,第二个参数是具体的类名或接口名
(3)学习链接
lass.isAssignableFrom与instanceof的区别