MR_WIND的博客


  • 首页

  • 归档

Android笔记

发表于 2018-02-01

合并多个清单文件

1
2
3
4
5
<!-- tools:node="replace" 可以以此为准,替换其他清单中的值-->
<meta-data
android:name="com.baidu.lbsapi.API_KEY"
android:value="14234234adsf"
tools:node="replace"/>

参考Android-合并多个清单文件

图片压缩解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public static int getRatioSize(int bitWidth, int bitHeight) {
// 图片最大分辨率
int imageHeight = 1280;
int imageWidth = 960;
// 缩放比
int ratio = 1;
// 缩放比,由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
if (bitWidth > bitHeight && bitWidth > imageWidth) {
// 如果图片宽度比高度大,以宽度为基准
ratio = bitWidth / imageWidth;
} else if (bitWidth < bitHeight && bitHeight > imageHeight) {
// 如果图片高度比宽度大,以高度为基准
ratio = bitHeight / imageHeight;
}
// 最小比率为1
if (ratio <= 0)
ratio = 1;
return ratio;
}
public static void compressBitmap(Bitmap image, String filePath) {
// 最大图片大小 100KB
int maxSize = 100;
// 获取尺寸压缩倍数
int ratio = NativeUtil.getRatioSize(image.getWidth(), image.getHeight());
// 压缩Bitmap到对应尺寸
Bitmap result = Bitmap.createBitmap(image.getWidth() / ratio, image.getHeight() / ratio, Config.ARGB_8888);
Canvas canvas = new Canvas(result);
Rect rect = new Rect(0, 0, image.getWidth() / ratio, image.getHeight() / ratio);
canvas.drawBitmap(image, null, rect, null);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
int options = 100;
result.compress(Bitmap.CompressFormat.JPEG, options, baos);
// 循环判断如果压缩后图片是否大于100kb,大于继续压缩
while (baos.toByteArray().length / 1024 > maxSize) {
// 重置baos即清空baos
baos.reset();
// 每次都减少10
options -= 10;
// 这里压缩options%,把压缩后的数据存放到baos中
result.compress(Bitmap.CompressFormat.JPEG, options, baos);
}
}
链接:https://www.jianshu.com/p/e9e1db845c21

applicationIdSuffix配置可能会遇到的坑

  • 在配置不同的buildType时,可能会使用applicationIdSuffix来进行debug和release等版本的区分。对于有些需要在manifest文件中配置包名的第三方SDK来说,可能会有些影响。此时,在相应的buildType下可以用defaultConfig.applicationId + applicationIdSuffix来获取包名的配置(以极光推送的配置为例)

    1
    'JPUSH_PKGNAME':defaultConfig.applicationId + applicationIdSuffix
  • 在配置过manifest文件后,可以切换视图来查看项目所有gradle文件混合后的manifest文件,点击不同的条目能看到详细的配置路径等。

sourceSets的一些配置

  • 在build.gradle的配置中,可以针对不同的buildType或者不同的productFlavors进行特定的资源的配置。默认情况下,会使用main下的配置。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    sourceSets {
    debug {
    res.srcDirs = ['src/debug/res']
    jniLibs.srcDirs = ['jniLibs']
    assets.srcDirs = ['src/debug/assets']
    }
    main {
    res.srcDirs = ['src/main/res']
    jniLibs.srcDirs = ['jniLibs']
    assets.srcDirs = ['src/main/assets']
    }
    }
  • 新建assets等文件夹时,可以通过右键点击项目路径生成,Android Studio会在新建文件夹的时候给出一些配置提示

Gradle配置技巧

发表于 2017-09-19

强大的Gadle

自Android Studio发布以来,Gradle作为构建工具,有着强大的功能。我们可以通过简单的配置来实现一些复杂的构建过程。包括自动区分版本打包,多渠道打包等。引用一张官方的构建流程图:
典型 Android 应用模块的构建流程

主要文件

