@TOC
【56.Android P 9.0网络权限http】
解决方法:共四种
1、 如果一定要使用明文通信的话,则可以打开AndroidManifest.xml 文件,在 application 元素中添加:
android:usesCleartextTraffic="true"
- 为了安全,不建议上面的使用明文的通信方式,不过上面的这种方法可以作为一种临时的通信策略
- 如果声明不使用明文通信,则可以在application元素中添加:
android:usesCleartextTraffic=”false”
此声明指示该应用不使用明文网络通信,并使 Android Marshmallow 的平台网络堆栈禁止该应用中的明文通信。例如,如果您的应用意外尝试通过 HTTP 明文请求登录用户,该请求将被阻止,该用户的身份和密码信息不会泄露到网络上。
2、 项目改用https请求;
3、 项目的targetSdkVersion改为27以下;
4、 在res的xml目录下,新建一个xml文件(名称自定义,如 network_security_config.xml),内容如下:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
在manifest清单文件配置application:
<application
...
android:networkSecurityConfig="@xml/network_security_config"
...
/>
【57.布局分包】
模块build.gradle下添加代码,然后每个文件夹下需要有layout文件夹
android {
。。。
sourceSets {
main{
res.srcDirs=[
"src/main/res", //这个意思是全部资源,包括mipmap等,必须
"src/main/res/layout",
"src/main/res/layout/practice4",
"src/main/res/layout/practice3",
]
}
}
}
【58.mHandler在activity警告 --】
其一:(Activity中)
private final MHandler mHandler = new MHandler(this);
private static class MHandler extends Handler {
private final WeakReference<MainActivity> mActivity;
public MHandler(MainActivity activity) {
mActivity = new WeakReference<MainActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = mActivity.get();
if(activity!=null){
}
}
}
不规范的写法:
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
};
};
//另外一个办法
private Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
//但是不能引用外部方法了
return false;
}
});
其二:(自定义View中)
private TimerHandler mHandler;
//采用弱引用防止内存泄漏
private static final class TimerHandler extends Handler {
private WeakReference<StudyView> mView;
private TimerHandler(StudyView clockView) {
mView= new WeakReference<>(clockView);
}
@Override
public void handleMessage(Message msg) {
StudyView view = mView.get();
//isPlaying是StudyView里的变量
if (view != null && view.isPlaying) {
view.getTime(); //StudyView里的方法
view.invalidate();//重新绘制
sendEmptyMessageDelayed(1, 1000);//每1000毫秒一请求
}
}
}
原文链接:https://blog.csdn.net/qq_38363506/article/details/90903240
【其他警告】
@SuppressLint("SimpleDateFormat")
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-ddHH:mm:ss");
//解决
SimpleDateFormat newSimpleDateFormat = new SimpleDateFormat(
"yyyy年MM月dd日HH时mm分", Locale.getDefault());
@SuppressWarnings(“rawtypes”)和@SuppressWarnings({ “unchecked”, “rawtypes” })
不规范写法:Class clazz = Class.forName(“android.view.Display”);
正确写法:Class<?> clazz = Class.forName("android.view.Display");
【59.shape划线注意事项】
line划线时注意一下几点:
- 只能画水平线,画不了竖线;
- 线的高度是通过stroke的android:width属性设置的;
- size的android:height属性定义的是整个形状区域的高度;
- size的height必须大于stroke的width,否则,线无法显示;
- 线在整个形状区域中是居中显示的;
- 线左右两边会留有空白间距,线越粗,空白越大;
- 引用虚线的view需要添加属性android:layerType,值设为"software",否则显示不了虚线。
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="line">
<stroke
android:width="1dp"
android:color="#ff0000"/>
<size
android:height="4dp"/>
</shape>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="line">
<solid
android:color="#990000ff"/>
<stroke
android:width="1dp"
android:color="#00ff00"
android:dashWidth="4dp"
android:dashGap="4dp"/>
</shape>
【60.异或加解密】
//java.test里测试 加密与解密的代码相同
int pwd = 9;
String normal = "~!@#$%^&*()_+=-0";
//加密
int len = normal.length();
StringBuilder bui=new StringBuilder();
for(int i=0;i<len;i++){
System.out.println("char:"+normal.charAt(i));
int res=normal.charAt(i)^pwd;
bui.append((char)res); //强转为ASCII字符
}
System.out.println("加密后:"+bui.toString());
System.out.println("-------下面代码跟上面一样--------------");
String cry=bui.toString();
//解密
int len = cry.length();
StringBuilder bui=new StringBuilder();
for(int i=0;i<len;i++){
System.out.println("char:"+cry.charAt(i));
int res=cry.charAt(i)^pwd;
bui.append((char)res); //强转为ASCII字符
}
System.out.println("解密后:"+bui.toString());
【61。onPageScrollStateChanged的三个状态】
viewPager.addOnPageChangeListener(new ViewPager.OnPagerChangeListener(){
public void onPageScrolled(int position, float offset, int pix){
//参数2:偏移量0-1,滑动到一半可以用0.5 标识
//参数3:分辨率,比如1080P的,则就是0-1079
}
public void onPageSelected(int position){
//停止滑动后的位置
}
public void onPageScrollStateChanged(int arg0){
参数arg0有三种取值:
0:什么都没做
1:开始滑动
2:滑动结束
打印了一下滑动过程的顺序:
从滑动开始依次为:
argo== (1,2,0)
}
});
【62.Android转场动画】
https://www.jianshu.com/p/86ba2e1eb80c
【63.矢量图简介Vector】
1.两种方法来创建:
- 1)右击drawable-->Drawable resource file-->设置root
element为vector,这样的矢量图绘制逻辑完全掌握在开发者手里(自己绘制--》看下面的); - 2)右击drawable-->Vector
Asset,选择SVG或者PSD文件直接生成根标签为vector的xml文件,怎样把png转换成SVG(可以用 阿里iconfont 或者
[http://inloop.github.io/svg2android/]())。
作者:宛丘之上兮
链接:https://www.jianshu.com/p/0972a0d290e9
- width和height:当使用这个矢量图的View的宽高是wrap_content 的时候这两个属性才生效;
- ewportWidth和viewportHeight:决定画布的宽高,是定义的一个虚拟空间,方便编辑pathData属性,如果pathData中的点超出了这个虚拟空间,超出的部分将不会展现给用户;虚拟空间的原点仍然还是在左上角(R点就是原点)。
简单的vector:[https://blog.csdn.net/qq_35323561/article/details/80018898]()
【64.权限请求6.0 M 以上动态】
//1.简单写法
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
//两种方式
String[] perms = {"android.permission.RECORD_AUDIO",Manifest.permission.WRITE_EXTERNAL_STORAGE};
//因为是在Activity下,所以可以直接调用
if(checkSelfPermission(perms[0]) == PackageManager.PERMISSION_DENIED ||
checkSelfPermission(perms[1]) == PackageManager.PERMISSION_DENIED){
//自己在onRequestPermissonsResult处理
requestPermission(perms, 200);
//ActivityCompat.requestPermissions(this,perms,200);
} else {....}
}
//2.分开 [自己去封装工具类]
//应用
if(checkPermission()){
//用户拥有权限
...
} else {
//去请求
requestPermission();
}
//检查权限
private boolean checkPermission(){
int result = ContextCompat.checkSelfPermission(this,Manifest.permission.RECORD_AUDIO);
//如果权限授予了
if(result == PackageManager.PERMISSION_GRANTED){
return true;
} else {
return false;
}
}
//请求权限
private void requestPermission(){
//第一次被拒后 或者之前允许又在设置去掉了 走这里,所以仍要请求权限(但是可以弹窗说名原因)
if(ActivityCompat.shouldShowRequestPermissionRationale(this,perms[0])){
toast("请在设置里允许权限");
//我强制在弹窗请求
ActivityCompat.requestPermission(this,new String[]{perms[0],perms[1]}, 1);
} else {
//请求权限(两种情况)
//第一种:第一次请求权限
ActivityCompat.requestPermission(this,new String[]{perms[0],perms[1]}, 1);
//第二种:拒绝并选择“Never ask again”
}
}
//接受权限结果
@override
public void onRequestPermissonsResult(...){
if(requestCode == 1){
if(grantResults.length>0 && grantResults[0]==PackageManager.PERMISSION_GRANTED){
//申请成功
。。。逻辑代码
} else {
//用户拒绝
}
return;
}
super.onRequestPermissonsResult(...);
}
shouldShowRequestPermissionRationable 方法会返回以下两种情况:
- 返回true:
用户之前在申请权限操作时,点击了“拒绝”按钮,但是没有选中“Never ask again”选项。
处理方法—— 再次调用requestPermission方法申请权限。
返回false:
- 用户从来没有申请过此权限;
处理方法—— 直接调用 requestPermission方法申请权限。 - 用户之前选中拒绝,并勾选了“Never ask again”选项。
处理方法—— 弹出自定义对话框,提示用户此操作必须通过权限申请之后才能继续使用此功能,并给用户提供进入权限设置界面的入口。
- 用户从来没有申请过此权限;
注意: shouldShowRequestPermissionRationable 返回true的情况
在国内很多手机厂商中设置了自动屏蔽,也就是没有返回true的情况,比如华为、小米等手机。
外例
public void requestPerm(View view){
//版本高于23,需要动态申请
if(shouldAskPerm){
//判断是否已经授予权限
if(ContextCompat.checkSelfPermission(this,Manifest.permission.WRITE_CONTACTS)
!= PackageManager.PERMISSION_GRANTED){
//调用shouldShowRequestPer...判断用户之前的操作
if(ActivityCompat.shouldShowRequestPR(this,Manifest.permission.WRITE_CONTACTS)){
//用户再对话框中拒绝权限,并没有选中“Never ask again”
ActivityCompat.requestPermission(this,new String[]{Manifest.permission.WRITE_CONTACTS},REQUEST_CODE);
}else{
//第一种:第一次请求权限
ActivityCompat.requestPermission(this,new String[]{perms[0],perms[1]}, 1);
//第二种:拒绝并选择“Never ask again”
//这里需要再SharedPreference里设置第一次申请的操作,默认true,第一次申请后false
//跳转到设置里,去手动设置允许
}
} else{
//权限已申请,执行操作
。。。
}
} else{
//版本低于高于23
}
}
public boolean shouldAskPerm(){
return Build.VERSION.SDK_INT>=Build.VERSION_CODES.M;
}
checkSelfPermission 检查某权限是否已申请
requestPermissions 主动发送权限申请
shouldShowRequestPermissionRationale 判断用户之前对申请权限做出的动作
【65.RecyclerView刷新固定控件】--避免图片闪烁
https://blog.csdn.net/qq402164452/article/details/53464091
//注意这是三个参数的 onBindViewHolder
@Override
public void onBindViewHolder(ViewHolder holder, int position, List<Object> payloads) {
//为空时,就是最初的,初始化
if (payloads.isEmpty()) {
onBindViewHolder(holder, position);
} else {
holder.container.setBackgroundColor(position == mCurrentPosition ? ContextCompat.getColor(activity,R.color.color_FF35BAF3) : Color.TRANSPARENT);
}
}
【66.判断主子线程】
onCreate->onStart->onPostCreate->onResume->onPostResume
到onPostCreate时,Activity应该已经彻底跑起来了,这时可以测量View宽高
让View重绘, 需要先判断当前线程到底是不是主线程, 然后根据判断结果来决定到底是调用 invalidate() 还是 postInvalidate() 方法. 如果当前是主线程, 就调用 invalidate() 方法; 而如果当前是子线程, 就调用 postInvalidate() 方法, 注意: 子线程中不能调用 invalidate() 方法, 否则就会报异常, 提示我们不能在子线程中更新UI
//1.
public boolean isMainThread() {
return Looper.getMainLooper() == Looper.myLooper();
}
//2.
public boolean isMainThread() {
return Looper.getMainLooper().getThread() == Thread.currentThread();
}
//3.
public boolean isMainThread() {
return Looper.getMainLooper().getThread().getId() == Thread.currentThread().getId();
}
Android中切换到主线程更新方法:
?? View.post()方法在android7.0之前,可能会不生效,在异步线程view.post方法不执行的情况居多。建议使用Handler post方法代替。但Android 7.0之后不管在主线程还是在子线程都可以成功执行view.post内部逻辑(https://blog.csdn.net/longlong2015/article/details/88826269)
??在Android 7.0之后的手机上如果通过new创建的View,如果没有将它通过addView()加入到ViewGroup布局中,那通过View.post()发送出去的任务将不再执行,也就无法通过Viwe.post更新UI。
经验证7.0 以后 post可以执行,但是6.0却无法执行
在子线程中更新UI
//1.方法一: view.post(Runnable action) 【注意这个,在下面】
textView.post(new Runnable() {
@Override
public void run() {
textView.setText("更新textView");
}
});
//view.postDelayed(Runnable action, long delayMillis)用来延迟发送。
//2.方法二:runOnUiThread(Runnable action)
注意:context 对象要是 主线程中的MainActivity,这样强转才可以。
runOnUiThread(new Runnable() {
@Override
public void run() {
//此时已在主线程中,更新UI
}
});
//3.方法三:Handler机制
首先在主线程中定义Handler,Handler mainHandler = new Handler();(必须要在主线程中定义才能操作主线程,
如果想在其他地方定义声明时要这样写
Handler mainHandler = new Handler(Looper.getMainLooper()),来获取主线程的 Looper 和 Queue )
获取到 Handler 后就很简单了,用handler.post(Runnable r)方法把消息处理放在该 handler 依附的消息队列中(也就是主线程消息队列)。
(1)假如该方法是在子线程中
Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(new Runnable() {
@Override
public void run() {
//已在主线程中,更新UI
}
});
//Handler还有下面的方法:
//1.postAtTime(Runnable r, long uptimeMillis); //在某一时刻发送消息
//2.postAtDelayed(Runnable r, long delayMillis); //延迟delayMillis毫秒再发送消息
(2)假设在主线程中
Handler myHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch(msg.what) {
case 0:
//更新UI等
break;
default:
break;
}
}
}
之后可以把 mainHandler 当做参数传递在各个类之间,当需要更新UI时,可以调用sendMessage一系列方法来执行handleMessage里的操作。
子线程中发送消息到主线程更新UI
/**
*获取消息,尽量用obtainMessage()方法,查看源码发现,该方法节省内存。
*不提倡用Messenger msg=new Messenger()这种方法,每次都去创建一个对象,肯定不节省内存啦!
*至于为什么该方法还存在,估计还是有存在的必要吧。(留作以后深入研究)
*/
new Thread(new Runnable(){
@Override
public void run() {
//耗时操作,完成之后发送消息给Handler,完成UI更新;
mHandler.sendEmptyMessage(0);
//需要数据传递,用下面方法;
Message msg =new Message();
msg.obj = "数据";//可以是基本类型,可以是对象,可以是List、map等;
mHandler.sendMessage(msg);
myHandler.sendEmptyMessage(0); //其实内部实现还是和上面一样
endEmptyMessageAtTime(int what, long uptimeMillis); //定时发送空消息
sendEmptyMessageDelayed(int what, long delayMillis); //延时发送空消息
sendMessageAtTime(Message msg, long uptimeMillis); //定时发送消息
sendMessageDelayed(Message msg, long delayMillis); //延时发送消息
sendMessageAtFrontOfQueue(Message msg); //最先处理消息(慎用)
}
}).start();
//方法四: AsyncTask
/**
* 该类中方法的执行顺序依次为:onPreExecute, doInBackground, onPostExecute
注意:doInBackground方法是在子线程中,所以,我们在这个方法里面执行耗时操作。
同时,由于其返回结果会传递到onPostExecute方法中,
而onPostExecute方法工作在UI线程,这样我们就在这个方法里面更新ui,达到了异步更新ui的目的。
*/
private class MyAsyncTask extends AsyncTask<String, Integer, String> {
/**
* 主线程中执行
* 在execute()被调用后首先执行
* 一般用来在执行后台任务前对UI做一些标记
*/
@Override
protected void onPreExecute() {
super.onPreExecute();
System.out.println("MyAsyncTask.onPreExecute");
}
/**
* 子线程中执行,执行一些耗时操作,关键方法
* 在执行过程中可以调用publishProgress(Progress... values)来更新进度信息。
*/
@Override
protected String doInBackground(String... params) {
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();
}
}
// publishProgress((int) ((count / (float) total) * 100));
return "耗时操作执行完毕";
}
/**
* 主线程中执行
* 在调用publishProgress(Progress... values)时,此方法被执行,直接将进度信息更新到UI组件中
*/
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
progressBar.setProgress(values[0]);
textView.setText("loading..." + values[0] + "%");
System.out.println("MyAsyncTask.onProgressUpdate");
}
/**
* 在主线程中,当后台操作结束时,此方法将会被调用
* 计算结果将做为参数传递到此方法中,直接将结果显示到UI组件上。
*/
@Override
protected void onPostExecute(String aVoid) {
super.onPostExecute(aVoid);
System.out.println("MyAsyncTask.onPostExecute aVoid=" + aVoid);
textView.setText(aVoid);
}
/**
* 主线程中执行
* 当异步任务取消后的,会回调该函数。在该方法内可以更新UI
*/
@Override
protected void onCancelled() {
super.onCancelled();
System.out.println("MyAsyncTask.onCancelled");
progressBar.setProgress(0);
textView.setText("0");
}
@Override
protected void onCancelled(String s) {
super.onCancelled(s);
}
}
【67.重用布局 <include/> <merge/>】
注意点:
< include >
- 重写layout_*的属性记得先重写 android:layout_height 和android:layout_width。
- include如果指定了id,那么layout属性的根视图id会被强制修改成include中的id,如果不注意很容易出现空指针问题。
< merge >
- 复用在LinearLayout和RelativeLayout中会有不同的表现,在前者会以线性的方式布局,后者delete按钮会遮挡add按钮,所以使用merge标签一定要注意实际的根视图类型
- merge必须放在布局文件的根节点上
- merge并不是一个ViewGroup,也不是一个View,它相当于声明了一些视图,等待被添加。
- 因为merge标签并不是View,所以在通过LayoutInflate.inflate方法渲染的时候, 第二个参数必须指定一个父容器,且第三个参数必须为true,也就是必须为merge下的视图指定一个父亲节点。
- 因为merge不是View,所以对merge标签设置的所有属性都是无效的
- 如果Activity的布局文件根节点是FrameLayout,可以替换为merge标签,这样,执行setContentView之后,会减少一层FrameLayout节点。
- 自定义XXXLayout控件时,如果使用LayoutInflater.inflate(R.layout.xxx, this, true)填充视图,那么该布局的根元素最好设置成,这一点其实是和上一点相同的,有助于直接减少视图层级。
< ViewStub> 懒加载View
你的布局中可能存在很少情况下才用到的复杂布局,比如单条详情、进图条或者是一些撤销消息等等,这些布局可以只在你需要的时候才加载以提升布局的渲染速度。
定义ViewStub
ViewStub 是一个轻量级的视图,它不参与绘制也不参与任何的布局工作。因此,它在视图层级的构建中消耗的资源是非常小的。每一个ViewStub在使用时只需要通过android:layout去定义它需要加载布局文件即可。
下面给出的ViewStub承载了一个透明的进度条,它只在特定情况下才需要展现给用户。
<ViewStub
android:id="@+id/stub_import"
android:inflatedId="@+id/panel_import"
android:layout="@layout/progress_overlay"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom" />
加载ViewStub布局
当我们需要让ViewStub承载的视图展现时,只需要通过调用setVisibility(View.VISIBLE)或者inflate()方法即可
((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);
// or
View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();
一旦ViewStub被可见或者被布局了,那么它就从视图层级中剥离出来,取代ViewStub存在于视图层级的是android:layout属性所指定的布局,该布局的id可以通过android:inflatedId指定。
这里和include一样,android:inflatedId属性也会覆盖layout中根视图的id。
注意点
- ViewStub只能被inflate一次,多次调用会出异常。第一次setVisibility(View.Visibility)会被动调用一次inflate,因此需要注意。
- ViewStub被inflate之后会从视图层级中移除,因此再次调用findViewById尝试获取ViewStub对象会返回空,不要尝试使用该对象,否则会出现空指针。
- ViewStub中layout_属性都是为新加载的视图的根视图设置的,与 < include > 标签一样,ViewStub加载的根视图自身的layout_属性会被ViewStub重写。比如layout_height,它不能指定ViewStub本身的高度,因为ViewStub本身的高度和宽度都是0,它指定的其实是需要加载的布局的根视图高度。又由于此,在布局时要注意基于ViewStub的相对布局在ViewStub未inflate之前,位置与实际位置是有偏差的。
- 一般xml文件中定义的属性都可以通过代码设置,同样ViewStub也可以通过方法setLayoutResource在代码中动态设置应该加载的layout文件,此时一个ViewStub就可以根据逻辑不同使用不同的视图。
【67.5. Parcelable和Serializable的效率对比】
Parcelable和Serializable的效率对比 Parcelable vs Serializable 号称快10倍的效率.
- 内存序列化上选择Parcelable,
- 存储到设备或者网络传输上选择Serializable(当然Parcelable也可以但是稍显复杂)
选择序列化方法的原则
1)在使用内存的时候,Parcelable比Serializable性能高,所以推荐使用Parcelable。
2)Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC。
3)Parcelable不能使用在要将数据存储在磁盘上的情况,因为Parcelable不能很好的保证数据的持续性在外界有变化的情况下。尽管Serializable效率低点,但此时还是建议使用Serializable 。
链接:https://www.jianshu.com/p/df35baa91541
【待测试】
//写在android下面
applicationVariants.all { variant ->
variant.outputs.all {
outputFileName = "MyAppName${variant.versionName}_${releaseTime()}_${variant.name}.apk"
}
}
//放到build里面跟apply同级就行了。
static def releaseTime() {
SimpleDateFormat str = new SimpleDateFormat("yyyy_MM_dd_KK_mm")
return str.format(new Date())
}