TextView文字颜色渐变

TextView文字颜色渐变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import android.graphics.LinearGradient
import android.graphics.Shader
import android.widget.TextView

/**
* 左到右渐变
*/
fun TextView.setHorizontalGradientTextColor(startColor: Int, endColor: Int) {
val x1 = this.paint.measureText(this.text.toString())//测量文本 宽度
val shader = LinearGradient(0f, 0f, x1, 0f, startColor, endColor, Shader.TileMode.CLAMP)
this.paint.shader = shader
this.invalidate()
}

/**
* 上到下渐变
*/
fun TextView.setVerticalGradientTextColor(startColor: Int, endColor: Int) {
val y1 = this.paint.textSize//测量文本 高度
val shader = LinearGradient(0f, 0f, 0f, y1, startColor, endColor, Shader.TileMode.CLAMP)
this.paint.shader = shader
this.invalidate()
}

使用方式,调用扩展方法即可

textView.setHorizontalGradientTextColor(Color.RED, Color.GREEN)

APK签名之4种签名方案

4种APK签名方案

应用签名: https://source.android.google.cn/docs/security/features/apksigning?hl=zh-cn

0x01 V1签名

Android 7.0以下版本, 只能用旧签名方案 V1 scheme (JAR signing)

  • 来自JDK(jarsigner), 对zip压缩包的每个文件进行验证, 签名后还能对压缩包修改(移动/重新压缩文件)
  • 对V1签名的apk/jar解压,在META-INF存放签名文件(MANIFEST.MF, CERT.SF, CERT.RSA),
  • 其中MANIFEST.MF文件保存所有文件的SHA1指纹(除了META-INF文件), 由此可知: V1签名是对压缩包中单个文件签名验证

0x02 V2签名

Android 7.0开始, 谷歌增加新签名方案 V2 Scheme (APK Signature)

  • 来自Google(apksigner), 对zip压缩包的整个文件验证, 签名后不能修改压缩包(包括zipalign),
  • 对V2签名的apk解压,没有发现签名文件,重新压缩后V2签名就失效, 由此可知: V2签名是对整个APK签名验证

V2签名优点很明显:

  • 签名更安全(不能修改压缩包)
  • 签名验证时间更短(不需要解压验证),因而安装速度加快

0x03 V3签名

Android 9 支持 APK 密钥轮替,这使应用能够在 APK 更新过程中更改其签名密钥。为了实现轮替,APK 必须指示新旧签名密钥之间的信任级别。v3 在 APK 签名分块中添加了有关受支持的 SDK 版本和 proof-of-rotation 结构体的信息,以允许使用新旧密钥。

0x04 V4签名

Android 11 添加了对 APK 签名方案 v4 的支持。此方案会在单独的文件 (apk-name.apk.idsig) 中生成一种新的签名。此方案支持 ADB 增量 APK 安装,这样会加快 APK 安装速度。v4 签名需要 v2 或 v3 签名作为补充。

ADB 增量 APK 安装
在设备上安装大型(2GB 以上)APK 可能需要很长的时间,即使应用只是稍作更改也是如此。ADB(Android 调试桥)增量 APK 安装可以安装足够的 APK 以启动应用,同时在后台流式传输剩余数据,从而加速这一过程。如果设备支持该功能,并且您安装了最新的 SDK 平台工具,adb install 将自动使用此功能。如果不支持,系统会自动使用默认安装方法。
https://developer.android.google.cn/about/versions/11/features

运行以下 adb 命令以使用该功能。如果设备不支持增量安装,该命令将会失败并输出详细的解释。

adb install –incremental

在运行 ADB 增量 APK 安装之前,您必须先为 APK 签名并创建一个 APK 签名方案 v4 文件。必须将 v4 签名文件放在 APK 旁边,才能使此功能正常运行。

20230817204614

0x05 总结

综上,可以看到APK v4是面向ADB即开发调试的,而如果我们没有签名变动的需求也可以不考虑APK v3,所以目前国内大部分还停留在APK v2。

