- 首先开启手机的USB调试并且使用USB连接电脑
在终端执行
adb tcpip 5555
:1
2C:\Users\LENOVO>adb tcpip 5555
restarting in TCP mode port: 5555在终端执行
adb connect 192.168.2.106
:1
2C:\Users\LENOVO>adb connect 192.168.2.106
connected to 192.168.2.106:5555如果要断开连接
在终端执行adb disconnect
:1
2C:\Users\LENOVO>adb disconnect
disconnected everything
android studio生成aar包并在其他工程引用aar包
1、aar包是android studio下打包android工程中src、res、lib后生成的aar文件,aar包导入其他android studio 工程后,其他工程可以方便引用源码和资源文件
2、生成aar包的步骤:
①.用android studio打开一个工程,然后新建一个Module,新建Module时候选择Android Library,后面按新建普通工程操作;
②.在新建的Module中编写完代码后,接下来编译整个工程后就会自动生成aar包,包的路径在新建的Module ==》 build ===》outputs ==>aar目录下
3、其他androidstudio工程引用aar包
①.将aar包复制到lib目录下
②.配置build.gradle文件:
加入1
2
3
4
5
6
7
8
9
10repositories {
flatDir {
dirs 'libs'
}
}
dependencies {
...
compile(name:'auil-debug', ext:'aar')
...
}
③.编译一次工程
以上操作成功后可以在扩展包下看到被引用的aar包文件
Android String.xml 相关
动态显示
%n$ms:代表输出的是字符串,n代表是第几个参数,设置m的值可以在输出之前放置空格
%n$md:代表输出的是整数,n代表是第几个参数,设置m的值可以在输出之前放置空格
%n$mf:代表输出的是浮点数,n代表是第几个参数,设置m的值可以控制小数位数,如m=2.2时,输出格式为00.00%1$s
1 <string name="loading">离下班回家还剩%1$s分钟</string>
1 | String temp = getResources().getString(R.string.loading); |
结果:离下班回家还剩38分钟
%1$3s
1 | <string name="loading">离下班回家还剩%1$4s分钟</string> |
1 | String temp = getResources().getString(R.string.loading); |
结果:离下班回家还剩 38分钟
注:m设置为4只有2个空格
%1$2.2f
1 | <string name="loading">离下班回家还剩%1$2.2f分钟</string> |
1 | String temp = getResources().getString(R.string.loading); |
结果:离下班回家还剩1234.12分钟
%的显示
使用%%来显示一个%1
<string name="loading">离下班回家还剩%1$2.2f%%分钟</string>
1 | String temp = getResources().getString(R.string.loading); |
结果:离下班回家还剩34.12%分钟
空格的显示
 