咱们最常用到的是build.gradle文件,通常的项目中会有两个:

  • Project根目录下,Project级别的build.gradle文件
  • 各个Module目录下,Module级别的build.gradle文件

Project级别的build.gradle

这个是顶级的构建文件,位于项目根目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* 定义项目中所有模块共用的 Gradle 存储区和依赖项
*/
buildscript {
/**
* 代码远程仓库,可以定义搜索并下载的远程仓库,也能定义自己的远程仓库。
* 支持本地和远程仓库,如下是引用了JCenter作为远程依赖仓库。
*/
repositories {
jcenter()
}
/**
* 一些顶级的依赖,用于整个项目,如下是使用的gradl插件2.3.3
*/
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
}
}
/**
* 给所有的Module配置公用依赖,包括一些第三方的插件和库。单独的需要进入Module级别的build.gradle文件进行配置
* Android新项目会默认使用JCenter,但不会添加任何依赖。
*/
allprojects {
repositories {
jcenter()
}
}
/**
* 经常会有些项目有这样的写法,位于顶层的配置可以被其他的gradle文件调用,所以有些项目会把版本号,SDK的版本配置在这里,方便所有的gradle文件统一
*/
ext {
//版本号版本名称等
versionCode = 1
versionName = '1.0.0'
}

Module级别的build.gradle

通常我们新建项目的时候,这个目录是app/build.gradle。这个文件中就有很多可以给我们进行自定义了。包括 android{} repositories{} dependencies{} 等;

  • android{}
    这边挑几个主要的介绍

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    android {
    //...
    //默认配置
    defaultConfig{
    }
    //编译类型配置
    buildTypes {
    //可以根据实际情况来定义各种构建类型
    debug{
    //对于debug版本的单独配置,包括debug开关,和一些参数的配置
    }
    release{}
    //...
    }
    //渠道打包。可以针对不同渠道生成不同的包名。也可以用来区分免费版和付费版
    productFlavors {
    free {
    applicationId 'com.example.myapp.free'
    }
    paid {
    applicationId 'com.example.myapp.paid'
    }
    }
    //...
    }
  • repositories{}

    1
    2
    3
    4
    5
    6
    7
    repositories {
    //主要是一些仓库的设置,支持本地代码库
    mavenCentral()
    flatDir {
    dirs 'aars'
    }
    }
  • dependencies{}
    比较常用的是这个配置,用来依赖各种第三方代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //代码依赖
    dependencies {
    //本地的依赖
    compile fileTree(include: ['*.jar'], dir: 'libs')
    //通常的依赖
    compile 'io.reactivex:rxjava:1.1.6'
    //测试依赖
    testCompile 'junit:junit:4.12'
    //正式发布的时候依赖
    releaseCompile 'com.android.support:appcompat-v7:25.3.1'
    //只在调试的时候依赖
    debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
    }

一些实用的配置和技巧

关于签名的一些配置

在实际开发中,大部分情况下,我们会将代码通过Git上传至代码仓库,这样的话,签名配置等一些敏感信息,例如密码等都不适合上传。那么这个时候,我们可以使用本地的一个「local.properties」文件来进行本地的签名信息配置(该文件不被上传至代码库,库将其添加至.ignore文件中)。Android Studio会默认为我们生成这么一个文件的,它本来是用来配置SDK在本地的路径等信息的。现在我们也能利用这个文件进行签名文件的配置。

  1. 首先我们将我们的签名信息配置到local.properties文件中

    1
    2
    3
    4
    5
    6
    # 本地签名文件部署,主要修改storeFile的路径值
    keyAlias=example
    keyPassword=123456
    # 签名文件的路径
    storeFile=/Users/keys/example.jks
    storePassword=123456
  2. 在build.gradle开头加载这个文件(在 android{} 代码之前添加)

    1
    2
    3
    4
    //加载本地签名配置
    def keystorePropertiesFile = rootProject.file("local.properties")
    def keystoreProperties = new Properties()
    keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
  3. 在 signingConfigs{} 中配置

    1
    2
    3
    4
    5
    6
    7
    8
    signingConfigs {
    ExampleSign {
    keyAlias keystoreProperties['keyAlias']
    keyPassword keystoreProperties['keyPassword']
    storeFile file(keystoreProperties['storeFile'])
    storePassword keystoreProperties['storePassword']
    }
    }