0x06 使用

  1. 在项目app的build.gradle中配置支持的签名方案
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
android {
signingConfigs {
release {
keyAlias 'test'
keyPassword 'pwd123'
storeFile file("test.jks")
storePassword 'pwd123'

enableV1Signing true
enableV2Signing true
enableV3Signing true
enableV4Signing true
}
}
}

  1. Eclipse或Android Studio在Debug时,对App签名都会使用一个默认的密钥库:
  • 默认在C:\Users\用户名\.android\debug.keystore
  • 密钥库名: debug.keystore
  • 密钥别名: androiddebugkey
  • 密钥库密码: android

APK签名之apksigner签名工具

APK签名之apksigner签名工具

0x00 参考

apksigner官网:https://developer.android.google.cn/studio/command-line/apksigner?hl=zh-cn
Android中APK签名工具之jarsigner和apksigner详解:https://www.jb51.net/article/141954.htm
Android的:https://zhuanlan.zhihu.com/p/541983756

0x01 V1和V2签名工具

  • jarsigner 是JDK提供的针对jar包签名的通用工具,位于 JDK/bin/jarsigner.exe,仅支持V1签名
  • apksigner 是Google 官方提供的针对 Android apk 签名及验证的专用工具,位于 Android SDK/build-tools/SDK版本/apksigner.bat,支持apk的多种签名方案

不管是 apk 包,还是 jar 包,本质都是 zip 格式的压缩包,所以它们的签名过程都差不多(仅限V1签名),以上两个工具都可以对 Android apk 包进行签名.

注意:apksigner 工具默认同时使用V1+V2+V3签名方案,以兼容 Android7.0以下系统版本

0x02 签名命令

进入Android SDK/build-tools/SDK版本,输入:

1
.\apksigner sign --v1-signing-enabled true --v2-signing-enabled true --ks D:\apk\test.jks --in D:\apk\xiaomi-unsign.apk --out D:\apk\xiaomi-signed.apk

参数说明:

–v1-signing-enabled <true | false>
确定 apksigner 是否会使用基于 JAR 的传统签名方案为给定的 APK 软件包签名。默认情况下,该工具会使用 –min-sdk-version 和 –max-sdk-version 的值来决定何时采用此签名方案。
–v2-signing-enabled <true | false>
确定 apksigner 是否会使用 APK 签名方案 v2 为给定的 APK 软件包签名。默认情况下,该工具会使用 –min-sdk-version 和 –max-sdk-version 的值来决定何时采用此签名方案。
–v3-signing-enabled <true | false>
确定 apksigner 是否会使用 APK 签名方案 v3 为给定的 APK 软件包签名。默认情况下,该工具会使用 –min-sdk-version 和 –max-sdk-version 的值来决定何时采用此签名方案。
–v4-signing-enabled <true | false | only>
确定 apksigner 是否会使用 APK 签名方案 v4 为给定的 APK 软件包签名。此方案会在单独的文件 (apk-name.apk.idsig) 中生成签名。如果为 true 并且 APK 未签名,则系统会根据 –min-sdk-version 和 –max-sdk-version 的值生成 v2 或 v3 签名。然后,该命令会根据已签名的 APK 的内容生成 .idsig 文件。
使用 only 仅生成 v4 签名,而不会修改 APK 及其在调用前具有的任何签名。如果 APK 没有 v2 或 v3 签名,或者签名使用的密钥不同于为当前调用提供的密钥,则 only 会失败。
默认情况下,该工具会使用 –min-sdk-version 和 –max-sdk-version 的值来决定何时采用此签名方案。

0x03 验证命令

1
2
3
.\apksigner verify -v D:\apk\xiaomi-signed.apk

keytool -printcert -jarfile D:\apk\xiaomi-signed.apk

20230817212409

Android之FileProvider详解

Android之FileProvider详解

原文地址:https://juejin.cn/post/7009204672225345549

Android 7.0之前,文件的Uri以 file:///形式提供给其他app访问。

Android 7.0之后,分享文件的Uri发生了变化。为了安全起见, file:///形式的Uri不能正常访问。官方提供了 FileProvider,FileProvider生成的Uri会以 content://的形式分享给其他app使用。

