精华内容
下载资源
问答
  • 本实例提供了个实体属性变更历史记录工具,只要写很少的代码就能实现强大的变更历史记录功能.本工具的主要优点是1.工具对实体对象没有依赖性,任何对象都能记录历史. 先看看如何使用吧: 1.得到变化前后的...

      在实际mis项目中增删改查必不可少,针对"改"的操作,重要的项目中都要有变更历史记录.本实例提供了一个实体属性变更历史记录工具类,只要写很少的代码就能实现强大的变更历史记录功能.本工具类的主要优点是1.工具类对实体对象没有依赖性,任何对象都能记录历史.

    先看看如何使用吧:
    1.得到变化前后的对象.
    2.调用下面方法传入第一步中的值.
      HistoryUtil util = new HistoryUtil();
      util.record(类名, 变化前的对象, 变化后的对象,实体Id,修改人);

    例子详细代码:

    package com.tgb.lk.history;
    
    public class HistoryTest {
    	public static void main(String[] args) {
    		Student s = new Student();
    		s.setId(1);
    		s.setName("李坤");
    		s.setSex("男");
    		s.setClazz("五期提高班");
    
    		// 可以使用commons-beanutils-xxx.jar中的下面这个方法来保留原对象.
    		// BeanUtils.copyProperties(dest, src);
    
    		Student s2 = new Student();
    		s2.setId(1);
    		s2.setName("李佳");
    		s2.setSex("女");
    
    		// HistoryUtil util = new HistoryUtil();
    		// util.record(Student.class, s, s2,"1","admin");
    
    		HistoryUtil<Student> util = new HistoryUtil<Student>();//方法内部有输出
    		util.record(Student.class, s, s2, "1", "admin");
    	}
    }
    

    例子输出结果:


    实体类:
    package com.tgb.lk.history;
    
    public class Student {
    	private int id;
    	private String name;
    	private String sex;
    	private String clazz;
    
    	//get和set方法
    	@Override
    	public String toString() {
    		return "Student [clazz=" + clazz + ", id=" + id + ", name=" + name
    				+ ", sex=" + sex + "]";
    	}
    }
    
    核心逻辑:
    变更历史实体类:

    package com.tgb.lk.history;
    
    import java.util.Date;
    
    public class History {
    	private int id;
    	private String entity; // 实体类名,标记是哪个实体类
    	private String entityId; // 实体对象的Id
    	private String property; // 实体的属性名,标记实体的哪个属性发生修改
    	private String oldValue; // 原属性值
    	private String newValue; // 新属性值
    	private String user; // 修改人
    	private Date modifyDate; // 修改时间
    	private String desc; // 描述
    
    	//get和set方法略
    
    	@Override
    	public String toString() {
    		return "\n History [id=" + id + ", entity=" + entity + ",\n entityId="
    				+ entityId + ", property=" + property + ", oldValue="
    				+ oldValue + ", newValue=" + newValue + ",\n user=" + user
    				+ ", modifyDate=" + modifyDate + ", desc=" + desc + "]\n";
    	}
    }
    
    HistoryUtil类(注意需要修改一下注释中添加入库和批量入库的实现):
    package com.tgb.lk.history;
    
    import java.lang.reflect.Field;
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.List;
    
    /*
     * @author likun 
     * 记录属性变更历史的工具类,使用步骤见main方法.
     * HistoryUtil util = new HistoryUtil();
     * util.record(Student.class, s, s2,"1","admin");
     */
    public class HistoryUtil<T> {
    
    	/**
    	 * 支持自己定义一个History对象并保存入库.
    	 * 
    	 * @param history
    	 */
    	public void record(History history) {
    		System.out.println(history);
    		// 调用添加入库方法.
    	}
    
    	// 批量保存到数据库中,考虑到对象修改的属性可能较多,所以采用批量导入效率会高一些.
    	public void record(List<History> historys) {
    		System.out.println(historys);
    		// 调用批量添加入库方法.
    	}
    
    	/**
    	 * 比较两个对象哪些属性发生变化,将变化的属性保存为History对象.
    	 * 
    	 * @param clazz
    	 *            修改类
    	 * @param oldObj
    	 *            老对象
    	 * @param newObj
    	 *            新对象
    	 * @param entityId
    	 *            实体Id
    	 * @param user
    	 *            修改人
    	 */
    	public void record(Class<T> clazz, T oldObj, T newObj,
    			String entityId, String user) {
    		if (oldObj == newObj) {
    			return;// 如果两个对象相同直接退出
    		}
    		
    		List<History> list = new ArrayList<History>();
    		Field[] allFields = clazz.getDeclaredFields();// 得到指定类的所有属性Field.
    		
    		for (Field field : allFields) {
    			field.setAccessible(true);// 设置类的私有字段属性可访问.
    			try {
    				if (!field.get(oldObj).equals(field.get(newObj))) {
    					History history = new History();
    					history.setEntity(clazz.toString());
    					history.setProperty(field.getName());
    					history.setOldValue(String.valueOf(field.get(oldObj)));
    					history.setNewValue(String.valueOf(field.get(newObj)));
    					history.setModifyDate(new Date());
    					history.setEntityId(entityId);// 记录修改的对象的主键Id.
    					history.setUser(user);// 记录修改者
    					list.add(history);
    				}
    			} catch (IllegalArgumentException e) {
    				e.printStackTrace();
    			} catch (IllegalAccessException e) {
    				e.printStackTrace();
    			}
    		}
    		record(list);// 调用保存入库的方法.
    	}
    
    }
    


    这篇文章中是属性变更历史记录框架的核心逻辑,看看你是否有这样的需求:
    1.所有属性都记录变更历史太多,有些属性我不想记录变更历史.
    2.实体属性变化前后的值需要把编码转成有意义的文字.例如:实体类中性别用0,1表示,而希望变更历史中记录前后变化的值是"男","女".
    3.传入实体对象自动识别实体Id,而不需要手工传入.
    4.实体的属性名不好记,想加一个好记别名,这样便于查询.

    请看下篇文章:http://blog.csdn.net/lk_blog/article/details/8092925


    实体属性变更历史记录框架(一)-变更历史记录从此无忧(http://blog.csdn.net/lk_blog/article/details/8007777)

    实体属性变更历史记录框架(二)-变更历史记录从此无忧(http://blog.csdn.net/lk_blog/article/details/8092925)


    限于本人水平有限,很多地方写的并不完美,希望大家不吝赐教.如果觉得本文对您有帮助请顶支持一下,如果有不足之处欢迎留言交流,希望在和大家的交流中得到提高.

    代码下载:http://download.csdn.net/detail/lk_blog/4667643

    展开全文
  • ASM(二) 利用Core API 变更类成员

    万次阅读 2015-06-13 17:32:38
    篇把这producer(ClassReader)和consumer(ClassWriter)来结合起来介绍一下如何动态添加 移除 Java 成员以及利用一些ASM工具

          之前一篇简单介绍了一下ASM框架。这一篇继续对CoreApi进行扩展。这里还是继续对ClassWriter ,ClassReader和ClassVisitor的应用的扩展。前面一篇主要介绍的是ClassWriter和ClassReader单独应用的场景。这一篇把这两者作为producer(ClassReader)和consumer(ClassWriter)来结合起来介绍一下另外一些用途。

      一、迁移转换类

        事件的生产者ClassReader通过accept方法可以传递给ClassWriter。上一篇我们知道ClassWriter继承自ClassVisitor。而ClassReader可以接收ClassVisitor具体实现类,通过顺序访问实现类的方法来解析整个class文件结构。先看个例子。为了简便,我们读取一个现成的class文件ChildClass.class(前一篇用ASM生成的class,源码见前一篇)。然后经过解析拿到一个ClassReader实例。然后再通过ClassWriter重新构造了一个Class ,通过cw.toByteArray()返回一个和前面一样的Class 的字节数组。

     

    package asm.core;
     
    import org.objectweb.asm.ClassReader;
    import org.objectweb.asm.ClassVisitor;
    import org.objectweb.asm.ClassWriter;
     
    import java.io.*;
     
    /**
     * Created by yunshen.ljy on 2015/6/9.
     */
    public class TransformClasses {
     
        public static void main(String[] args) throws IOException {
            File file = new File("ChildClass.class");
            InputStream input = new FileInputStream(file);
            // 构造一个byte数组
            byte[] byt = new byte[input.available()];
            input.read(byt);
            ClassWriter cw = new ClassWriter(0);
            ClassVisitor cv = new ClassVisitor (cw){};
           //  改变class的访问修饰
           //  ClassVisitor cv = new ChangeAccessAdapter(cw);
            ClassReader cr = new ClassReader(byt);
            cr.accept(cv, 0);
            byte[] toByte = cw.toByteArray();// byt 和toByte其实是相同的数组
            // 输出到class文件
            File tofile = new File("ChildClass.class");
            FileOutputStream fout = new FileOutputStream(tofile);
            fout.write(toByte);
            fout.close();
     
        }
    }

        光这样解析然后构造一个相同的Class觉得没什么实际意义,但是我们注意到ClassVisitor 可以接收一个ClassVisitor 实例,而ClassWriter 作为Visitor的子类,是可以被Visitor接收调用的。。ASM官方文档的下面这张图,很好地描述了整个调用链。而这其中也可以套用更多的adapter层层传递,顺序调用。


        所以我们这里可以创建一个定制化的Visitor。ClassVisitor cv = new ChangeAccessAdapter(cw);这行我们去掉注释再看看,这里我们写了一个自己的ClassVisitor来修改class的访问修饰。把public abstract变成public。根据第一篇的介绍,我们需要自己实现visit方法,并设置访问参数。ChangeAccessAdapter 代码如下:

     

    package asm.core;
     
    import org.objectweb.asm.ClassVisitor;
    import org.objectweb.asm.Opcodes;
     
    /**
     * Created by yunshen.ljy on 2015/6/10.
     */
    public class ChangeAccessAdapter extends ClassVisitor {
     
        public ChangeAccessAdapter(ClassVisitor cv) {
            super(Opcodes.ASM4, cv);
        }
        @Override
        public void visit(int version, int access, String name,
                          String signature, String superName, String[] interfaces) {
            cv.visit(version, Opcodes.ACC_PUBLIC , name, signature, superName, interfaces);
        }
     
    }

      二、移除类成员

             通过visit()方法,我们可以访问、解析类成员。当我们需要移除一个类成员,比如InnerClass、OuterClass就可以直接通过继承响应的visitOuterClass、visitInnerClass方法,但是不去实现方法体来达到移除目的。Method和Field成员的移除需要终止下一层继续调用,也就是返回null 而不是MethodVisitor 或者FieldVisitor实例。例子中需要移除的Class 还是以第一篇的Task 类为例。这次我们加入了一个内部类给Task。代码如下:


    package asm.core;
     
    /**
     * Created by yunshen.ljy on 2015/6/8.
     */
    public class Task {
     
        private int isTask = 0;
     
        private long tell = 0;
     
        public void isTask(boolean test){
            System.out.println("call isTask");
        }
        public void tellMe(){
            System.out.println("call tellMe");
        }
     
        class TaskInner{
            int inner;
        }
    }


       我们这次把Task的内部类以及 isTask方法移除,一样,需要实现自己的ClassVisitor,Visitor 代码如下。 


    package asm.core;
     
    import org.objectweb.asm.ClassVisitor;
    import org.objectweb.asm.ClassWriter;
    import org.objectweb.asm.MethodVisitor;
    import org.objectweb.asm.Opcodes;
     
    /**
     * Created by yunshen.ljy on 2015/6/12.
     */
    public class RemovingClassesVisitor extends ClassVisitor{
     
        public RemovingClassesVisitor(int api) {
            super(api);
        }
     
        public RemovingClassesVisitor(ClassWriter cw) {
            super(Opcodes.ASM4,cw);
        }
     
        // 移除内部类
        @Override
        public void visitInnerClass(String name, String outerName, String innerName, int access) {
     
        }
     
        @Override
        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
            if (name.startsWith("is")) {
                // 移除以is开头的方法名的方法
                return null;
            }
            return cv.visitMethod(access, name, desc, signature, exceptions);
        }
    }

        下面就来构造整个调用链,将移除后的class字节流输出到文件中:  

    package asm.core;
    import org.objectweb.asm.ClassReader;
    import org.objectweb.asm.ClassVisitor;
    import org.objectweb.asm.ClassWriter;
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
     
    public class RemovingClassesTest {
        public static void main(String[] args) throws IOException {
            ClassReader cr = new ClassReader("asm.core.Task");
            ClassWriter cw = new ClassWriter(0);
            ClassVisitor cv = new RemovingClassesVisitor(cw);
            cr.accept(cv, 0);
            byte[] toByte = cw.toByteArray();// byt 和toByte其实是相同的数组
            // 输出到class文件
            File file = new File("Task.class");
            FileOutputStream fout = new FileOutputStream(file);
            fout.write(toByte);
            fout.close();
        }
     
    }

      然后,Task.class 文件就变成了下面我们期望的class文件。isTask()方法已经被移除。

    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by Fernflower decompiler)
    //
     
    package asm.core;
     
    public class Task {
        private int isTask = 0;
        private long tell = 0L;
     
        public Task() {
        }
     
        public void tellMe() {
            System.out.println("call tellMe");
        }
    }


     三、添加类成员

       添加类成员,我们一样需要继承ClassVisitor 来写我们自己的适配器。移除的情况,我们是终止class字节流的遍历和调用。那么添加的时候我们就需要去多调用一次visitField或者visitMethod方法。但这里我们需要注意的一点是,如果我们无法单纯在visit方法中去添加一个FieldVisitor或MehtodVisitor实例来实现再次调用visitField或者visitMethod。因为ASM是按照顺序来解析class二进制字节流的,visit方法后续还会再次触发visitSource, visitOuterClass, visitAttribute,等方法。那么实现在visitField或者visitMethod方法中也会有问题,因为比如每次调用visitField方法,会重复产生很多你需要添加的Field。

        为了解决这个问题,我们可以在visitEnd方法中去实际添加类成员(因为visitEnd方法总是会被调用到),在visitField方法中加入判断是否已经存在类成员,再继续往下执行。也就是通过counter的方式,防止重复添加,我们可以在每个新加的属性上加一个counter,也可以添加一个计数方法分别在每个方法中调用。

       下面看一个简单的例子。首先先写一个adapter 来添加类成员。例子中我们添加一个私有的int类型的Filed 到Task.class中。我们把counter写在visitField中,判断是否已经有这个属性,如果没有,进行一次标记。然后在visitEnd中去构建。

     

    package asm.core;
     
    import org.objectweb.asm.ClassVisitor;
    import org.objectweb.asm.FieldVisitor;
    import org.objectweb.asm.Opcodes;
     
    /**
     * Created by yunshen.ljy on 2015/6/13.
     */
    public class AddingClassesVisitor  extends ClassVisitor {
     
     
        private int fAcc;
        private String fName;
        private String fDesc;
        private boolean isFieldPresent;
        public AddingClassesVisitor(ClassVisitor cv, int fAcc, String fName,
                               String fDesc) {
            super(Opcodes.ASM4, cv);
            this.fAcc = fAcc;
            this.fName = fName;
            this.fDesc = fDesc;
        }
        @Override
        public FieldVisitor visitField(int access, String name, String desc,
                                       String signature, Object value) {
            if (name.equals(fName)) {
                isFieldPresent = true;
            }
            return cv.visitField(access, name, desc, signature, value);
        }
        @Override
        public void visitEnd() {
            if (!isFieldPresent) {
                FieldVisitor fv = cv.visitField(fAcc, fName, fDesc, null, null);
                if (fv != null) {
                    fv.visitEnd();
                }
            }
            cv.visitEnd();
        }
    }


          在visitEnd方法中我们需要判断FieldVisitor实例是否为空,因为visitField方法的实现中,是会有返回null的情况。

          调用的代码中,只要把前面的Test类替换成如下的调用就可以了


         ClassReader cr = new ClassReader("asm.core.Task");
            ClassWriter cw = new ClassWriter(0);
            ClassVisitor cv = new AddingClassesVisitor(cw, Opcodes.ACC_PRIVATE,"addedField","I");
            cr.accept(cv, 0);

       再来看一下这次生成的Task.class 已经添加了我们期望的类成员。

     

    package asm.core;
     
    public class Task {
        private int isTask = 0;
        private long tell = 0L;
        private int addedField;
     
        public Task() {
        }
     
        public void isTask(boolean test) {
            System.out.println("call isTask");
        }
     
        public void tellMe() {
            System.out.println("call tellMe");
        }
    }


         这里我们发现,可以把各种adapter链式调用,来实现复杂的调用链,定制更加复杂的逻辑。我们可以在外层链式调用,ClassVisitor vca = new AClassVisitor(classWriter);ClassVisitor cvb= new BClassVisitor(cva)…。也可以通过传入一个调用链数组给一个Adalter。这里直接把官方说明文档的例子拿出来看下MultiClassAdapter 就是我们的ClassVisitor 的“总代理”:

    package asm.core;
     
    import org.objectweb.asm.ClassVisitor;
    import org.objectweb.asm.Opcodes;
     
     
    public class MultiClassAdapter extends ClassVisitor {
        protected ClassVisitor[] cvs;
        public MultiClassAdapter(ClassVisitor[] cvs) {
            super(Opcodes.ASM4);
            this.cvs = cvs;
        }
        @Override public void visit(int version, int access, String name,
                                    String signature, String superName, String[] interfaces) {
            for (ClassVisitor cv : cvs) {
                cv.visit(version, access, name, signature, superName, interfaces);
            }
        }
    }
     

     

    四、工具Api    

        ASM的Core API 中给我们提供了一些工具类,都在 org.objectweb.asm.util包中。有TraceClassVisitor、CheckClassAdapter、ASMifier、Type等。通过这些工具类,能更方便实现我们的动态生成字节码逻辑。这里就简述一下TraceClassVisitor 。

       TraceClassVisitor 顾名思义,我们可以“trace”也就是打印一些信息,这些信息就是ClassWriter 提供给我们的byte字节数组。因为我们阅读一个二进制字节流还是比较难以理解和解析一个类文件的结构。TraceClassVisitor通过初始化一个classWriter 和一个Printer对象,来实现打印我们需要的字节流信息。通过TraceClassVisitor 我们能更好地比较两个类文件,更轻松得分析class的数据结构。

      下面看个例子,我们用TraceClassVisitor 来打印Task 类信息。

    package asm.core;
     
    import org.objectweb.asm.ClassReader;
    import org.objectweb.asm.ClassWriter;
    import org.objectweb.asm.util.TraceClassVisitor;
     
    import java.io.IOException;
    import java.io.PrintWriter;
     
    /**
     * Created by yunshen.ljy on 2015/6/13.
     */
    public class TraceClassVisitorTest {
     
        public static void main(String[] args) throws IOException {
            ClassReader cr = new ClassReader("asm.core.Task");
            ClassWriter cw = new ClassWriter(0);
            TraceClassVisitor cv = new TraceClassVisitor(cw, new PrintWriter(System.out));
            cr.accept(cv, 0);
        }
    }

     控制台的结果如下,Task的类的局部变量表、操作数栈的一些信息也能打印出来,这比看二进制字节码文件舒服多了。

    // class version 50.0 (50)
    // access flags 0x21
    public class asm/core/Task {
     
      // compiled from: Task.java
      // access flags 0x0
      INNERCLASS asm/core/Task$TaskInner asm/core/Task TaskInner
     
      // access flags 0x2
      private I isTask
     
      // access flags 0x2
      private J tell
     
      // access flags 0x1
      public <init>()V
       L0
        LINENUMBER 6 L0
        ALOAD 0
        INVOKESPECIAL java/lang/Object.<init> ()V
       L1
        LINENUMBER 8 L1
        ALOAD 0
        ICONST_0
        PUTFIELD asm/core/Task.isTask : I
       L2
        LINENUMBER 10 L2
        ALOAD 0
        LCONST_0
        PUTFIELD asm/core/Task.tell : J
       L3
        LINENUMBER 19 L3
        RETURN
       L4
        LOCALVARIABLE this Lasm/core/Task; L0 L4 0
        MAXSTACK = 3
        MAXLOCALS = 1
     
      // access flags 0x1
      public isTask(Z)V
       L0
        LINENUMBER 13 L0
        GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
        LDC "call isTask"
        INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
       L1
        LINENUMBER 14 L1
        RETURN
       L2
        LOCALVARIABLE this Lasm/core/Task; L0 L2 0
        LOCALVARIABLE test Z L0 L2 1
        MAXSTACK = 2
        MAXLOCALS = 2
     
      // access flags 0x1
      public tellMe()V
       L0
        LINENUMBER 16 L0
        GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
        LDC "call tellMe"
        INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
       L1
        LINENUMBER 17 L1
        RETURN
       L2
        LOCALVARIABLE this Lasm/core/Task; L0 L2 0
        MAXSTACK = 2
        MAXLOCALS = 1
    }

          ASM框架的CoreApi的基础类已经介绍完毕。后面会陆续介绍CoreApi 中的Methods接口和组件。以及TreeApi。在Methods 类库之前,需要先了解下JVM中的运行期方法调用和执行,能帮助我们更好地理解怎么样用ASM实现动态扩展。

    展开全文
  • WPF 属性变更通知的实现

    千次阅读 2013-06-15 11:24:57
    平时用依赖属性多一些,普通属性的变更通知知道有这个方法,但是老是忘记名字,再写遍吧。 public class Student : INotifyPropertyChanged { private string studentID; public string StudentID { get { ...

    平时用依赖属性多一些,普通属性的变更通知知道有这个方法,但是老是忘记名字,再写一遍吧。

    public class Student : INotifyPropertyChanged
    {
    private string studentID;
    public string StudentID
    {
    get { return studentID; }
    set
    {
    studentID = value;
    NotifyPropertyChange("StudentID");
    }
    }
    private string studentName;
    public string StudentName
    {
    get { return studentName; }
    set
    {
    studentName = value;
    NotifyPropertyChange("StudentName");
    }
    }
    
    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChange(string propertyName)
    {
    if (PropertyChanged != null)
    {
    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
    }


    展开全文
  • 如果您不打算以 Android Q 为目标平台,那么其中一些变更可能不会立即对您产生影响。虽然您目前可以使用灰名单中的一些非 SDK 接口(取决于您应用的目标 API 级别),但如果您使用任何非 SDK 方法或字段,则应用无法...

    非 SDK 接口限制

    官方明确指出:目标是在限制使用非 SDK 接口之前确保有可用的公开替代方案。
    反射或JNI必须要有替代方案!

    如果您不打算以 Android Q 为目标平台,那么其中一些变更可能不会立即对您产生影响。虽然您目前可以使用灰名单中的一些非 SDK 接口(取决于您应用的目标 API 级别),但如果您使用任何非 SDK 方法或字段,则应用无法运行的风险终归较高。
    要了解详情,请参阅非 SDK 接口在 Android Q 中的受限情况出现变化以及针对非 SDK 接口的限制

    手势导航

    从 Android Q 开始,增加 手势导航 功能。
    若启用手势导航,则会影响设备上的所有应用,无论应用是否以 Android Q 为目标平台,都是如此。
    例如,如果用户从屏幕边缘向内滑动,系统会将该手势解读为“返回”导航,除非应用针对屏幕的相应部分明确替换该手势

    导航栏区域,系统手势区域,可能需要将这些重叠区域排除出去。
    为了确保App与手势导航兼容,您需要将应用内容扩展到屏幕边缘,并适当地处理存在冲突的手势。有关信息,请参阅手势导航文档。

    NDK

    从 Android Q 开始,共享对象不得包含文本重定位被强制执行

    Bionic 库和动态链接器路径变更

    从 Android Q 开始,多个路径不再采用常规文件形式,而是采用符号链接形式。如果应用一直以来依赖的都是采用常规文件形式的路径,则可能会出现故障:

    • /system/lib/libc.so -> /apex/com.android.runtime/lib/bionic/libc.so
    • /system/lib/libm.so -> /apex/com.android.runtime/lib/bionic/libm.so
    • /system/lib/libdl.so -> /apex/com.android.runtime/lib/bionic/libdl.so
    • /system/bin/linker -> /apex/com.android.runtime/bin/linker

    这些变更也会影响文件的 64 位版本,对于这些版本,会将 lib/ 替换为 lib64/。

    为了确保兼容性,新符号链接会基于旧路径提供,例如 /system/lib/libc.so 现在是指向 /apex/com.android.runtime/lib/bionic/libc.so 的符号链接,等等。因此,dlopen(“/system/lib/libc.so”) 会继续工作,但当应用尝试通过读取 /proc/self/maps 或类似项来检测已加载的库时,将会发现不同之处。这并不常见,但我们发现一些应用会将这种做法作为对抗黑客攻击的一项举措。如果是这样,则应该将新的 /apex/… 路径添加为 Bionic 文件的有效路径。

    系统二进制文件/库会映射到只执行内存

    从 Android Q 开始,系统二进制文件和库会映射到只执行(不可读取)内存,作为应对代码重用攻击的安全强化技术。有意或意外读入已标记为只执行的内存段会抛出 SIGSEGV,无论此读入行为是来自错误、漏洞还是有意的内存自省都不例外。

    您可以通过检查 /data/tombstones/ 中的相关 tombstone 文件来确定崩溃是否由变更改所导致。与只执行相关的崩溃包含以下中止消息:

    Cause: execute-only (no-read) memory access error; likely due to data in .text.
    

    要解决此问题,开发者可以通过调用 mprotect() 将只执行内存段标记为**“读取+执行”**,例如用于执行内存检查。不过,我们强烈建议您事后将其重新设为只执行,因为这样可以更好地保护您的应用和用户。

    ptrace 的调用不会受到影响,因此 ptrace 调试也不会受到影响。

    安全性

    移除了应用主目录的执行权限

    以 Android Q 为targetApi的不受信任的应用,无法再针对应用主目录中的文件调用 exec()。这种从可写应用的主目录执行文件的行为违反了 W^X。
    应用应该仅加载嵌入到应用的 APK 文件中的二进制代码

    也就是说下载到应用目录的文件,无法被exec()。

    此外,以 Android Q 为目标平台的应用无法针对已执行 dlopen() 的文件中的可执行代码进行内存中修改。这包括含有文本重定位的所有共享对象 (.so) 文件。

    WLAN 直连广播

    在 Android Q 中,以下与 WLAN 直连相关的广播不再具有粘性(sticky)。

    • WIFI_P2P_CONNECTION_CHANGED_ACTION
    • WIFI_P2P_THIS_DEVICE_CHANGED_ACTION
      如果您的应用依赖于在注册时接收这些广播(因为其之前一直具有粘性),请在初始化时使用适当的 get() 方法获取信息。

    WLAN 感知功能

    Android Q 扩大了支持范围,现在可以使用 WLAN 感知数据路径轻松创建 TCP/UDP 套接字。

    • 要创建连接到 ServerSocket 的 TCP/UDP 套接字,客户端设备需要知道服务器的 IPv6 地址和端口。
    • 这在之前需要通过频外方式进行通信(例如使用 BT 或 WLAN 感知第 2 层消息传递),或者使用其他协议(例如 mDNS)通过频内方式发现。
    • 而借助 Android Q,可以将此类消息作为网络设置的一部分进行传递。

    服务器可以执行以下任一操作:

    • 初始化 ServerSocket 并设置或获取要使用的端口。
    • 将端口信息指定为 WLAN 感知网络请求的一部分。

    Go 设备上的 SYSTEM_ALERT_WINDOW

    在 Android Q(Go 版本)设备上运行的应用无法获得 SYSTEM_ALERT_WINDOW 权限
    这是因为绘制叠加层窗口会使用过多的内存,这对低内存 Android 设备的性能十分有害

    如果在搭载 Android 9 或更低版本的 Go 版设备上运行的应用获得了 SYSTEM_ALERT_WINDOW 权限,则即使设备升级到 Android Q 也会保留此权限。不过,尚不具有此权限的应用在设备升级后便无法获得此权限了。

    如果 Go 设备上的应用发送具有 ACTION_MANAGE_OVERLAY_PERMISSION 操作的 intent,则系统会自动拒绝此请求,并将用户转到设置屏幕,上面会显示不允许授予此权限,原因是它会减慢设备的运行速度。如果 Go 设备上的应用调用 Settings.canDrawOverlays(),则此方法始终返回 false。同样,这些限制不适用于在设备升级到 Android Q 之前便已收到 SYSTEM_ALERT_WINDOW 权限的应用

    关于以旧版 Android 系统为目标平台的应用的警告

    在 Android Q 中,当用户首次运行以 Android 6.0(API 级别 23)以下的版本为目标平台的任何应用时,Android 平台会向用户发出警告。如果此应用要求用户授予权限,则系统会先向用户提供调整应用权限的机会,然后才会允许此应用首次运行。

    由于 Google Play 的目标 API 方面的要求,用户只有在运行最近未更新的应用时才会看到这些警告。对于通过其他商店分发的应用,我们也将于 2019 年引入类似的目标 API 方面的要求。要详细了解这些要求,请参阅在 2019 年扩展目标 API 级别方面的要求

    移除了 SHA-2 CBC 加密套件

    以下 SHA-2 CBC 加密套件已从平台中移除:

    • TLS_RSA_WITH_AES_128_CBC_SHA256
    • TLS_RSA_WITH_AES_256_CBC_SHA256
    • TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
    • TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
    • TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
    • TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384

    这些加密套件不如使用 GCM 的类似加密套件安全,并且大多数服务器要么同时支持这些加密套件的 GCM 变体和 CBC 变体,要么二者均不支持。

    注意:应用和库应该让其所需的加密套件集与 getSupportedCipherSuites() 中返回的值相交,以便提前防范加密套件日后遭到移除。
    

    应用使用情况

    Android Q 引入了与应用使用情况相关的以下行为变更:

    • UsageStats 应用使用情况的改进

      • 当在分屏或画中画模式下使用应用时,Android Q 现在能够使用 UsageStats 准确地跟踪应用使用情况。
      • Android Q 现在可以跟踪免安装应用的使用情况。
    • 按应用开启灰度模式

      • Android Q 现在可以将应用设为灰度显示模式。
    • 按应用开启干扰模式

      • Android Q 现在可以选择性地将应用设为“干扰模式”,
      • 此时系统会禁止显示其通知,
      • 并且不会将其显示为推荐的应用。
    • 暂停和播放

      • 在 Android Q 中,暂停的应用无法再播放音频。

    HTTPS 连接变更

    • 如果运行 Android Q 的应用将 null 传递给 setSSLSocketFactory(),现在会出现 IllegalArgumentException
    • 在以前的版本中,将 null 传递给 setSSLSocketFactory() 与传入当前的默认 SSL 套接字工厂效果相同

    android.preference 库现已弃用

    android.preference 库现已弃用。开发者应该改为使用 AndroidX preference 库,这是Android Jetpack 的一部分。如需获取其他有助于迁移和开发的资源,请查看经过更新的设置指南以及我们的公开示例应用参考文档

    ZIP 文件实用程序库变更

    Android Q 对 java.util.zip 软件包(用于处理 ZIP 文件)中的类做出了以下变更。这些变更会让库的行为在 Android 和使用 java.util.zip 的其他平台之间更加一致。

    Inflater

    在以前的版本中,如果在调用 end() 之后调用 Inflater 类中的某些方法,这些方法会抛出 IllegalStateException。在 Android Q 中,这些方法会抛出 NullPointerException。

    ZipFile

    如果所提供的 ZIP 文件不包含任何文件,则 ZipFile 的构造函数(采用的参数类型为 File、int 和 Charset)不再抛出 ZipException。

    ZipOutputStream

    如果 ZipOutputStream 中的 finish() 方法尝试为不包含任何文件的 ZIP 文件写入输出流,此方法不再抛出 ZipException。

    摄像头变更

    很多使用摄像头的应用都会假定如果设备采用纵向配置,则物理设备也会处于纵向,正如摄像头方向中所述。在过去可以做出这样的假定,但在推出新型设备(例如可折叠设备)后,这发生了变化。针对这些设备做出这样的假定可能导致相机取景器的显示产生错误的旋转和/或缩放。

    以 API 级别 24 或更高级别为目标平台的应用应该明确设置 android:resizeableActivity,并提供必要的功能来处理多窗口操作。

    电池用量跟踪

    从 Android Q 开始,只有在发生重大充电事件之后拔下设备电源插头,SystemHealthManager 才会重置其电池用量统计信息。
    一般来说,重大充电事件指的是设备电池已充满,或者设备电量从几乎耗尽变为即将充满

    Android Q 之前,无论何时拔下设备电源插头,无论电池电量有多微小的变化,电池用量统计信息都会重置

    展开全文
  • 客户业务上的变化自然算需求变更,某个业务的具体实现经过技术部门讨论总结后有了方案,当该方案实现后内侧发现效果不好,客户不认可,计划采用方案二,但时间就会超出预期,此时可以和客户提出算作需求变更吗?...
  • StrictMode 个有助于开发者检测代码问题的开发工具。 在 Android 9 及更高版本中,StrictMode 可以检测需要名称解析的网络地址查询所导致的网络违规 。 您在交付应用时不应启用 StrictMode。 否则,您的...
  • 基线变更与非基线变更

    千次阅读 2010-09-17 15:48:50
    、基线变更变更申请 项目经理或变更申请人填写《软件变更申请表》,说明要变更的内容、变更的原因、受变更影响的关联配置项 、工作量、变更实施人等,并提交给CCB。 (二)变更评估 CCB组长...
  • Android 8.0 行为变更

    千次阅读 2017-12-13 14:33:12
    Android 8.0 除了提供诸多新特性和功能外,还对系统和 API 行为做出了各种变更。本文重点介绍您应该了解并在开发应用时加以考虑的一些主要变更。 其中大部分变更会影响所有应用,而不论应用针对的是何种版本的 ...
  • Android 7.0 行为变更

    千次阅读 2016-12-09 16:27:37
    这些变更可能会影响您的应用访问系统资源,以及您的应用通过特定隐式 intent 与其他应用交互的方式。 低电耗模式 Android 6.0(API 级别 23)引入了低电耗模式,当用户设备未插接电源、处于静止状态且屏幕关闭时...
  • 因为经常要写各种配置文件,所以曾经写过监控文件变更的方法,后来其他地方要用到时每次都要copy不方便,于是修改了下代码,封装成个类库,这样只要需要监控的文件都可以通过这个类库来实现监控,以下是代码 ...
  • PMP项目管理—变更

    千次阅读 2019-05-31 17:56:22
    变更是每次PMP考试中出现频率最高的考点,没有之,每次考试大概40道题目可以归为变更题目。 PMP考试中,变更题目常见的几个不能选的选项: 拒绝变更 忽略变更 下次再说 直接实施变更 直接更新计划 以上5条都...
  • Hibernate数据变更记录

    千次阅读 2015-12-27 21:53:32
    使用Hibernate Envers实现数据库数据变更监听。
  • 项目变更管理

    千次阅读 2015-10-30 13:29:07
    1.1 什么都改客户就满意了吗—如何管理变更  开发阶段后期,客户可以操作实际系统了。这个过程中,客户经常感觉与最初的设想有差异,变更开始多了起来。  虽然有变更流程,但在进度压力下一些人觉得“太麻烦了”...
  • Android各个版本的Feature变更

    千次阅读 2018-11-06 10:44:13
    Android 8.0 相关变更  后台执行限制改动默认只只针对Android8.0及以上,不过用户可以设置将该改动作用于任何应用。 处于后台的应用不在允许创建后台服务了,如果创建会抛出个IllegalStateException异常。  ...
  • 软件变更控制

    千次阅读 2008-04-14 06:00:00
    软件工程过程中某阶段的变更,均要引起软件配置的变更,这种变更必须严格加以控制和管理,保持修改信息,并把精确、清晰的信息传递到软件工程过程的下步骤。 变更控制包括建立控制点和建立报告与审查制度。 对于...
  • 如何应对需求变更

    千次阅读 2019-04-13 15:28:31
    我们国内做开发,经常需要加班加点,而外企很少加班,但是产出却很明显,这里面涉及因素很多,包括大环境、管理制度水平、配置设施等等,但是有个因素至关重要,那就是需求变更。 我们国内大部分软件公司,需求...
  • 除了此页面所列的变更以外,Android Q 还引入了大量变更和限制以增强用户隐私保护。有关详情,请参阅隐私权页面。 非 SDK 接口的受限情况出现变化 官方明确指出:目标是在限制使用非 SDK 接口之前确保有可用的公开...
  • 最近把我的项目传到了GitHub,但是因为源文件都是直接在VS中新建的,VS又把所有文件都新建在了同个目录下,所有文件堆在一起又乱又丑。 但是又不能在资源管理器的文件夹里直接移,这样VS找不到文件又要报错。虽然...
  • Android7.0 API变更

    千次阅读 2016-08-23 16:59:37
    Android N 除了提供诸多新特性和功能外,还对系统和 API 行为做出了各种变更。 本文重点介绍您应该了解并在开发应用时加以考虑的一些重要变更。 如果您之前发布过 Android 应用,请注意您的应用可能受到这些平台...
  • 消费端的代码就更加简单,只需要在创建消费者时选择顺序消费监听器即可,代码如下图所示: 温馨提示:顺序消费端重试次数并不是16,而是Integer.MAX_VALUE,故请特别注意,业务异常一定在消费端监听器中必须处理...
  • 本实例提供了个实体属性变更历史记录工具,只要写很少的代码就能实现强大的变更历史记录功能.本工具的主要优点是1.工具对实体对象没有依赖性,任何对象都能记录历史.2.只要编写很少的代码就能实现需求. 上篇...
  • 产品研发过程是不断迭代的过程,发生需求变更、设计变更的情况非常多,需要系统的管理变更。 1 产品变更管理方案 1.1 产品变更的原则 变更管理的原则是产品基准化、变更管理过程规范化,妥善保存变更所产生的相关...
  • 问题描述:Winfrom中某些控件(下面以ListBox为例)绑定List数据源,第次绑定上去之后,后面更改List集合数据以后,ListBox并没有自动更新到UI上面。可能大家已经知道,如果 ListBox的数据源是 DataTable 则是可以...
  • Android N Preview 行为变更

    千次阅读 2016-05-23 16:34:05
    行为变更 Android N 除了提供诸多新特性和功能外,还对系统和 API 行为做出了各种变更。本文重点介绍您应该了解并在开发应用时加以考虑的一些重要变更。 如果您之前发布过 Android 应用,请注意您的...
  • Android 6.0 7.0 8.0特性变更

    千次阅读 2017-12-22 17:49:40
    Android 6.0 变更 本文内容 运行时权限低电耗模式和应用待机模式取消支持 Apache HTTP 客户端BoringSSL硬件标识符访问权通知音频管理器变更文本选择浏览器书签变更Android 密钥库变更WLAN 和网络连接...
  • 谈谈如何应对软件开发中的需求变更

    千次阅读 热门讨论 2013-05-07 14:50:55
     在软件开发中,大家都会遇到过这样的问题:客户的个新想法,就推翻了之前与客户经过再三讨论而确认定下来的需求。如果是功能性需求变更还会让人容易接受一些,毕竟功能性需求不实现的话,是会大大影响到软件产品...
  • 实体属性变更历史记录框架()-变更历史记录从此无忧(http://blog.csdn.net/lk_blog/article/details/8007777) 实体属性变更历史记录框架(二)-变更历史记录从此无忧...
  • 2、打开git bash,我们进去命令行界面,首先输入git help查看git有哪些命令: 3、下面开始介绍基本的Git命令 1)在本地创建个Git仓库 ...3)提交变更信息,同时查看仓库最新的记录信息 4)为了测
  • 软件项目的需求变更及对策

    千次阅读 2014-05-15 13:17:25
    [摘 要] 需求分析是软件生命周期中的重要阶段,决定软件项目的成败;需求变更会严重影响软件项目的质量、成本和工期。本文分析了需求变更的原因和影响,并给出了对需求...、问题的提出 什么是需求分析? 要知
  • EAS科目变更辅助账的规律

    千次阅读 2016-07-07 17:08:25
    1.科目变更辅助账,少变多,如原科目挂个核算项目职员,变更为职员+部门;  规律是 切换实体组织,选择末级科目,点击变更辅助账,选择对应的辅助账类型,如果是新增加核算项目,则需默认个值;  如果想要保持...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 235,352
精华内容 94,140
关键字:

一类变更