经过以上3步的配置,就不用担心密码泄露的一些问题了。

动态配置AndroidManifest.xml中的一些值

在对接一些第三方的API时,往往需要在「AndroidManifest.xml」文件中进行「app_key」「app_secret」等参数的配置。
一般按照第三方API文档的做法,比如我接入一个阿里的推送服务,那我需要如下配置:

1
2
3
4
5
6
7
<meta-data
android:name="com.alibaba.app.appkey"
android:value="exampleappkey"/>
<meta-data
android:name="com.alibaba.app.appsecret"
android:value="exampleappsecret"/>

可是在实际生产中,我们往往会发布一个beta版用于发release版之前的测试,但是beta版和release版用的不是同一套「app_key」「app_secret」。这个时候比较笨的办法是,写两套,然后每次发布版本的时候,将其中一套注释掉。这样做当然是可行的,但是根据版本的不断迭代,往往会忘记对这一部分进行修改或者修改混乱,导致发布的版本产生混乱。

这个时候可以利用gradle结合AndroidManifest.xml进行动态的配置:

1
2
3
4
5
6
7
<meta-data
android:name="com.alibaba.app.appkey"
android:value="${ali_push_appkey}"/>
<meta-data
android:name="com.alibaba.app.appsecret"
android:value="${ali_push_appsecret}"/>

这里的 ${value} 的写法相当于定义了value这么一个变量,然后再在build.gradle文件中,找到不同的buildType

1
2
3
4
5
6
7
8
9
10
11
12
buildTypes {
debug {
//开启调试
debuggable = true
//编译时给包名
applicationIdSuffix '.dev'
//给AndroidManifest.xml中对应的变量设置值
manifestPlaceholders = [ ali_push_appkey : "2******6"
, ali_push_appsecret: "c1********************31"]
}
//...
}

可以看到manifestPlaceholders可以有很多对这样的值进行设定。经过这样的设置,每次对不同版本打包时,都会自动找到相应的配置,这样就不需要每次打包都要进行修改了。

使用Android Studio切换运行不同的版本

通常使用Android Studio项目运行时,默认是使用的debug的配置。根据上面动态配置来看,我们是存在beta版或者release版的,有时候,我们会面对需要调试特定版本来判定服务器(服务器一般会有测试环境和生产环境的区分)或者第三方API在该版本下的配置是否正常运行的情形。

可是,我们总不能每次需要测试生产环境时,就打一个包,然后push到设备上进行安装吧。Android Studio为我们提供了一个运行切换的方法–Build Variant。通常,这个选项会在整个IDE的左下角,也可以通过「View」–>「Tool Windows」–>「Build Variant」找到。打开这个选项视图,可以看到各个Module及当前的Build Variant。这个Build Variant,指的是gradle中buildTypes和productFlavors排列组合所有的版本,比如,配置了2个buildTypes和2个productFlavors,那么会有2*2=4种组合。切换的方法也很简单,只需要点击选择你想切换的版本就好:
Build Variant
在选择版本之后,你运行的版本就是你选择的这个版本了。

打包自动生成包名

在辛苦的完成编码之后,我们打出了一个APP的安装包。这个安装包的名称为「app-release」你习惯性的给它重新命名了一番。只发这么一次版本打一次包的话这么做还好。如果要打很多个包,你总不能一个个命名过去吧。使用gradle进行配置的话会很方便,只需要在 android{} 中加入一段代码就可实现:

1
2
3
4
5
6
7
8
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
output.outputFile = new File(output.outputFile.parent,
defaultConfig.applicationId
+ (buildType.properties.containsKey("applicationIdSuffix") ? buildType.properties.get("applicationIdSuffix") : "")
+ "_" + defaultConfig.versionName + ".apk");
}
}