在7.0以前,为了访问 file:///形式的Uri,我们必须修改文件的权限。修改后的权限对所有app都是有效的,这样的行为是不安全的。 content://形式的Uri让Android的文件系统更安全,对于分享的文件,接收方app只拥有临时的权限,减少了我们app内部的文件被其他app恶意操作的行为。

0x01 创建FileProvider

在manifest文件 <application></application>标签中添加pvodier标签,配置如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
<manifest>
<application>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>

android:name指定Provider的类名,使用官方提供的androidx.core.content.FileProvider

android:authorities相当于一个用于认证的暗号,在分享文件生成Uri时,会通过它的值生成对应的Uri。。值是一个域名,一般格式为 <包名>.fileprovider</包名>

android:exported设置为false,FileProvider不需要公开。

android:grantUriPermissions设置为true,这样就能授权接收端的app临时访问权限了。

0x02 配置共享目录file_paths.xml

res/xml中创建一个资源文件(如果xml目录不存在,先创建),名字随便(一般叫file_paths.xml)。

1
2
3
4
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="my_logs" path="logs/"/>
...
</paths>

<paths></paths>必须有1个或多个子标签,每个子标签代表要请求的私有文件目录。不同的子标签代表不同的目录类型。

<provider></provider>标签中添加 <meta-data></meta-data>子标签。

设置 <meta-data></meta-data>的属性 android:name值为 android.support.FILE_PROVIDER_PATHS,属性 android:resouce的值为刚才我们创建的path文件名。

配置paths

<paths></paths>的每个子标签必须有 path属性,代表content Uris的路径。 name不需要和path保持一样,只是一个名称。

子标签有以下几种。

files-path
1
2
<files-path name="my_files" path="path" />

代表内部存储的files目录,与 Context.getFilesDir()获取的路径对应。

最终生成的Uri格式为:authorities/pathname/filename

示例:

1
2
content:

cache-path
1
2
<cache-path name="name" path="path" />

代表内部存储的cache目录,与 Context.getCacheDir()获取的路径对应。

external-path
1
2
<external-path name="name" path="path" />

代表外部存储(sdcard)的cache目录,与 Environment.getExternalStorageDirectory()获取的路径对应。

external-files-path
1
2
<external-files-path name="name" path="path" />

代表app的外部存储的根目录,与 Context#getExternalFilesDir(String) Context.getExternalFilesDir(null)获取的路径对应。

external-cache-path
1
2
<external-cache-path name="name" path="path" />

代表app外部缓存区域的根目录,与 Context.getExternalCacheDir()获取的路径对应。

external-media-path
1
2
<external-media-path name="name" path="path" />

代表app外部存储媒体区域的根目录,与 Context.getExternalMediaDirs()获取的路径对应。

注意: 这个目录只在API 21(也就是Android 5.0)以上的系统上才存在。

0x03 生成Content Uri文件

为了让其他app使用Content Uri,我们的app必须提前生成Uri。

1
2
3
4
5
6
7
File file = new File(Context.getFilesDir(), "my_log");
Uri uri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
uri = FileProvider.getUriForFile(getContext(), this.getPackageName() + ".FileProvider", file);
} else {
uri = Uri.fromFile(file);
}

这里注意获取目录,在配置paths时我们讲了,paths的子标签必须和获取目录的代码保持对应。这里我们用的是 Context.getFilesDir(),所以paths文件中必须包含 files-path子标签,不然别的app获取uri时会出现异常。

最终生成Uri是使用的 FileProvider.getUriForFile()。第一个参数就是 provider中设置的 authorities属性值。

0x04 Content Uri的几种使用场景

为邮箱app分享附件文件

1
intent.putExtra(Intent.EXTRA_STREAM, contentUri);

其他分享

使用 Intent.setDateIntent.setClipData()

1
intent.setClipDataClipData.newRawUri("", contentUri)

最后使用 startActivity(intent)启动分享操作。

0x05 授权临时权限

分享一般只有这读取和写入2种权限,根据需要传入 Intent.addFlags()中。

1
2
Intent intent = new Intent(Intent.ACTION_SEND);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

Problems专题之Gradle

Problems专题之Gradle

0x01 More than one file was found with OS independent path ‘META-INF/webview_release.kotlin_module’

