应用内更新App

需要三步: 6.0 动态申请存储权限 7.0 通过FileProvider创建uri 8.0 需要申请【安装未知来源应用权限】 应用内更新App,在将下载的App保存到本地时,肯定要先处理申请存储权限。这里不提。 当安装App时,需要提供本地App的存储路径。在不同的Android版本上会有不同的提供方式。 FileProviderAndroid 7(API24)开始使用FileProvider共享应用内的文件。向外界暴露一个安全的文件Uri(格式:”content:// “ 代替以前的 “file://“)。 声明FileProvider在AndroidManifest.xml中声明一个FileProvider。 <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="your app PackageName"> <application ...> <provider android:name="android.support.v4.content.FileProvider" android:authorities="${your app PackageName}.fileprovider" android:grantUriPermissions="true" android:exported="false"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths" /> </provider> ... </application> </manifest> android:exported=false;不应该将FileProvider设置成public。 android:grantUriPermissions=true;授予临时获取文件的权限。 <meta-data>子元素指向一个XML文件,该文件指定您要共享的目录。android:resource属性是该文件的路径和名称,不包含 .xml扩展名。 指定可共享的目录如何指定共享文件的路径?只能在res/xml目录下创建filepaths.xml文件来配置共享文件路径。 <paths> <!-- 至少要包含以下一种 --> <!-- Context.getFilesDir() --> <files-path path="images/" name="myimages" /> <!-- Context.getCacheDir() --> <cache-path name="name" path="path" /> <!-- Environment.getExternalStorageDirectory() --> <external-path name="name" path="path" /> <!-- Context.getExternalFilesDir(String)和Context.getExternalFilesDir(null) --> <external-files-path name="name" path="path" /> <!-- Context.getExternalCacheDir() --> <external-cache-path name="name" path="path" /> <!-- Context.getExternalMediaDirs() API 21+ --> <external-media-path name="name" path="path" /> </paths> <files-path path="images/" name="myimages" /> <files-path>标记共享了应用内部存储的 files/ 目录中的目录( /data/user/0/com.ideal.file.study/files )。path 属性共享了 files/的images/子目录。name 属性指示 FileProvider 将路径段 myimages 添加到 files/images/ 子目录中文件的内容 URI中。属于内部存储区域,通过context.getFilesDir()获取。 对上面的name和path做个解释。 <files-path name="files" path="Download"/> <files-path name="files" path="file"/> <files-path name="file" path="file1"/> <files-path name="f" path="file12"/> 首先files-path指定的是data/user/0/packagename/files下的路径。name和path相当于我们常说的键值对key-value。因此name可以指定任意合法的值。path就是要创建的文件的子路径。也就是data/user/0/packagename/files的下一级路径。所以最终文件的路径就是data/user/0/packagename/files/${path}/filename。 在代码中要创建这个文件路径,使用的是path而不是name: val filePath = File(filesDir,"${path}") 生成共享文件的URIval uri : Uri = FilePRovider.getUriForFile(context,authorities,file) 其中authorities就是我们在清单文件中声明的。 授予临时权限安装App时,使用FileProvider需要授予临时权限。 intent.flags = Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION //读的权限 Intent.FLAG_GRANT_WRITE_URI_PERMISSION //写的权限 Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION //与上面的两个权限组合使用 Intent.FLAG_GRANT_PREFIX_URI_PERMISSION // 与前面两个组合使用 安装时申请未知来源权限(API26,android8.0) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ //判断是否授予了允许安装未知来源App的权限 if (!packageManager.canRequestPackageInstalls()){ val packageUri = Uri.parse("package: $file-absolutePath") val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES,packageUri) startActivityForResult(intent,requestCode) }else { installApk() } }else { installApk() } 安装Apkfun installApk(){ //7.0开始使用FileProvider if (Build.VERSION_CODES.N <= Build.VERSION.SDK_INT){ val apkUri = FileProvider.getUriForFile(this, "${application_id}.fileprovider", file) intent.flags = Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION intent.setDataAndType(apkUri, "application/vnd.android.package-archive") }else{ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); var apkUri = Uri.fromFile(file); intent.setDataAndType(apkUri, "application/vnd.android.package-archive"); } }
 2020-01-26   Android    FileProvider  App更新 

反射基础.md

首先需要获取到Class对象。 获取Class对象有两种获取方式: 通过类,接口或者枚举类名去获取,基本变量类型也可以。 Class<Student> clazz = Student.class; Class<Integer> intClazz = int.class; 通过变量名去获取(变量的类型一定是对象类型,基本变量不可以) Student student; Class<?> clazz = student.getClass(); ConstructorClass中定义了多个获取Constructor的方法。 方法 返回类型 方法释义 getConstructor(Class<?>… parameterTypes) Constructor 返回指定参数对应的构造函数(public权限的) getConstructors() Constructor<?>[] 返回构造函数的数组 (public权限的) getDeclaredConstructor(Class<?>… parameterTypes) Constructor 返回指定参数对应的构造函数(包含public,private,protected,default权限的) getDeclaredConstructors() Constructor<?>[] 返回构造函数的数组 (包含public,private,protected,default权限的) 构造函数的参数如果为基本类型,比如int,就直接传入int.class。 MethodClass定义了多个获取Method的方法。 方法 返回类型 方法释义 getMethods() Method[] public权限的方法,包含继承自父类(接口)的。 getDeclaredMethod(String name, Class<?>… parameterTypes) Method 获取自身方法名为name的方法,该方法可为protected,public,private,default权限。 getDeclaredMethods() Method[] 获取自身所有的方法,该方法可为protected,public,private,default权限。 getEnclosingMethod Method 方法返回的是个抽象类(接口)对象。比如方法内实例化了一个接口或者抽象类,并返回。 getMethod(String name, Class<?>… parameterTypes) Method 获取自身方法名为name的public权限的方法 Method method = clazz.getMethod(String methodName,Class<?>... parameterTypes) 如果方法参数中包含泛型参数T,那么parameterTypes对应应当传入Object.class。如果泛型参数T有明确的父类型(也就是说其父类型不是泛型类型),那么应当传入父类型 public <T> void get1(T t,String name){ } public <T extends People> void get2(T t,String name){ } public <D,T extends D> void get3(T t,String name){ } //T是泛型类型,这里要传入Object Method methodGet1 = clazz.getMethod("get1",Object.class,String.class); //T的父类型是People,这里要传入People Method methodGet2 = clazz.getMethod("get2",People.class,String.class); //T的父类型仍然是泛型D,这里要传入Object Method methodGet3 = clazz.getMethod("get3",Object.class,String.class); 方法返回值类型 方法 返回值类型 方法释义 getReturnType Class<?> 方法返回值的Class类型对象,如果返回值为泛型T,就返回Object。 getGenericReturnType Type 方法返回值的Type类型对象,如果返回值为泛型T,就返回T。 getTypeParameters TypeVariable <Method>[] 方法上声明的泛型类型。比如:T public <T> T get(T t){ return t; } Method method; Type type = method.getGenericReturnType();//T --> TypeVariable Class<?> classType = method.getReturnType();//java.lang.Object TypeVariable<Method>[] typeParameters = method.getTypeParameters(); 方法参数Method method; Parameter[] parameters = method.getParameters(); 方法参数类型public <T> T get(T t,String name){ return t; } Type[] genericParameterTypes = method.getGenericParameterTypes();//T,java.lang.String Class<?>[] parameterTypes = method.getParameterTypes();//java.lang.Object,java.lang.String 方法参数注解//方法注解 Annotation[][] parameterAnnotations = method.getParameterAnnotations(); 方法签名:方法的名字,方法的参数个数,参数类型,参数顺序。不包括方法返回值类型。 FieldParameterField和Parameter和上面的都差不多,暂时不列出了。
 2020-01-21   java    反射 

android屏幕适配.md

基础概念屏幕尺寸屏幕对角线长度。单位英寸。1英寸≈2.54㎝。 屏幕分辨率屏幕横纵方向上的像素点数,单位是px。1px = 1个像素点数。如1920*1080。 屏幕像素密度每英寸上的像素点数,单位是dpi。即dot per inch。屏幕像素密度与屏幕尺寸和分辨率有关。 像素密度 = \sqrt{ width^{2} + height^{2} } / 屏幕尺寸区分dpi和dipdpi是像素密度,全称是dot per inch。dip是密度无关像素,全称是density independence pixels。 dip,dpi与px的转换 px = dip * (dpi / 160)比如:1dip在160dpi的屏幕上显示为1px,在320dpi的屏幕上显示为2px。 屏幕适配为什么要屏幕适配?在不同尺寸的设备上,ui控件尺寸显示不一。比如:同样是5.0英寸的屏幕,在320dpi的屏幕上显示32px,在480dpi的屏幕上就显示为48px。前者看起来就会比后者尺寸大。 适配后的效果ui控件在不同设备上显示的尺寸与屏幕的比例保持一致。 如何适配这里使用的是开源框架AndroidAutoSize。 添加依赖implementation 'me.jessyan:autosize:0.9.5' 配置AndroidManifest.xml<!-- 以宽或者高为基准,配置其一即可 --> <meta-data android:name="design_width_in_dp" android:value="360"/> <meta-data android:name="design_height_in_dp" android:value="640"/> 如何去确定宽或者高的基准值? 在蓝湖中查看 框架原理源码中定义了InitProvider,继承自ContentProvider。并在AndroidManifest.xml中声明了InitProvider。 App启动时,会自动加载InitProvider。其onCreate方法就会被调用。 @Override public boolean onCreate() { AutoSizeConfig.getInstance() .setLog(true) .init((Application) getContext().getApplicationContext()) .setUseDeviceSize(false); return true; } AutoSizeConfig的init方法被调用。 AutoSizeConfig init(final Application application, boolean isBaseOnWidth, AutoAdaptStrategy strategy) { //省略不必要的代码。。。 final DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics(); //读取清单中配置的设计图的尺寸 getMetaData(application); //通过WindowManager获取宽高 int[] screenSize = ScreenUtils.getScreenSize(application); mScreenWidth = screenSize[0]; mScreenHeight = screenSize[1]; //和屏幕尺寸相关的初始值 mInitDensity = displayMetrics.density; mInitDensityDpi = displayMetrics.densityDpi; mInitScaledDensity = displayMetrics.scaledDensity; mInitXdpi = displayMetrics.xdpi; application.registerComponentCallbacks(new ComponentCallbacks() { @Override public void onConfigurationChanged(Configuration newConfig) { if (newConfig != null) { int[] screenSize = ScreenUtils.getScreenSize(application); mScreenWidth = screenSize[0]; mScreenHeight = screenSize[1]; } } @Override public void onLowMemory() { } }); mActivityLifecycleCallbacks = new ActivityLifecycleCallbacksImpl(strategy == null ? new DefaultAutoAdaptStrategy() : strategy); //注册生命周期回调 application.registerActivityLifecycleCallbacks(mActivityLifecycleCallbacks); return this; } AutoSizeConfig主要职责: 获取和屏幕尺寸相关的属性值 /** * 最初的 {@link DisplayMetrics#density} */ private float mInitDensity = -1; /** * 最初的 {@link DisplayMetrics#densityDpi} */ private int mInitDensityDpi; /** * 最初的 {@link DisplayMetrics#scaledDensity} */ private float mInitScaledDensity; /** * 最初的 {@link DisplayMetrics#xdpi} */ private float mInitXdpi; /** * 设计图上的总宽度, 单位 dp */ private int mDesignWidthInDp; //设计图上的总高度, 单位 dp private int mDesignHeightInDp; /** * 设备的屏幕总宽度, 单位 px */ private int mScreenWidth; /** * 设备的屏幕总高度, 单位 px, 如果 {@link #isUseDeviceSize} 为 {@code false}, 屏幕总高度会减去状态栏的高度 * 如果有导航栏也会减去导航栏的高度 */ private int mScreenHeight; 全局配置属性 //是否全局按照宽度进行等比例适配。true:以宽度适配,false:以高度适配 public AutoSizeConfig setBaseOnWidth(boolean baseOnWidth) ; //是否使用设备的实际尺寸做适配 //useDeviceSize {@code true} 为使用设备的实际尺寸 (包含状态栏, 导航栏), {@code false} 为不使用 (不包含状态栏, 导航栏) public AutoSizeConfig setUseDeviceSize(boolean useDeviceSize); //设置屏幕适配逻辑策略类 public AutoSizeConfig setAutoAdaptStrategy(AutoAdaptStrategy autoAdaptStrategy); 源码中有这样一个类ActivityLifecycleCallbacksImpl。这是ActivityLifecycleCallbacks的实现类。在AutoSizeConfig的init方法中注册了这个类。 屏幕适配的具体业务逻辑就在类ActivityLifecycleCallbacksImpl的方法onActivityCreated中通过AutoAdaptStrategy完成的。 @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { //... if (mAutoAdaptStrategy != null) { mAutoAdaptStrategy.applyAdapt(activity, activity); } } //DefaultAutoAdaptStrategy @Override public void applyAdapt(Object target, Activity activity) { //如果 target 实现 CustomAdapt 接口表示该 target 想自定义一些用于适配的参数, 从而改变最终的适配效果 if (target instanceof CustomAdapt) { AutoSize.autoConvertDensityOfCustomAdapt(activity, (CustomAdapt) target); } else { AutoSize.autoConvertDensityOfGlobal(activity); } } 最终是通过AutoSize完成修改DisplayMetrics。具体代码就不再贴出来了。 几点总结使用ConstraintLayout能解决一些适配上的问题。 高度适配滑动布局按照常规设置宽高属性即可。 固定布局高度固定的布局(整个页面高度不超出当前屏幕高度),在使用ConstraintLayout时,可以结合layout_constraintHeight_percent属性,设置布局高度相对于父布局的比例(数值在蓝湖设计图上有标注,windows下通过alt+鼠标左键查看,mac下通过option+鼠标左键查看),同时layout_height要设置为0dp。 如果高度占满父布局,直接设置top和bottom和父布局约束关系,设置layout_height为0dp即可。 宽度适配如果宽度占满父布局,直接设置left和right和父布局约束关系,设置layout_width为0dp即可。
 2020-01-20   Android    屏幕适配