在new File()的第二个参数中,你可以传入想要的生成文件名的规则。上面这段代码生成的格式是「包名+build后缀_版本名.apk」其中 applicationIdSuffix 的值会根据不同bulidTypes生成后缀。

提供一份较完整的app/build.gradle文件供参考build.gradle

参考
官方文档
ANDROID 开发你需要了解的 GRADLE 配置

使用GoogleTTS

发表于 2017-09-18

TTS–TextToSpeech

TextToSpeech指的是文字转语音技术。随着人工智能的发展,现在很多智能设备都开始支持语音输出功能了。对应APP开发来说,让机器说话,已经是一项很常见的需求了。目前有很多相关方面的API供大家选择。国内比较出名的比如讯飞的开放平台,提供了一套比较完整的SDK。

其实,Android系统本身是直接支持TTS的。大部分应该是在「设置」–「语言和输入法」里找到TTS相关的设置。国内可能由于各个厂商手机定制的ROM的不同,TTS里也会有不同预装的引擎。这些引擎能够提供给一些APP来使用,比如,某些阅读类APP是支持朗读功能的,大部分情况下,这类APP会使用系统的TTS来完成一个「文字」->「语音」的过程,来实现功能。

比如在项目中,需要这么一个功能,能够将交易完成时的金额实时的播报出来。通过Android系统的TTS功能就能比较快速的实现了。

准备工作

  1. 确定是否安装TTS引擎,若没有安装,这边提供Google官方的TTS引擎GoogleTTS(无法下载点这里)
  2. 安装好TTS引擎后,下载安装语音包。(在TTS的设置里,进行语音试听,语音引擎就会在后台自动下载语音包了)

代码实现

代码实现也是比较简单的,主要就是实例化一个TextToSpeech的对象,完成相关的初始化设定之后,再传入要转换成语音的字符串,就完成了一次完整的调用。下面来看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
mTextToSpeech = new TextToSpeech(context, new TextToSpeech.OnInitListener() {
@Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
//设置语音包语言
int result = mTextToSpeech.setLanguage(Locale.CHINA);
initSuccess = false;
if (result == TextToSpeech.LANG_MISSING_DATA) {
ToastUtil.showShortToast(context, "缺少语音包,请稍后重试");
} else if (result == TextToSpeech.LANG_NOT_SUPPORTED) {
ToastUtil.showShortToast(context, "不支持该语言");
} else if (result != TextToSpeech.LANG_COUNTRY_AVAILABLE
&& result != TextToSpeech.LANG_AVAILABLE) {
Log.e(TAG, "onInit: TTS暂时不支持这种语言的朗读!");
} else {
initSuccess = true;
mTextToSpeech.setOnUtteranceProgressListener(new UtteranceProgressListener() {
@Override
public void onStart(String utteranceId) {
Log.d("TextToSpeech", "onStart: ");
}
@Override
public void onDone(String utteranceId) {
Log.d("TextToSpeech", "onDone: ");
}
@Override
public void onError(String utteranceId) {
Log.d("TextToSpeech", "onErr: ");
}
});
HashMap<String, String> map = new HashMap<>();
String utteranceId = UUID.randomUUID().toString();
map.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, utteranceId);
map.put(Constants.GOOGLE_TTS_PKG_NAME + ":engine", utteranceId);
//调用speak方法,传入文本和模式
int res = mTextToSpeech.speak(msg, TextToSpeech.QUEUE_ADD, map);
if (res != TextToSpeech.SUCCESS) {
Log.e(TAG, "onInit:res:" + res);
}
}
} else {
Log.e(TAG, "onInit: 连接TTS引擎失败");
}
}
}, "com.google.android.tts");

上面这段代码,需要注意的就是设置语音包语言,再根据结果进行要不要输出语音。可以看到在onInit(int status)的回调中,需要对语音包语言进行设置,当第一次调用,发现缺少语音包的时,引擎会在后台下载相对应语言的语音包。