这是因为第三方库中存在很多重名的META-INF文件,在打包的时候去除即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
android {
// ...
packagingOptions {
exclude 'META-INF/webview_release.kotlin_module'
exclude 'META-INF/proguard/androidx-annotations.pro'
exclude 'META-INF/gradle/incremental.annotation.processors'
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/license.txt'
exclude 'META-INF/NOTICE'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/notice.txt'
exclude 'META-INF/ASL2.0'
// ...
}
}

0x02 Certificate for <x.x.x> doesn’t match any of the subject alternative names

在执行gradlew命令打包时遇到这个错误,肯定是https证书有问题。

解决方案:如果支持http的话就使用http

1
2
3
4
5
6
7
8
9
> Could not resolve com.bytedanceapi:ttsdk-ttmp:1.36.2.25.pcdn.
Required by:
project :player > com.bytedanceapi:ttsdk-player_premium:1.36.2.25.pcdn > com.bytedanceapi:ttsdk-ttplayer:1.36.2.25.pcdn
> Could not resolve com.bytedanceapi:ttsdk-ttmp:1.36.2.25.pcdn.
> Could not get resource 'https://artifact.bytedance.com/repository/Volcengine/com/bytedanceapi/ttsdk-ttmp/1.36.2.25.pcdn/ttsdk-ttmp-1.36.2.25.pcdn.pom'.
> Could not GET 'https://artifact.bytedance.com/repository/Volcengine/com/bytedanceapi/ttsdk-ttmp/1.36.2.25.pcdn/ttsdk-ttmp-1.36.2.25.pcdn.pom'.
> Certificate for <artifact.bytedance.com> doesn't match any of the subject alternative names: [*.alicdn.com, *.cmos.greencompute.org, cmos.greencompute.org, m.intl.taob
ao.com, *.mobgslb.tbcache.com, alikunlun.com, *.alikunlun.com, s.tbcdn.cn, *.django.t.taobao.com, alicdn.com]

把项目根目录的build.gradle文件中所有的https://artifact.bytedance.com/替换为http://artifact.bytedance.com/即可

Ox03 module java.base does not “opens java.io” to unnamed module

升级到 Java 16 以上,AndroidStudio编译遇到错误。

Unable to make field private final java.lang.String java.io.File.path accessible: module java.base does not “opens java.io” to unnamed module @fb04536

解决方案一:

  1. gradle-wrapper 属性中的 gradle 版本更改为 7.1.1(6.x 不支持 java 16)
  2. gradle.properties 中添加以下行
1
2
3
4
5
6
org.gradle.jvmargs=-Xmx1536m \
--add-exports=java.base/sun.nio.ch=ALL-UNNAMED \
--add-opens=java.base/java.lang=ALL-UNNAMED \
--add-opens=java.base/java.lang.reflect=ALL-UNNAMED \
--add-opens=java.base/java.io=ALL-UNNAMED \
--add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED

解决方案二:

升级build-gradlew版本。 将项目根目录的 build.gradle文件中
classpath 'com.android.tools.build:gradle:4.2.2'
升级为
classpath 'com.android.tools.build:gradle:7.2.1'

RecyclerView+SnapHelper实现ViewPager滑动效果

RecyclerView+SnapHelper实现ViewPager滑动效果

SnapHelper结合RecyclerView使用,能很方便的实现ViewPager滑动效果。SnapHelper是一个抽象类,Google内置了两个默认实现类,LinearSnapHelper和PagerSnapHelper。

LinearSnapHelper的使用方法

使当前Item居中显示,常用场景是横向的RecyclerView, 类似ViewPager效果,但是又可以快速滑动多个条目。

1
2
3
4
5
LinearLayoutManager manager = new LinearLayoutManager(getContext());
manager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(manager);
LinearSnapHelper snapHelper = new LinearSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);

PagerSnapHelper的使用方法

使RecyclerView像ViewPager一样的效果,每次只能滑动一页。

1
2
3
4
5
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(linearLayoutManager);
PagerSnapHelper snapHelper = new PagerSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);

原文地址:https://developer.aliyun.com/article/665537

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×