半个中文字
1 | <string name="num">今日    工单总数</string> |
结果:
重构——引入Databinding
添加DataBinding支持
在Android Studio上使用,需要在module级别的build.gradle上添加对DataBinding的支持1
2
3
4
5
6android {
....
dataBinding {
enabled = true
}
}
布局文件
DataBinding的layout files和普通的非DataBinding布局文件是有一些区别的,下面是一个基础的使用了DataBinding的布局文件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<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="com.example.User" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}" />
</LinearLayout>
</layout>
事件处理
点击事件onClick
android:onClick="@{()->viewModel.logout()}
1
2
3
4
5
6
7
8
9
10
11<Button
android:id="@+id/log_out"
android:layout_width="268dp"
android:layout_height="32dp"
android:layout_centerHorizontal="true"
android:layout_marginTop="40dp"
android:onClick="@{()->viewModel.logout()}"
android:background="@drawable/round_corner_logout"
android:text="@string/log_out"
android:textColor="#ffffff"
android:textSize="14sp" />
ViewPager
setCurrentItem(int item)
- 在ViewModel创建
public ObservableInt mCurrentIndex = new ObservableInt();
- 在xml关联
app:currentItem="@{viewModel.mCurrentIndex}"
- 通过设置mCurrentIndex的值,即可改变ViewPager的显示位置
1
2
3public void setCurrentItem(int currentIndex) {
mCurrentIndex.set(currentIndex);
}
setAdapter(PagerAdapter adapter)
- 在ViewModel创建
public FragmentPagerAdapter mAdapter;
- 在
setViewModel
前,需要初始化mAdapter
- 在xml关联
app:adapter="@{viewModel.mAdapter}"
setOnPageChangeListener(OnPageChangeListener listener)
- 在ViewModel实现接口
implements OnPageChangeListener
重写
OnPageChangeListener
的方法1
2
3
4
5
6
7
8
9
10
11public void onPageScrollStateChanged(int state) {
}
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
public void onPageSelected(int position) {
mCurrentIndex.set(position);
}在xml关联
app:onPageChangeListener="@{viewModel}"
View的可见状态控制
- 引入View
<import type="android.view.View" />
- 通过
?:
来控制显示1
android:visibility="@{viewModel.isVisible?View.VISIBLE:View.GONE}"
颜色控制
- 通过
?:
来控制显示1
android:textColor="@{viewModel.isBlue?@color/blue:@color/gray}"
Include布局
Include这个布局标签在DataBinding布局里面使用有点特殊, 因为它需要我们传递绑定的方法和数据对象。比如我们有以下的include布局:1
2
3
4
5
6
7
8
9<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="android.view.View" />
<variable
name="viewModel"
type="com.zhhx.fragment.IbmsMainFragment" />
</data>
</layout>
那么我们在另一个xml引用时,就需要传递这个include需要绑定的方法和数据:1
2
3<include
layout="@layout/include_sub_system_airconditioner"
app:viewModel="@{viewModel}" />
ObservableField
使用ObservableField可以引用复杂的数据类型,如1
public ObservableField<SubsysBean> subsysBean = new ObservableField<>();
在数据更新时,直接set即可,不需要对SubsysBean
实现Bindable注解。1
subsysBean.set(info);
Databinding自动判空
使用时不需要判空,如:1
2info.Video.getData().setObjList(null);
subsysBean.set(info);
1 | <TextView |
查看源码可知,虽然进行了判空,但是对数组越界没有进行处理,所以要 谨慎防止数组越界 1
2
3
4
5
6if (viewModelSubsysBeanVideoDataGetObjList != null) {
// read viewModel.subsysBean.get().Video.data.getObjList().get(1)
viewModelSubsysBeanVideoDataGetObjListGetInt1 = viewModelSubsysBeanVideoDataGetObjList.get(1);
// read viewModel.subsysBean.get().Video.data.getObjList().get(2)
viewModelSubsysBeanVideoDataGetObjListGetInt2 = viewModelSubsysBeanVideoDataGetObjList.get(2);
}
TextView的int类型数值的显示
直接使用int类型会报错,可以调用String.valueOf()
方法
<TextView
...
android:text="@{String.valueOf(viewModel.subsysBean.Video.data.ObjList.get(2).ValidCount)}" />
重构智慧社区遇到的问题
transformNativeLibsWithStripDebugSymbolForDebug
1 | Error:Execution failed for task ':zhhxProperty:transformNativeLibsWithStripDebugSymbolForDebug'. |
解决方案:
将compileSdkVersion 19
改成compileSdkVersion 27
,即升级所有module
的compileSdkVersion
程序包org.apache.http
不存在
解决方案:
build.gradle
中,在buildToolsVersion
和defaultConfig
之间添加如下代码1
useLibrary 'org.apache.http.legacy'
FloatMath不存在
找不到符号FloatMath
符号: 方法 floor(float)
位置: 类 FloatMath
解决方案:
将FloatMath.floor()
改成Math.floor()
setLatestEventInfo
Error:(403, 25) 错误: 找不到符号
符号: 方法 setLatestEventInfo(Context,String,String,PendingIntent)
位置: 类型为Notification的变量 notification
解决方案:
将notification.setLatestEventInfo(context, title, message, pi);
改成showNotification(pi,title,message);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18private void showNotification(PendingIntent contentIntent, String contentTitle, String contentText) {
//2、发布到通知栏,让监听者能点击
//获取NotificationManager实例
NotificationManager notifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
//实例化NotificationCompat.Builde并设置相关属性
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
.setContentIntent(contentIntent)
.setDefaults(Notification.DEFAULT_ALL)
//设置小图标
.setSmallIcon(R.mipmap.ic_launcher)
//设置通知标题
.setContentTitle(TextUtils.isEmpty(contentTitle) ? getResources().getString(R.string.app_name) : contentTitle)
.setAutoCancel(true)
//设置通知内容
.setContentText(contentText);
//通过builder.build()方法生成Notification对象,并发送通知,id=1
notifyManager.notify(1, builder.build());
}
找不到Frganment的getContext()方法。
Error:(197, 28) 错误: 找不到符号
符号: 方法 getContext()
位置: 类 ComplaintClosedFragment
解决方法:
改用getActivity()
方法。
找不到ViewPager的addOnPageChangeListener()方法。
Error:(428, 23) 错误: 找不到符号
符号: 方法 addOnPageChangeListener(ComplaintFragment.TabOnPageChangeListener)
位置: 类型为ViewPager的变量 mViewPager
解决方法:
改用setOnPageChangeListener()
方法。
javaPreCompileDebug报错
1 | Error:Execution failed for task ':zhhxProperty:javaPreCompileDebug'. |
解决方案:
在defaultconfig添加1
2
3
4
5javaCompileOptions {
annotationProcessorOptions {
includeCompileClasspath true
}
}
android.dexOptions.incremental deprecated
1 | Warning:The `android.dexOptions.incremental` property is deprecated and it has no effect on the build process. |
解决方案:
删除如下代码的incremental属性1
2
3
4dexOptions {
incremental true
javaMaxHeapSize "4g"
}
transformDexArchiveWithExternalLibsDexMergerForDebug
1 | Error:Execution failed for task ':zhhxProperty:transformDexArchiveWithExternalLibsDexMergerForDebug'. |
解决方案:
通过aar引用了某工程,又通过module引用了,删掉module引用即可。
TODO
1、图片框架改造;
http://highlightjs.readthedocs.io/en/latest/css-classes-reference.html
Android 代码规范 - 命名规范
Layout
Layout的命名规则需要和使用他们的组件对应,方便查找和维护,比如我们在创建一个用户信息的UserInfoActivity
,对应的Layout的命名就应该是activity_user_info.xml
。对应Andorid组件的Layout
命名规则:
- Activity ->
activity_user_info.xml
- Fragment ->
fragment_sign_up.xml
- Dialog ->
dialog_change_password.xml
- AdapterView Item ->
item_user.xml
- include ->
include_stats_bar.xml
czy1121_update应用笔记(二)——Android客户端
最近做的一个APP需要检测更新,以前都是自己写的,这次决定使用别人封装好的一个库。
czy1121/update
清晰灵活简单易用的应用更新库
第一步,需要搭建一个检测更新的服务。详见
czy1121_update应用笔记(一)——搭建服务
第二步,就是编写Android客户端代码了。
- 创建Android工程;
导入相关的库
-
1
2
3
4
5
6repositories {
maven { url "https://jitpack.io" }
}
dependencies {
implementation 'com.github.czy1121:update:1.1.1'
} -
1
2
3
4dependencies {
implementation 'com.afollestad.material-dialogs:core:0.9.5.0'
implementation 'com.afollestad.material-dialogs:commons:0.9.5.0'
} -
1
2
3dependencies {
implementation 'com.blankj:utilcode:1.9.2'
}
需要设置Application并且初始化Utils
1
2
3
4
5
6
7public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Utils.init(this);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>- 修改app下的build.gradle
1
2
3
4
5
6
7
8defaultConfig {
applicationId "cn.czl.updatedemo"
minSdkVersion 16
targetSdkVersion 22
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}1
2
3
4compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}- 修改xml布局文件,新增一个TextView用于显示版本,新增一个Button用于主动更新
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<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="cn.czl.updatedemo.MainActivity">
<TextView
android:id="@+id/tv_version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="检查更新"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_version" />
</android.support.constraint.ConstraintLayout>在MainActivity中编写更新方法,自定义下载提示框、进度框。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58/**
* 根据 agent.getInfo() 显示更新版本对话框,具体可参考 {@link UpdateAgent.DefaultUpdatePrompter}
*
* @param isManual
*/
public void update(boolean isManual) {
// 设置默认更新接口地址与渠道
UpdateManager.setUrl("http://10.129.51.27:8080/app/check", "yyb");
// 进入应用时查询更新
UpdateManager.create(this)
//自定义更新提示框
.setPrompter(agent -> {
UpdateInfo updateInfo = agent.getInfo();
String size = Formatter.formatShortFileSize(MainActivity.this, updateInfo.size);
new MaterialDialog.Builder(MainActivity.this)
.title("应用更新")
.content(R.string.update_content, updateInfo.versionName, size, updateInfo.updateContent)
.negativeText("以后再说")
.negativeColor(getResources().getColor(R.color.blue))
.positiveText("立即更新")
.positiveColor(getResources().getColor(R.color.blue))
.onPositive((dialog, which) -> agent.update())
.show();
})
//更新失败给出提示
.setOnFailureListener(error -> ToastUtils.showShort(error.toString()))
//设置下载进度条
.setOnDownloadListener(new OnDownloadListener() {
private MaterialDialog dialog;
@Override
public void onStart() {
dialog = new MaterialDialog.Builder(MainActivity.this)
.content("下载中")
.progress(false, 100, true)
.cancelable(false)
.show();
}
@Override
public void onProgress(int progress) {
if (null != dialog) {
dialog.setProgress(progress);
}
}
@Override
public void onFinish() {
if (dialog != null) {
dialog.dismiss();
dialog = null;
}
}
})
.setManual(isManual)
//开始更新
.check();
}配置网络权限ACCESS_NETWORK_STATE和INTERNET
1
2<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />-安装运行,即可看到如下2个界面:
-
czy1121_update应用笔记(一)——搭建服务
最近做的一个APP需要检测更新,以前都是自己写的,这次决定使用别人封装好的一个库。
czy1121/update
清晰灵活简单易用的应用更新库
第一步,需要搭建一个检测更新的服务。这里直接在之前写的一个库的基础上修改。
chenzhenlindx/SpringBootDemo
清晰灵活简单易用的应用更新库
首先导入此项目,或者按照说明创建项目。
要实现的效果为:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16url:http://example.com/check?package=ezy.demo.update&version=123&channel=yyb
method:get
response:
{
"hasUpdate": true,
"isSilent": false,
"isForce": false,
"isAutoInstall": true,
"isIgnorable": true,
"versionCode": 2,
"versionName": "v2.0.2018-1-16",
"updateContent": "1、修改了bug1;\r\n2、修改了bug2;\r\n3、新增了某个功能。",
"url": "http://10.129.51.27:8080/apk/apk2018-1-15.apk",
"md5": "34db53f031fccf92b779e0d160ef37c1",
"size": 1824234
}
1、在package com.example.demo.domain
创建UpdateInfo.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class UpdateInfo {
// 是否有新版本
public boolean hasUpdate = false;
// 是否静默下载:有新版本时不提示直接下载
public boolean isSilent = false;
// 是否强制安装:不安装无法使用app
public boolean isForce = false;
// 是否下载完成后自动安装
public boolean isAutoInstall = true;
// 是否可忽略该版本
public boolean isIgnorable = true;
public int versionCode;
public String versionName;
public String updateContent;
public String url;
public String md5;
public long size;
}
2、在resources
目录下创建static/apk文件夹,将新版本apk复制到此目录
启动服务后,点击 http://10.129.51.27:8080/apk/apk2018-1-15.apk 弹窗文件下载框,表示文件位置正确。
3、创建AppConttroller.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72package com.example.demo.controller;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.math.BigInteger;
import java.security.MessageDigest;
import org.springframework.util.ResourceUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.example.demo.domain.UpdateInfo;
@RequestMapping("/app")
@RestController
public class AppConttroller {
/**
* http://10.129.51.27:8080/app/check?package=cn.czl.updatedemo&version=1&channel=main
*
* @return
* @throws FileNotFoundException
*/
@RequestMapping(value = "/check", method = RequestMethod.GET)
public UpdateInfo check(@RequestParam(name = "package", required = true) String pkg,
@RequestParam(name = "version", required = true) int version,
@RequestParam(name = "channel", required = true) String channel) throws FileNotFoundException {
UpdateInfo updateInfo = new UpdateInfo();
if (version > 1) {
updateInfo.hasUpdate = false;
} else {
updateInfo.hasUpdate = true;
updateInfo.versionCode = 2;
updateInfo.versionName = "v2.0.2018-1-16";
updateInfo.updateContent = "1、修改了bug1;\r\n" + "2、修改了bug2;\r\n" + "3、新增了某个功能。";
updateInfo.url = "http://10.129.51.27:8080/apk/apk2018-1-15.apk";
File file = ResourceUtils.getFile("classpath:static/apk/apk2018-1-15.apk");
updateInfo.md5 = md5(file);
updateInfo.size = file.length();
}
return updateInfo;
}
public static String md5(File file) {
MessageDigest digest = null;
FileInputStream fis = null;
byte[] buffer = new byte[1024];
try {
if (!file.isFile()) {
return "";
}
digest = MessageDigest.getInstance("MD5");
fis = new FileInputStream(file);
while (true) {
int len;
if ((len = fis.read(buffer, 0, 1024)) == -1) {
fis.close();
break;
}
digest.update(buffer, 0, len);
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
BigInteger var5 = new BigInteger(1, digest.digest());
return String.format("%1$032x", new Object[] { var5 });
}
}
在浏览器访问:
http://10.129.51.27:8080/app/check?package=cn.czl.updatedemo&version=1&channel=main
即可得到预想的json。
注意:
- 正常情况我们应该apk使用表单上传,更新信息应该从表单获取;
- 代码直接当version>1判断无更新,实际上应该查询数据库最新版本进行比较;
- md5是获取apk文件的md5,而不是打包签名文件的md5,具体的md5获取代码如上。
至此,服务端代码编写完成,详情可参考
czy1121Update/SpringBoot