关键调用的是mTextToSpeech.speak(msg, TextToSpeech.QUEUE_ADD, map);这个函数。它需要传入3个参数:

  1. 第1个参数,我们要转换的文字内容;
  2. 第2个参数,在列队中的等待模式,它有两个参数TextToSpeech.QUEUE_ADD和TextToSpeech.QUEUE_FLUSH,QUEUE_ADD就是向播放列队末尾添加一个列队,在列队中,会依次完成转换和播放,而QUEUE_FLUSH则会清空列队里要播放的音频和要转换的文本,直接转换并播放当前的列队条目;
  3. 第3个参数,唯一的识别ID,可以生成UID传入;

需要注意的点:

  • 实例化TextToSpeech对象时,注意第三个参数需要传语音引擎的包名
  • speak方法在API21以上调用的方法有所不同,不过大同小异参考官方文档;
  • speak方法只能在onInit回调中进行调用;
  • 语音转换的文本的长度是有限制的,需要小于getMaxSpeechInputLength()获取的值;

最后给一个供参考的工具类SpeakerUtil.java

自定义键盘

发表于 2017-02-25

UI布局

在xml包(如果没有,自己在res包下新建一个)中创建一个键盘布局num_keyboard_nine.xml

  • Keyboard基本需要设置每个按键的width和height,还有Gap
  • 设置Row,设计每一行的键盘
  • 设置codes,主要是键盘上的字符的ASCII码
  • 设置keyLabel,用于显示在键盘上
  • 设置keyIcon,用图标代替lable显示(不能跟keyLabel同时使用)
  • 设置isRepeatable,设置是否长按时,可以重复输入
  • 设置keyEdgeFlag,设置该键的边界
  • 问题1: keyLabel的字符个数若大于1,那么该按键的字体显示效果,将于默认的不同。字体会缩小,并且是加粗字体。
  • 解决: 以上的问题找了很多资料,后来发现在源码中有这么一段:

    1
    2
    3
    4
    5
    6
    7
    8
    // For characters, use large font. For labels like "Done", use small font.
    if (label.length() > 1 && key.codes.length < 2) {
    paint.setTextSize(mLabelTextSize);
    paint.setTypeface(Typeface.DEFAULT_BOLD);
    } else {
    paint.setTextSize(mKeyTextSize);
    paint.setTypeface(Typeface.DEFAULT);
    }

    从上面的源码可以看到,官方在这里做了一些处理。所以,咱们可以设置codes的个数大于两个从而绕过这一段代码。

    layout中使用View

    主要是用到keyboardView或者自定义一个view继承自keyboardView。下面用原生控件来举例。

  • 直接使用官方的keyboardView。注意使用标签来引用大概如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <android.inputmethodservice.KeyboardView
    android:id="@+id/keyboard_nine"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@color/keyBoard"
    android:focusable="true"
    android:focusableInTouchMode="true"
    android:keyBackground="@drawable/keyback"
    android:keyTextColor="@color/keyColor"
    android:keyTextSize="@dimen/font_24"
    android:shadowRadius="0.0"
    android:visibility="gone">
    </android.inputmethodservice.KeyboardView>
  • keyBackground,设置按键的背景

  • 创建的时候需要在OnCreate中使用

    1. new一个键盘布局,并引用自己定义的键盘布局

      1
      Keyboard keyboard = new Keyboard(getContext(),R.xml.num_keyboard_nine);
    2. 将KeyboardView与布局绑定

      1
      2
      mKeyboardView = (KeyboardView)findViewById(R.id.keyboard_nine);
      mKeyboardView.setKeyboard(keyboard);
    3. 使用自定义的CustomKeyboard进行组装

      1
      customKeyboard = new CustomKeyboard(getActivity(), keyboard, mKeyboardView);

自定义类进行封装

使用自定义的组装类,可以大幅度实现案件功能上的自定义
CustomKeyboard.java

MR_WIND

4 日志
1 标签
RSS
© 2018 MR_WIND
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.2