
暂无个人介绍
最近想研究一下服务端,先安装一个Mysql数据库,结果官网最新版本(5.7)确认难装,搞了大半天,一次安装全部,一次仅安装服务,都倒在认证失败的界面。结果根据网上查询和询问朋友,发现最新5.6版本更好用一些,于是顺利安装,教程如下: 一. 工具 Win7 64位操作系统 二. 步骤 第一步:下载安装包 下载 地址:http://www.mysql.com/ 平台默认为Windows。 确认自己的电脑是x86的还是x64的,点击download即可。 第二步:解压 解压之后可以将该文件夹改名,放到合适的位置。我是将文件夹改名为MySQL Server 5.6,放到D:\Program Files\MySQL路径中。当然你也可以放到自己想放的任意位置。 第三步:配置(不要急着使用,最好先配置) 配置环境变量PATH后,以后打开mysql就可以不用切换目录。 过程:计算机—>系统属性—>高级系统配置—>环境变量 选择PATH,在其后面添加: 你的mysql bin文件夹的路径 (如:我的是:D:\Program Files\MySQL\MySQL Server 5.6\bin ) 修改后的PATH为: PATH=…….;D:\Program Files\MySQL\MySQL Server 5.6\bin (需注意:1.必须在原有PATH后加英文输入下的分号;2.是追加,不是覆盖。) 第四步:修改配置文件 配置完环境变量之后先不要启动mysql,还需要修改一下配置文件,mysql-5.6.1X默认的配置文件是在D:\Program Files\MySQL\MySQL Server 5.6\my-default.ini,可以自己再建立一个my.ini文件,在其中修改配置: [mysqld] basedir=C:\Program Files\MySQL\MySQL Server 5.6(mysql所在目录) datadir=C:\Program Files\MySQL\MySQL Server 5.6\data (mysql所在目录\data) mydefault.ini内容如下: my.ini内容如下: 第五步:安装mysql 首先,以管理员身份运行cmd(必须以管理员身份运行,否则权限不够,会出错) 接着,进入D:\Program Files\MySQL Server 5.6\bin目录, 然后,执行命令mysqld –install,若无错,则会提示安装成功。有错则先安装“微软常用运行库”,网上搜一下,下载安装后继续。 注:若之前安装过mysql,但由于某种原因未卸载干净,当再次安装mysql时,会提示此服务已存在。可用sc delete mysql命令,删除之前的版本,再执行mysqld –install命令。 第六步:启动mysql服务器 在cmd中 输入net start mysql 命令。 第七步:登录mysql 在cmd中,输入 mysql -u root -p,第一次登录没有密码,直接回车,登陆成功。 退出mysql的命令是exit 。直接关闭cmd窗口是没有退出的,要输入exit才会退出。 设置密码: 进入mysql命令行: 1、set password for 'root'@'localhost' =password('你的密码'); 2、mysql> use mysql;Database changedmysql> update user set password=password('654321') where user='root'; Query OK, 3 rows affected (0.00 sec) IntelliJ IDEA2017 激活方法(亲测可用): 搭建自己的授权服务器,对大佬来说也很简单,我作为菜鸟就不说了,网上有教程。 我主要说第二种,现在,直接写入注册码,是不能成功激活的(如果你成功了,那当然好,如果不行,按照下面步骤操作) 1、进到文件夹中:C:\Windows\System32\drivers\etc ,找到hosts文件,用记事本编辑 2、如果没有找到hosts文件,可在查看设置中勾选“显示隐藏的项目”,不会的可百度“设置查看隐藏文件”,看见hosts文件直接看第4步。 3、如果依然没有,那么就需要自己在桌面新建一个空的记事本(有些系统因为权限问题,无法直接在当前文件夹中新建文本),在查看设置中,勾选显示“文件扩展名”,然后重命名为“hosts”,切记,删除后缀名! 4、将“ 0.0.0.0 account.jetbrains.com ”添加到hosts文件中 5.进入 http://idea.lanyus.com/ ,点获取注册码 最后打开idea,将注册码粘贴即可。
Labmda表达式: Runnable runnable =new Runnable(){ public void run(){ //123 } } 变: Runnable runnable =()->{ //123 } “123”为多句代码,可以写上括号,如果仅一句,可以不写。 获得小数位数: 经测试,使用小数(1.01)通过使用“.”进行split操作后,得到的数组为空,原因在于String的split方法 public String[] split(String regex, int limit) { /* fastpath if the regex is a (1)one-char String and this character is not one of the RegEx's meta characters ".$|()[{^?*+\\", or (2)two-char String and the first char is the backslash and the second is not the ascii digit or ascii letter. */ char ch = 0; if (((regex.value.length == 1 && ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) || (regex.length() == 2 && regex.charAt(0) == '\\' && (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 && ((ch-'a')|('z'-ch)) < 0 && ((ch-'A')|('Z'-ch)) < 0)) && (ch < Character.MIN_HIGH_SURROGATE || ch > Character.MAX_LOW_SURROGATE)) { ... } return Pattern.compile(regex).split(this, limit); } if语句判断无法通过,因为不允许使用"."做split的regex字符;第2个条件明显长度只有1;第3个条件ch<55276或者ch>57344才满足。 运行到return语句,Pattern执行结果,获得空数据。 那么只能通过indexOf方法来获得小数位数了: 1位小数且小数第1位是0的(如1.0),显示整数;其他情况显示原小数(String默认会把1.10看成1.1)。 public static String get1BitData(double number) { String first = String.valueOf(number); String second = first.substring(first.indexOf(".")); if (second.length() > 1 && !second.endsWith("0")) { return String.valueOf(number); } return String.valueOf((int) number); } 取相应格式的小数: // 取一位整数 System.out.println(new DecimalFormat("0").format(pi));// 3 // 取一位整数和两位小数 System.out.println(new DecimalFormat("0.00").format(pi));// 3.14 // 取两位整数和三位小数,整数不足部分以0填补。 System.out.println(new DecimalFormat("00.000").format(pi));// 03.142 // 取所有整数部分 System.out.println(new DecimalFormat("#").format(pi));// 3 // 以百分比方式计数,并取两位小数 System.out.println(new DecimalFormat("#.##%").format(pi));// 314.16% long c = 299792458; // 显示为科学计数法,并取五位小数 System.out.println(new DecimalFormat("#.#####E0").format(c));// 2.99792E8 // 显示为两位整数的科学计数法,并取四位小数 System.out.println(new DecimalFormat("00.####E0").format(c));// 29.9792E7 // 每三位以逗号进行分隔。 System.out.println(new DecimalFormat(",###").format(c));// 299,792,458 // 将格式嵌入文本 System.out.println(new DecimalFormat("光速大小为每秒,###米。").format(c)); Ioc反转和Di注入的可用容器有Spring、JBoss、Ejb等。 它们都是一种编程思想,目的是解耦。 A调用B,正常情况下是,创建B,再调用B,则A依赖B,并且使用完毕还要销毁B。 Ioc,A告诉容器要调用B,容器创建B,并通知A,然后A通过构造函数、属性和接口调用方式,获得B,再去调用B。 Di,A告诉容器要调用B,容器创建B,并通知A,然后A通过反射的方式,获得B,再去调用B。 AOP:Aspect Oriented Programming。面向切面编译,切面指某类的某方法的代码片断,切点(JoinPoint)指某类的某方法。AOP指通过动态注入的方式,将几处共用的代码片断,在使用时注入,使得代码维护更加便利。
Handler、Looper、Message、MessageQueue之间的关系(基于Android API 26) 安卓系统设计的消息分发体系,不仅在应用层广泛应用;而且在底层也是使用这个体系,与Binder一起进行消息分发,因此熟悉这个体系是十分必要的。 1、ActivityThread初始化时,执行它的main方法,通过Looper.prepareMainLooper方法,初始化一个Looper对象(代码有省略),同时初始化ActivityThread,接着执行Looper的loop方法。 public static void main(String[] args) { Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } // End of event Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); } 2、Looper对象初始化时,又会初始化一个MessageQueue对象; public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } } private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); } private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); } 3、Handler初始化时,会持有这个Looper(MainLooper)的MessageQueue(代码有省略) public Handler(Callback callback, boolean async) { mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; } 4、发消息时将message放入MessageQueue; public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); } private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); } boolean enqueueMessage(Message msg, long when) { if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); } synchronized (this) { if (mQuitting) { IllegalStateException e = new IllegalStateException( msg.target + " sending message to a Handler on a dead thread"); Log.w(TAG, e.getMessage(), e); msg.recycle(); return false; } msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) { // New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; needWake = mBlocked; } else { // Inserted within the middle of the queue. Usually we don't have to wake // up the event queue unless there is a barrier at the head of the queue // and the message is the earliest asynchronous message in the queue. needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } // We can assume mPtr != 0 because mQuitting is false. if (needWake) { nativeWake(mPtr); } } return true; } 5、看Looper的loop方法,不断从MessageQueue取出消息 public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue; // Make sure the identity of this thread is that of the local process, // and keep track of what that identity token actually is. Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } // This must be in a local variable, in case a UI event sets the logger final Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs; final long traceTag = me.mTraceTag; if (traceTag != 0 && Trace.isTagEnabled(traceTag)) { Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); } final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis(); final long end; try { msg.target.dispatchMessage(msg); end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis(); } finally { if (traceTag != 0) { Trace.traceEnd(traceTag); } } if (slowDispatchThresholdMs > 0) { final long time = end - start; if (time > slowDispatchThresholdMs) { Slog.w(TAG, "Dispatch took " + time + "ms on " + Thread.currentThread().getName() + ", h=" + msg.target + " cb=" + msg.callback + " msg=" + msg.what); } } if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } // Make sure that during the course of dispatching the // identity of the thread wasn't corrupted. final long newIdent = Binder.clearCallingIdentity(); if (ident != newIdent) { Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what); } msg.recycleUnchecked(); } } 问题1:为什么handler可以发送延迟消息?我们可以看到,如果当前消息还未到时间,则延迟。当放入一个Message时,执行enqueueMessage方法,唤醒cpu,同时把msg赋值给Message,优先执行,取出这一个message。总体来讲,不影响正常Message的插入,同时根据消息插入和时间两个因素,来实现延迟消息发送。 Message next() { int nextPollTimeoutMillis = 0; for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; } // Process the quit message now that all pending messages have been handled. if (mQuitting) { dispose(); return null; } // If first time idle, then get the number of idlers to run. // Idle handles only run if the queue is empty or if the first message // in the queue (possibly a barrier) is due to be handled in the future. if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } if (pendingIdleHandlerCount <= 0) { // No idle handlers to run. Loop and wait some more. mBlocked = true; continue; } if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } // Run the idle handlers. // We only ever reach this code block during the first iteration. for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // release the reference to the handler boolean keep = false; try { keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); } if (!keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } // Reset the idle handler count to 0 so we do not run them again. pendingIdleHandlerCount = 0; // While calling an idle handler, a new message could have been delivered // so go back and look again for a pending message without waiting. nextPollTimeoutMillis = 0; } } 问题2:为什么 Looper对象只有一个,MessageQueue只有一个,handler有多个,却可以保证每次都能将处理过的消息,发到对应的handlerCallback里去? 每个Message都持有当前的Handler-target,使用handler.dispatchMessage方法回调(见loop方法)。最后使用Message的callback方法独立执行或者Handler的callBack方法 public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } private static void handleCallback(Message message) { message.callback.run(); } 问题3:那么屏幕熄灭后,为什么 消息会出现延迟? 因为looper方法里,使用SystemClock.uptimeMillis()来确定处理消息时间,而它在休眠时是不处理的。 /** * Returns milliseconds since boot, not counting time spent in deep sleep. * * @return milliseconds of non-sleep uptime since boot. */ @CriticalNative native public static long uptimeMillis();
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! Android中动画的应用,在应用管理软件、购物软件、追星软件等比较广泛;比如常见的进度条设计,此处尤其指圆形的那种。比如清理小火箭,从下向上飞出;比如清理软件提示,由深色渐变成浅色。这些都是动画的应用场景。 Android动画分为两种,一种叫帧动画,就像flash一样,学名Frame,进度条一般使用这种;另一种叫补间动画,学名Tween,可以移动位置、变化颜色、变换大小、翻转变化。 先说帧动画 <?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false"> <item android:drawable="@mipmap/icon_s01" android:duration="30" /> <item android:drawable="@mipmap/icon_s02" android:duration="30" /> <item android:drawable="@mipmap/icon_s03" android:duration="30" /> </animation-list> onshot的意思是,true只播放一遍,false循环播放;item项指每隔duration展示的图片应用: 先写布局 <ImageView android:id="@+id/loading_iv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:background="@drawable/anim_progress_round"/> 再执行代码 ImageView loadingImage = child.findViewById(R.id.loading_iv); AnimationDrawable animation = (AnimationDrawable) loadingImage.getBackground(); if (animation.isRunning()) { animation.stop(); } animation.start(); 获得背景的AnimationDrawable对象,执行start方法即可。 补间动画: 基类Animation,子类ScaleAnimation、AlphaAnimation、RotateAnimation、TranlateAnimation,分别用于大小变换、色彩变换、翻转变换和位移变换。 常用方法有setDutation-设置运行时间,setFillAfter-设置运行结束是否保持最后状态,setFillBefore-设置运行结束是否保质最初状态,setRepeatCount-设置重复执行次数;setRepeatMode-设置重复类型,如REVERSE会倒着再执行repeatCount遍;setInterpolater-设置插补器,可以让控件回弹,控制速度。 //accelerate_decelerate_interpolator先慢后快再慢linear_interpolator匀速decelerate_interpolator先快后慢 mAnimationTranslate.setInterpolator(mContext, android.R.anim.decelerate_interpolator); 例: mAnimationTranslate = new TranslateAnimation(Animation.RELATIVE_TO_PARENT, 0f, Animation.RELATIVE_TO_PARENT, 1, Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0); mAnimationTranslate.setDuration(3000); mAnimationTranslate.setRepeatMode(Animation.ABSOLUTE); mAnimationTranslate.setRepeatCount(0); mRocketIv.startAnimation(mAnimationTranslate);设置相关参数,执行startAnimation即可。 ScaleAnimation:fromXScale-X轴方向缩放比例,toXScale、fromYScale和toYScale同理;pivoteX起点X坐标,pivoteY同理。AlphaAnimation:fromAlpha-起始透明度,toAlpha-最终透明度。 RotateAnimation:fromDegress-起始角度,toDegress-最终角度,正数为顺时针,负数为逆时针。 TranslateAnimation:fromXType-起始位移类型,fromYType同理;fromXValue-起始X坐标,fromYValue同理; 从左边折出来 ScaleAnimation animation = new ScaleAnimation(0f, 1f, 1f, 1f); animation.setDuration(1000);//设置动画持续时间 mChannelLayout.setAnimation(animation); 从左上角弹出来 ScaleAnimation animation = new ScaleAnimation(0f, 1f, 0f, 1f); animation.setDuration(1000);//设置动画持续时间 mChannelLayout.setAnimation(animation); 从左向右的动画: AnimationUtils.makeInAnimation(this, true); 从右回到左的动画: AnimationUtils.makeOutAnimation(this, false); 最后AnimationSet可以对以上四种补间动画类型进行组合,制造出更加炫酷的效果。
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 签名机制: V1:7.0以前默认,使用META-INF目录下三个文件,MANIFEST.MF,CERT.MF,CERT.RSA来保证apk不被修改。 MANIFEST.MF:记录apk资源中除META-INF,所有文件的Base64编码的SHA1值。防止apk资源被修改。 CERT.MF:记录MANIFEST.MF中属性值的Base64。防止MANIFEST.MF被修改。 CERT.RSA:记录通过开发者私钥对CERT.MF计算得到的签名,和开发者公钥。 解析过程是通过PackageManagerService.java和PackageParser.java对上述过程进行逆向操作。如果签名验证成功,则安装成功,否则安装失败。 V2:7.0以后默认此类型签名方式,如不处理安装时显示无证书。V1缺点在于,签名可以被清除,重新签名。 安装时,不用每次再去解压校验每个文件,只需要检验整个APK的数据摘要,发明SigningBlock插入apk修改偏移量满足zip结构要求。安装速度是V1的3倍左右。 将数据摘要、数字证书、额外属性组装成类MF文件 用私钥将MF计算得到类SF文件 将类MF文件和类SF文件,以及开发者公钥,用私钥签名成V2。 结构如下图: V2严格限制apk不能被重新签名,否则已安装的apk,再替换安装时,检验不通过。 重签名:保证安装。环境:jdk1.8,据说jdk1.7要加入“-digestalg SHA1 -sigalg MD5withRSA”几个参数(未验证) 将apk解压,删除META-INF文件夹,重新压缩成zip格式。 v1:jarsigner -verbose -keystore “jks路径” -storepass “存储密码” -keypass “键密码” -signedjar “输出apk 路径” “转换apk路径” “alias 别名” v2:从build-tools/26.0.1左右的包里,拷贝apksigner.jar到tools文件夹内,并进入tools文件夹,执行如下命令 java -jar apksigner.jar sign --ks “jks路径” --ks-key-alias “alias 别名” --ks-pass pass:“存储密码” -key-pass pass:“键密码” --out “输出apk 路径” “转换apk路径” 验证v2签名: java -jar apksigner.jar verify -v 输出apk 路径” 实验过程:将一个包含v1和v2签名的apk重签名,在签名前后分别安装。 实验结果: 1、不签名,手机提示“无法安装” 2、v1签名后,可安装所有sdk版本的手机。说明:7.0以下手机验证v1签名通过,7.0以上手机未找到v2签名,所以也验证了v1。 2、v2签名后,结论如上。说明v1和v2签名均生效。如果只有v2签名,则7.0以下手机会报“无证书”。 2017年初,Android7.0出现新的签名机制,如果不适配将出现无签名的问题,华为Mate8最先出现,取巧的方式是将该签名机制取消(它是向下兼容的),build.gradle的release设置如下 [html] view plain copy release { // 如果要支持最新版的系统 Android 7.0 // 这一行必须加,否则安装时会提示没有签名 // 作用是只使用旧版签名,禁用V2版签名模式 v2SigningEnabled false 接上篇:Ant、Gradle、Python三种打包方式的介绍 上一组的打包机制是修改META-INF,在它的目录下面,使用python快速复制apk,并添加一个channel文件,用来读取作为项目的渠道名,显然这样做是不够严谨的,一直在关注并期待有新的方式解决这两个问题,那么walle应运而生。 其原理:在signiture block里的ID-VALUE对象添加channel-huawei,这样的键值对,再来读取渠道号即可,避免开发者修改apk造成安全问题,同时开放接口给开发者为自己业务注入新的解决方案,walle就是一例。 操作方式: 1、配置build.gradle 在位于项目的根目录 build.gradle 文件中添加Walle Gradle插件的依赖, 如下: buildscript { dependencies { classpath 'com.meituan.android.walle:plugin:1.0.3' } } 并在当前App的 build.gradle 文件中apply这个插件,并添加上用于读取渠道号的AAR apply plugin: 'walle' dependencies { compile 'com.meituan.android.walle:library:1.0.3' } 2、如何获取渠道信息 在需要渠道等信息时可以通过下面代码进行获取 String channel = WalleChannelReader.getChannel(this.getApplicationContext()); 3、如何生成渠道包 生成渠道包的方式是和assemble指令结合,可以通过传入参数决定是否生成渠道包,渠道包的生成目录存放在 build/outputs/apk/ 下面是各类用法示例: 生成单个渠道包 ./gradlew clean assembleRelease -PchannelList=meituan 支持 productFlavors ./gradlew clean assembleMeituanRelease -PchannelList=meituan 生成多个渠道包 ./gradlew clean assembleRelease -PchannelList=meituan,dianping 通过渠道配置文件来生成渠道包 ./gradlew clean assembleRelease -PchannelFile=channel 渠道号设置两种方式: 第一种:在项目目录下新建一个channel文件,利用上面第4条通过配置文件生成渠道包 [html] view plain copy samsungapps #三星 hiapk anzhi xiaomi # 小米 第二种:直接配合gradle clean assembleRelease 命令使用,采用build.gradle中原始的渠道号方式 [html] view plain copy productFlavors { official {} baidu {} } 4、更多用法 插入额外信息 如果想插入除渠道以外的其他信息,请在生成渠道包时使用 ./gradlew clean assembleRelease -PchannelList=meituan -PextraInfo=buildtime:20161212,hash:xxxxxxx extraInfo以key:value形式提供,多个以,分隔。 注意: extraInfo需要搭配channelList或者channelFile使用,plugin不支持只写入extraInfo。 extraInfo 不要出现以channel为key的情况 而对应的渠道信息获取方式如下: ChannelInfo channelInfo= WalleChannelReader.getChannelInfo(this.getApplicationContext()); if (channelInfo != null) { String channel = channelInfo.getChannel(); Map<String, String> extraInfo = channelInfo.getExtraInfo(); } // 或者也可以直接根据key获取 String value = WalleChannelReader.get(context, "buildtime"); 而对应的渠道信息获取方式如下: 应用签名方案APK Signature Scheme v2原理:http://tech.meituan.com/android-apk-v2-signature-scheme.html 官方地址:https://github.com/Meituan-Dianping/walle PR: walle最新版本是1.1.5支持flavor和walle两种配置渠道号的方式. 如果两者均存在build.gradle中,则使用gradle assembleReleaseChannels命令时,会报"Task 'assembleReleaseChannels' not found in root project".错误。 walle支持flavor,但flavor不支持walle。 如果想执行walle配置,则需要加入渠道号即gradle assembleXiaoMiReleaseChannels,则只会执行walle命令。 如果想执行flavor配置,则需要删除walle,执行gradle assembleReleaseChannels即可。 使用walle打包,则在数据统计时需要手动加入渠道号。 walle打包优点:打包速度快。一个母包打完,修改渠道号即要生成新包。 缺点:不能灵活修改应用的logo、包名等 flavor打包优点:可以灵活修改应用信息(http://blog.csdn.net/stimgo/article/details/77480154), 缺点:需要一个个打包,速度太慢;无法用脚本批量打包。 解决加固后无渠道号的问题:https://github.com/Meituan-Dianping/walle/tree/master/walle-cli 下载完walle-cli后,在其目录下执行 java -jar walle-cli-all.jar put -c 渠道号 目标apk即可。
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 接上《AI将带我们去何方?(中)》 说这么多严肃的话题,稍微来点轻松的,这是昨天李开复先生的微博。朱先生讲人类为生活本能而劳累着,但依然拥有希望,因为人类是美好的,明天是美好的,人性存在;李先生调笑到,其实我是AI,我不会累,潜台词是因为AI会帮我减少人类本能的劳动,甚至帮我自动发微博。AI确实强大,但李先生却从未提过AI的危险性所在,作为一名技术领域的大咖,博主对他的尊敬大于怀疑,但往往名人会带动更多的人去参与一项事业,李先生也同样如此,几乎是中国AI提出的最早一批人,大家最近熟悉的吴恩达先生等均回国准备大干一场,为人类解脱为本能而生存的枷锁而努力着,这非常好,生产力要优于生产关系先进行改造,让人类尽可能脱离低级趣味,或者叫低级本能,向马洛斯高级需求前进。然而博主并不想泼大家冷水,只是想让大家知道并记住几件事,人性、人类的选择权等,靠机器可以解放人的”口眼耳鼻舌“,但不可以事事都依靠它,就像父母不会想你长大后就靠你养活一样的道理,人因劳动而光荣,人因本性而伟大! 最后讲一下什么叫对人类好?“你妈逼你穿秋裤”,“你妈觉得你饿”等种种的类似,一直存在于中国人民的心中,父母总以自己有限的生活经验告诉人该怎样怎样,当你小的时候,他们负担起对你养育和教育的责任,当你大的时候,他们的谆谆教导依然不绝于耳;为什么会这样?先天惯性,做什么事都有惯性,直接你跟他们理论一场,他们终于明白自己老了,新时代人有新的观念,人类有一个习惯是非常好的,叫作知错能改!减少人们生活中大部分灾难性的问题。同样的,你这样能跟AI讲清楚吗,它不是人或者它装作不是人,又或者它不听人话,你将如何跟它理论和斗争?你做不到,它是机器,它是程度,它虽然是你创造的,但未来却不一定由你掌控,我就问你:你怕不怕?父母尚且有惯性思维,机器更加强烈,想象一样,得如何的斗争才能让AI明白,这恐怕又会是一阵巨痛!虽然机器人三定律,或者叫三原则可以规避一些问题,但上文已讲到,这是靠武力作为后盾的,如果你的军队是AI,你的警察是AI,甚至你的武器是AI,你的后盾将如何存在?对,你的后盾是不存在的,即机器人三定律或许是它不遵守的,当它用机器手握住你的喉咙时,你当作何感想?庄子的消极,章北海的悲观,满溢理论,人类的骄傲,世界的生与灭,whatever。 因此,无论何种法则,不可以让机器,或者说AI成为主宰,当从生活习惯上适应无AI存在的生活,虽然无法回到”男耕女织“的冷兵器时代,人人需要一个君主来定夺好坏,但是我们可以做到将选择权握在自己手中,就如美国的巡回法庭,普通老百姓行使监督权和决策权一样,我们不能让一个饥饿小孩的奶奶去超市偷面包的行为出现,这是人类的良知,这也是人类高明于其他物种,甚至AI的关键因素。而半智能时代,军队成为年轻人锻炼身体和智商、情商的平台,如以色列的军队参军训练,警察以人的姿态去处理人们的事务纠纷,驾驶由人类自主操控、AI辅助,将会是人类的最美好时代;而关于程序怎么自动化生成,则无需关心,最终依然要通过人类的审核,房间温度远程控制、热水器远程打开,依然要通过人类的双手去操作,这些仍旧需要发展和延深,但最终记住一件事,人之所以为人,因为选择权在自己手中,无论古时代的巫婆-抓住与英俊健硕军官关系情同兄弟的国王,要求军官娶她为妻,问他喜欢自己白天漂亮还是晚上漂亮时,军官说你自己选择,于是她选择白天黑夜都漂亮,还是现代的女性,都需要人们给她一个选择权,拐卖妇女儿童违法是人类对弱者的尊重。对于AI,将选择权留在人类手中,仍旧是AI对人类尊重的象征,也唯有此,人类文明才能得以持续向前发展;人类不因强大而文明,而因保护弱者而文明!勿以恶小而为之,勿以善小而不为,不论在现在还是过去,依然具有深刻教育意义! 作家往往可以预示着人类的未来,不仅仅是明朝人希望借助火炮让人上天的理想,还是《海底两万里》对地球内部结构的想象, 又或者是《三体》预示着宇宙的毁灭,还是《北京折叠》预示着人类阶级的固化,博主只想说一句,人类文明的前进,不可以缺少对 文学的重视,尤其科技文学的重视,尤其对一个从事科技行业的工作者而言,读读这些作者的书,会让你脑洞大开,对未来有个清醒 的认识,理解自己为什么而存在,又为什么而去创造;人类因无知而无畏,而探索,而学习,而总结,而智慧,而强大,然而当人强 大到一定程度,会直接影响到周围环境的变化,如同名人效应一般,因此每个人都需要注重自己的修养和学识,使人类向着更加文明 的方向发展。 作为一名科技行业从业者,讲完理论,往往还要再说说实践,自己是否会加入AI浪潮中去,如果机器语言C、Java、PHP盛行的 年代,Android和iOS诞生,给予许多计算机专业的同学新的发展道路,而罗胖也曾经说过”现代人最强的方面不在于某方面一直强, 而在于随着时代的发展,哪块土地水草丰茂,就往哪块土地迁徙,使自己一直处在时代的风口浪尖,才不至于被社会淘汰“,因此做 还是不做,这确实是一个问题,未来会不会从事该行业,需要看际遇,博主不是强烈的主观主义者,在于非涉及原则的基础上。但 给予大家的建议,仍然是能转就转,一个人不可能永远强大,需要不断的充实自己才可以迎战一波又一波知识浪潮的侵袭,才能立 于不败之潮头。而现如今,AI面向几个方面,一个是区块链(比特币、莱特币为代表的虚拟货币),一个是智能语音(科大讯飞、 百度等公司从事领域),一个是智能安居(小米、美的等公司所从事领域),一个是大数据(淘宝等电商公司所从事领域),一个 是安全(默安、腾讯等公司从事领域),一个是驾驶(Tesla、百度等公司所从事领域)。虚拟货币记录人们交易的各类,具备唯一 标识性,减少犯罪的产生,使支付更加便捷;几千行代码控制的智能驾驶系统已经完成,通过大量的人类行为和交通法则,以及 路况信息等的教育和学习,AI将对复杂事务的应对更加得心应手,减少人类的驾驶累、困、禁锢之苦;智能语音将各种语言自动 读取,并且自动翻译,将上帝为人类混乱之语言再次整合,将是又一项创举,共同的语言提升沟通效率,让人类协作的成本更加 低廉;智能家居让人类的生活更加便利,通过温度、湿度、位置、天气、习惯等的感知,自动处理房间环境,帮助人们按时吃药, 使人们高效率的休息和学习成为可能;大数据通过识别人类的各种行为,通过身体和兴趣特征的读取,为人们快速选择做出巨大 贡献,节省人们的时间,有利于人们将时间花在更有意义的事情上面;安全,将监控和处理危害人类的行为,减少恶意行为的产生, 净化网络环境,为人们的生命做保证。 学习AI开发,数学和编程两项技能缺一不可,数学好意味着你可以走更远,编程好意味着你的程序简单、可读性高。两者综合 起来,数学好+编程差意味着虽然当前工作做不好,但依然可以有巨大上升空间,数学差+编程好意味着当前工作虽然做的好,但AI 之路却不好走,唯有数学好+编程好,那么你未来持之以恒将成为AI界的吴恩达。以上均从技术方面来考虑,数学好成为衡量的重要 元素,但你依然可以编程好+数学差来做产品,指导AI向前发展,正可谓”尺有所短,寸有所长“,无论好与不好,如果你想进这个行 业,总有你可以切入的入口。人生就是一场不回头的路,不要停好好干,争取百岁之后回首生活多姿多彩、妙趣横生,那么将不枉 来世上这一遭! AI涉及到的技术有:Hadoop、Spark、HsF、Java、Go、R、android、Python、图片识别、语音识别、语音合成等。 AI学习推荐:http://blog.csdn.net/u013709270 http://blog.csdn.net/chancein007
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 现在人们生活中已经出现很多半AI的制成品,电视、空调、手机、各种车、飞机等各种需要使用电或油等能源来驱动的设备,成为收集人类行为和喜好的终端设备,给人们的“听、看、住、行”等领域或感官带来长远的扩展,使人类的组织和沟通更加缜密和细致,生活变得丰富多彩,但无论如何,选择权在人类手中,我愿意关就关,愿意开就开;AI是什么?只要它遵循存在的基本原则,就可以想干什么干什么,人类从此失去对它的控制,即选择权的跌落,而这无疑是人类的巨大退步!从微AI、小AI时代增强人类的机能开始,如果一旦超越人类的本能,中国的中庸理论有这样的一套说法:满溢理论-满招损、谦受益;月盈则亏、水满则溢。人不能太傲慢,将自己的偏见应用于AI,妄想建造新时代的“通天塔”,后果将不堪设想!而实现半AI半,将选择权留在人类手中,无疑是最合适的选择! 智能指的是什么?可以先讲三本书,一本叫行为之书,一本叫有字之书,一本叫无字之书。行为之书最为常见,日常道德的约束,对美好的向往,即国人立德之说;有字之书,是对行为之书的记录,使后世依然可以作榜样,如古人记录战争、耕作、婚丧嫁娶等的生活行为,《伯罗奔尼撒战争史》、《本草纲目》等数不胜数,也有雕刻在岩石上的壁面,龙门、敦煌等石窟,无不在使用有字之书来记录行为之书的行为,体现一个民族或个人的价值观、世界观和人生观,一生的计划,对世界的认识,一生所追求,为后世留下宝贵财富;而无字之书,又是思想的体现,将有字之书提升到哲学角度,如苏东坡的“匹夫之勇,拔剑而起,挺身而斗,此不足为勇也;天下有大勇者,猝然临之而不惊,无故加之而不怒”对韩信勇气的描述和赞叹,中国的中庸理论指导思想,亚里士多德的哲学理论,以无形之手来约束人类的行为,同“以德治国”对“以法治国”具备更大意义,告诉人们行为导向,畏天悯人、子孝父慈、尊老爱幼、兄爱弟悌等,可以说思想之导向比法律之导向影响大的多。即无字之书为行为之书提供依据,即人类行为的标准,以此不断循环,增强人类的美好,祛除人类的邪恶,使未来可以向往,人类更加同心协力,美好生活可以创造! 再次讲智能是什么?讲一个人做事靠谱,首要条件是考虑周全,将事情的前因后果使用自己的经验智慧分析清楚,将事情大体的逻辑固定,即先后顺序,力度大小,将之写为固定程序即为智能;如遥控器利用红外线,对空调进行开关、温度调节,如汽车的方向盘利用杠杆等复杂原理,将车头向左或向右,又如淘宝购物利用用户查看记录、收藏行为、个人各种特征如年龄、城市、薪资、家庭等因此,来进行个性化推荐,再如手机通过无线电和基站,进行彼此之间的通信等。以上讲的这些都是智能,通过对物理世界的认识,对器的制造和使用,给人类生活带来方便;而庄子认为造器给人们带来方便,会引导人类制造各种奇技淫巧,最终让人类迷失在这上面。自然庄子的解读主要是说,人类无法控制自己的欲望,将带来巨大的破坏力,对照目前的种种行为,这又何尝不是呢?因此,智能化带来人类一种危机:如何控制人类自己的欲望?虽然庄子的无为而治的消极处世思维让很多人不理解,但这在未来人类极速向前发展的道路上,可能是一剂良药,让人类停止下来思考的一剂猛药,而人类会在什么时候思考,是不是地球发生核战争之后呢,不得而知,但一定需要这样一个关口,人类才可以平衡向前发展。 想象一下,现在半智能化时代给人类生活带来各种便利,人类便贪心的希望全部智能化,又将会给人类生活带来哪些改变?上文讲到的《机器人总动员》和《机械公敌》有讲,人类将生活在程序化的时代,各种生活不能自理,又胖又蠢,无美好可言;与此同时,繁重的任务由机器人承担,做手术、送快递、做饭等,机器人将人类经验学到手中,不断复制,不断更新和进化,除了具备重复劳动的技能,逐渐拥有人类情感,终将形成智慧,拥有自主意识,成为人类社会的主体,将人类的选择权剥夺,使人类成为二等公民。而以上仅是猜想,考虑到每个人一出生需要学习新的“三本书”,而机器人却可以复制又复制,在这上面已经慢了一步,人类的优势在于想象力和创造力,机器又何尝不能呢?目前机器,不AI,已经在索尼公司被验证,可以通过阅读Beatles中“列侬“写的音乐包括歌词和旋律,创造出一首新歌,风格一致,版权归谁?随着IBM”深蓝“打败”象棋大师“之后,谷歌的AlphaGo打败人类的”围棋大师“,意味着AI在重复这一层次的逐渐强大,它可以阅读人类的所有知识,同时不断自我交互,使自己变的比人类强大的多;中国的从语言自动翻译、飞机自动驾驶开始,到”讯飞语音输入法“识别语音、高铁等铁路运行、”百度智能导航“+汽车巡航逐步实现,在AI的道路上实现一个又一个的突破,到现在为止可以说是人类之幸吧,未来呢? 上面回答两个问题:谁将成为社会主体-机器人,AI是否会成为人-会。这两条在上文解读的偏少,因此需要再次解读,人类将经历四个阶段,人类80%的劳动+20%的享受,50%+50%,20%+80%,无限趋近于0%+无限趋近于100%,而这中间谁是促进因素,无疑是AI,AI将逐渐剥夺人类劳动的权利,将人类变的愚蠢而又懒惰,你说这是AI的时代还是人类的时代,不好说吧?而AI将经历,从物理常识的使用,到物理常识应用增强,到物理常识的全应用,到物理常识的扩展,到物理常识的探索和自更新;读过《三体》的朋友应该对此记忆深刻,一个水滴可以让人类战舰全部毁灭,光速飞船的创造让太空失去生机,一片二向箔让多维向低维坍塌,可怕之处就在这里,人类不可能掌握的技术,AI却有可能提前掌握,那时候谁将成为社会主体-机器人,AI是否会成为人-不仅会,而且更强大,更可怕。人类的繁衍生殖,不断从头再来,就是为了防止人类成长过快,不断反思,不断返璞归真,来唤醒人类的本性,意识到自身的弱小,弱小即安全,从某种程度上可以这么说;《三体》有讲黑暗森林体系,当你牛到一定程度,你可以发现别人,别人也可以发现你,每个人都是带枪的猎人,当别人发现你的时候,会不加解释的”砰“一枪结束你的生命,而其他人也同样如此,在一个陌生的环境,生命显得尤其弱小和不安,下意识就会采取这样的操作,人类舰队的互相残杀,”蓝色空间号“和”青铜时代号“飞船对同类的残忍毁灭,”谁是英雄,活下来的!”,谁说不是呢?而弱小使得强大文明不屑一顾,或者无信号可发现,即可躲避灾难的来临,傻人有傻福,傻子天照顾,至少文明可以持久一些吧。 讲完前两条,其实透露着一些悲观思维,但“章北海”的悲观却给人类带来过生机,悲观在某些时候展示的弱小却可以成为拯救人类文明的稻草!接下来,讲讲后两条,AI的安全性和什么是对人类好:首先就要考虑AI网络的安全性,当AI无处不在,那么网络就成为战争的核心武器,核反应堆泄露、高铁脱轨,未来人造太阳异常,都不是不可能发生的事情,人类强大的同时,也在消耗着人类的福祉,人与自然到底该怎样的和谐共处不应该被每个人牢记心中吗?相信每个人都记得下面这个影像: 没错,就是汽车的智能驾驶系统被攻击后,《速度与激情8》展示的汽车武器攻击效果,可能无处不在的AI智能准备谋杀你,而人类此时显得是那么的脆弱,片中使用一个俄罗斯首相的无助展示给人们这种场景;无论是《速8》的汽车,还是《攻壳机动队》的AI服务机器人,它们是无处不在的危险制造者,我们不能像《机械公敌》里刚开头的女人一样相信机器人为她拿药,而警察误解它是抢包,如果天真,那必将是灾难。再说网络安全性,《欺骗的艺术》一书讲最终人们通过破解赌场的赌博机来获得收益,连网后“熊猫烧香”到最近的“比特币病毒”,无时无刻不在昭示着网络安全性的重要;说到这个,如何在互联网通信的五层协议,应用层、网络层、传输层、数据链路层、物理层对网络进行防护,如何在路由层和主机层对广域网和局域网进行监视和处理,将成为人类社会从业计算机行业同仁面临的重要课题,如果对安全感兴趣,推荐云舒大神在杭州创立的“默安科技”。随着电商时代的厮杀日渐减少,位置导航和移动支付的完成,说明这个时代的日趋完善,伴随着大数据产生的智能时代和小微企业服务,日后将成为互联网的主基调。三十年河东,三十年河西,大英帝国船坚炮利的传说似乎是在昨天,BAT、硅谷和国家科技的日益强盛引领世界的新未来。 想象一下,你的汽车被人恶意控制、车门紧关,你的房门被锁、家里的家具挥舞着各种向你逼近,门外无时无刻的空中坠落物,你是否会感觉到害怕?无人机运输确实可以降低劳动成本,而一切的AI都在降低着人类生活的成本,解放着人类的本能,但劳动的本质,区别于其他动物的本质,是否应该被每个人牢记心中?失去劳动,人类将失去一切,无劳动、不永生! 接下来是展望篇《AI将带我们去何方?(下-展望篇)》
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! AI即人工智能,对科幻着迷的博主对此认知颇深,打算从科幻电影入手,先讲下未来的AI将给人类带来哪些变化,哪些思考。 从最初的《星际航行》中的各种星球、地形等的介绍,到各个鉴于的探索,以及其中问题的出现和解决,Data即是AI的一个结果,存储大量信息、力量大、对人类好感、趋向人类情感的进化、舍己为人等行为和特征,具备定位和数据传输功能,成为人类向外星球传播文明的最佳伙伴。这其中介绍的宇宙飞船,以及其他辅助装置,必须有AI的元素在内,但DATA成为大多数人身边可能存在的对象,而给人印象深刻,它会不会成为未来伴随着我们的那个机器人呢?接下来,请继续! 《火星救援》中人们对重力等各种宇宙存在力场的应用,介绍人类组织的功能,互帮互助,不抛弃不放弃,如果这次丢下他,那么下次就是丢下你,战争同样如此,遗弃受伤的战士会极大削弱军队的战斗力;而《人类简史》介绍,人类之所以成长为今天的人类,区别于其他人种和物种,最基本的区别就是组织,其他人种和特种只具备100人左右的组织能力,而人类越大到无限,因此即使对方很强大,也会被击败,想想恐龙时代,想想几吨重的帝锷,人类的强大不在于自身能力的强大,而在于组织能力的强大,团结成为人类走到今天的最终极力量;而《圣经》有讲,人类最初要建造一个通天塔,来跟上帝进行直接交流,上帝为跟人类保持距离,派天使使建造者的人类语言变的不通,沟通成本变大后,建造成本也逐渐升高,人类不得不放弃这个计划。这个故事说明组织力量的强大,另一方面也说明人类的傲慢,要与跟自然界和神做斗争,缺失敬畏心,而人类想和自然和谐相处,保持敬天爱人的习性是必须做到的,而且说明上帝要维持其尊严,就需要与民众保持距离,减少日常生活的融合。中庸之道存在方方面面,人类一开始的无知无畏,到后来的强大的影响环境,甚至上帝,人类需要学会停滞,需要学会思考,需要做出调整,坚持还是改变原来的信念。 其中到《机器人总动员》介绍人类经历政治、民族冲突后,爆发核战争,导致地球不再适宜人类生存,建造巨大飞船,生活在太空中,有大卖场、人造沙滩、薯片可乐无限制供应,人类只剩下吃喝玩乐,一个个变成大胖子,生育集中处理,往来均有无人车,吃喝拉撒、光线和水等形成稳定的生态系统,讲述人类实现社会主义之后的情况,恐怕也是人类的美好理想,如果地球无纷争恐怕也是如此,如果地球有纷争,到达一个飞船后是不是也会继续呢?这很难讲。说了这么多,进入主题,瓦力和伊芙成为AI的代言人,分别负责执守地球,对垃圾进行分类,监测核污染情况,由太阳光供电,是不是跟京东的仓库机器人很像,伊芙成为检查瓦力工作的一个角色,共同发生的若干美好事情。但这部影片也说明一个问题,其中有一个冲突,地球宜居后,人类要不要回去,重新开始,还是像个机器一样生老病死,重复很多不需要思考和创造的事情,这时人类已经成为胖子,行动不便,如果是AI启动为人类更好生存的开关,那么人类恐怕是难以返回的,因为返回意味着苦难,AI无法理解人类创造美好的愿望,这个冲突在影片中确实发生了,而且如愿人类战胜AI,夺回选择权,重返地球,重新种植植物,建造房子,寻找伴侣,生儿育女,享受阳光和水,大山和湖泊。 最真切反映AI的是《机械公敌》这部影片,人类出现新的巨头公司USR,替代微软和谷歌,为人类提供基础的看护、快递、驾驶等重复而繁重的服务,人类似乎只剩下享受科技带来的美好,机器人三定律:第一、不允许伤害人类,第二、听从人类的命令,第三、不违反第一和第二条情况可,保证自身安全;看起来非常美好,人类似乎可以从此过上幸福的日子,但有没有想过一个问题,地球上任意一个准则或者法律都是由军队最终来保证其效力,如果失去这一条,那么一切法则均无法保证。该部影片的重点也在于此,军队由机器人组成,万一叛变不受控制,那么人类将掉入痛苦的深渊,如同《猿类崛起》中的猿对人的态度一样,也即人类此时对猴子的态度一样,失去的它将加倍拿回来,由自己的意愿来管理人类。这又是不是AI可悲的一面? 《攻壳机动队》讲的人是AI的另一部分:半自动化,即喜欢喝酒的人换个机器胃,想喝多少都没问题;手臂无力或残疾的换个手臂,力大无穷;想防弹连身体都换掉除了头,少佐就是这样;脑洞大开的你可以开启想象之门,天马行空的去想象。当AI的应用无处不在的时候,谁来保证它的安全性,什么叫作永生,记忆+任意一个人的身体?这些概念都令人深思。 总体来说AI的出现会带来若干问题,1、谁将成社会的主体,2、AI能否成为人,3、AI网络的安全性,4、什么叫对人类好。 从这4个问题入手,我们可以接下来考虑,人类需要什么样的AI?AI应该先进到什么地步?哪些领域可以使用AI而别的绝对不请允许? 如有兴趣,请阅读《AI将带我们去何方?(中)》
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! Git接触并使用多年, 工作中使用较多,它的分布式存储,使每个人的电脑均为服务器的策略非常棒;再加上使用Rsa加密,使得项目可以被自己管理,大众任意提交Pr进行完善,最后自己合并分支,使得Git成为当今计算机界最为流行的版本管理工具。 关于Git的日常应用,有的同事喜欢用GitFlow来界面化管理,而我更倾向于用TorteriseGit和GitBash命令行来操作,简单、便捷、趣味性是博主挑选工具的几个关键字。一般情况下用于公司项目管理,业余也喜欢自己做点东西,放到Git上,比如自己经常做的测试代码片段,如果用Zip包来管理,那么家里和公司如何同步,离职后代码如何跟新公司同步,很容易就想到GitHub,而Csdn和码云也有这样的功能,但Github声名日久,经典方便,所以选择它。 再介绍一下自己经常做的代码片断,从工作至今有很多,但较多被遗忘;主要用来测试某些想法,如算法、基本数据类型、多线程、读写流等方面,最终组成项目ThinkingInJava,地址:https://github.com/LiuzxGeek/ThinkingInJava 前面呢,其实已经有过一篇,不过觉得介绍的相对粗略,不够完善,本次将教程再丰富一下。Git教程及问题解析 先说上传: 第一步、下载Git工具,无论GitFlow还是TorteriseGit或GitBash,博主推荐后两个一起,因为自己就选择的它们 第二步、右击项目-选择gitbash,配置用户名和邮箱,用于提交时表明身份,git config user.name/email "name/email" 第三步、生成公、私钥,ssh-keygen -t rsa -C "如前面你的email"。将公钥放入GitHub,自己握有私钥,用于通信。 第四步、将公钥内容拷贝出来,放入Github-头像右边箭头-选择Settings-SSH and GPG keys-new SSH key-命名并放入 第五步、项目右击Git bash 执行git init(初始化)、git add .(将全部文件加入版本管理)和git commit -m "提交解释" 第六步、在Github下,new respsitory,新建名字为ThinkingInJava的项目,保存即可 第七步、继续在命令行输入 git push --set-upstream git@github.com:LiuzxGeek/ThinkingInJava.git master 至此,上传成功!接着再说同步 第一步、使用puttygen.exe,生成同步最需要的ppk文件,选择save public key和save private key(ppk文件) 第二步、在空白处,git clone,出现如下图文件,选择load putty key(以后就不用输入用户名、密码,直接拉代码) 第三步、选用,如果不想用git命令一直敲,可以用。选择项目,git Sync出现下图 点击Manage,将如下内容填入 如何同步远程分支,用来Merge代码。 1、进入项目目录,右键选择TortoiseGit中的merge选项,出现如下界面 2、点击这个“...”按钮,出现如下界面 3、右键remotes的项目名称,选择fetch from "项目名称"即可 git fetch origingit merge origin YOUR_BRANCH_NAMEgit pull origin YOUR_BRANCH_NAME 至此,全部教程已经完毕,你可以流畅的上传、下载、同步代码了,如果有其他问题,欢迎评论! 对了,本教程同步可以用于多个账号同时上传,只需要公私钥和ppk命名不同即可,email和name作为私人项目,可改可不改。 问题1:TortoiseGit拉取代码,一直提示输入密码? 修改项目地址从“http”开头的,换到"git"开头的地址。 问题2:git push origin master ,提示github permission denied fatal could not read from remote 修改项目地址从“git”开头的,换到"https"开头的地址。 问题1:TortoiseGit拉取代码,一直提示输入密码? 修改项目地址从“http”开头的,换到"git"开头的地址。
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! Java的注解、反射等机制的产生,让动态代理成为可能,一般通过全限定名+类名,找到类,可以invoke它的构造方法以及其他方法,可以获取它的参数(Field)名称和值。 注解一般用在代码的注释上、代码审查上(有没有按标准写,比如inspect)、代码注入(hook,asbectj),需要考虑的是,在何时注入(编译期还运行期) 反射一般用在动态将json和Object互相转化,执行相关底层代码,比如设置某个类的Accessible为false,防止别人hook修改 例:阿里的FastJson解析: @Override public <T> T json2Object(String json, Class<T> clazz) { return JSON.parseObject(json, clazz); } @Override public String object2Json(Object instance) { return JSON.toJSONString(instance); } 例Java的默认注解策略: public enum RetentionPolicy { /** * Annotations are to be discarded by the compiler. 默认,编译时被抛弃 */ SOURCE, /** * Annotations are to be recorded in the class file by the compiler * but need not be retained by the VM at run time. This is the default * behavior. 默认被编译器保解释,但在运行时抛弃 */ CLASS, /** * Annotations are to be recorded in the class file by the compiler and * retained by the VM at run time, so they may be read reflectively. * 被编译时解释,运行时仍保存,可以直接被使用 * @see java.lang.reflect.AnnotatedElement */ RUNTIME } 例Hook: hook一事看似神秘,其实并不是那么难,希望各位看官看过本文之后能有所收获。 本次是hook Android的点击事件,也就是OnClickListener,hook的意义在于你能在调用setOnClickListener后做些其他的事,其他一些你想和所有点击事件一起处理的事,那么在这里,我就以埋点为例吧。 先来展示下效果: public void onClick(View view) { Map map = new HashMap(); switch (view.getId()) { case R.id.btn_hook1: map.put("巴", "掌"); map.put("菜", "比"); break; case R.id.btn_hook2: map.put("TF-Boys", "嘿嘿嘿"); map.put("id", "111"); break; } view.setTag(R.id.id_hook, map); } 我在onClick内干了三件事: 1、new HashMap 2、map塞你想埋点的数据 3、把数据传到对应的view里 然后点击按钮会弹出一个Toast,如下图: 那么有意思的地方来了,我们并没有在点击事件里弹Toast,那这个Toast哪来的呢?嘿嘿嘿,当然是hook的啦。 Hook 下面开始hook过程: 整个过程浓缩下来就是四个字--移花接木! 分析源代码 首先来看看android.view.View中的这块代码,mOnClickListener变量静静的在这里(这里还有别的事件哦,比如OnLongClickListener等,大家学完之后可以试着hook下别的),我们需要做的就是移花接木,把自己的花替换掉这个木,mOnClickListener是ListenerInfo这个类的成员变量,那继续看看ListenerInfo在View的哪里被初始化了,因为我们最开始拿到的只有View这一个对象。 没错,找到了,getListenerInfo()干了这件事,我们从这个方法入手先把ListenerInfo拿下,然后再移花接木。 技术方案已经有了,那么就开始着手撸码。 实现 hook的过程就是充分利用java反射机制的过程,几行代码搞定,我们来看看: //先拿下View的Class对象 Class clazzView = Class.forName("android.view.View"); //再把getListenerInfo拿到 Method method = clazzView.getDeclaredMethod("getListenerInfo"); //由于getListenerInfo并不是pulic方法,所以需要修改为可访问 method.setAccessible(true); //继续拿下ListenerInfo内部类的Class对象 Class clazzInfo = Class.forName("android.view.View$ListenerInfo"); //拿到主角mOnClickListener成员变量 Field field = clazzInfo.getDeclaredField("mOnClickListener"); //截止到这,我们已经完成了百分之95了,只剩最后一步,那就是把我们的木接进来 //那么这里先暂时停留下,我们把木给创建好。 //挖个坑 --> 待会填 由于移花接木有个本质不能忘,那就是尊重原有类型,因此,我们的木也得实现View.OnClickListener接口: public static class HookListener implements View.OnClickListener { private View.OnClickListener mOriginalListener; //直接在构造函数中传进来原来的OnClickListener public HookListener(View.OnClickListener originalListener) { mOriginalListener = originalListener; } @Override public void onClick(View v) { if (mOriginalListener != null) { mOriginalListener.onClick(v); } StringBuilder sb = new StringBuilder(); sb.append("hook succeed.\n"); //拿到之前传递的参数 Object obj = v.getTag(R.id.id_hook); //下面的操作可以猥琐欲为了 if (obj != null && obj instanceof HashMap && !((Map) obj).isEmpty()) { for (Map.Entry<String, String> entry : ((Map<String, String>) obj).entrySet()) { sb.append("key => ") .append(entry.getKey()) .append(" ") .append("value => ") .append(entry.getValue()) .append("\n"); } } else { sb.append("params => null\n"); } Toast.makeText(v.getContext(), sb.toString(), Toast.LENGTH_LONG).show(); } } 以上代码就是我们的木,为了看起来更简单,我直接通过构造函数把原来的花(OnClickListener)给传过来了,然后在新的HookListener的onClick()里把原来的事件继续完成,并加上自己想猥琐欲为的一些事情。 那么继续填上之前埋的坑: field.set(listenerInfo, new HookListener((View.OnClickListener) field.get(listenerInfo))); 接木的过程干了两件事,一个是把原有的OnClickListener传给HookListener,二是把新的HookListener替换进ListenerInfo,perfect。 至此,移花接木就完成了,简单吧。 合适的调用hook 我们把hook方法都写好了,最后就是调用你需要hook的View了,在大多数情况下,你可以把hook这件事交给Base去做,遍历当前rootView所有的View,然后每个都调用hook,本文的重点不是这,我就不赘述了。 小结 本文仅仅以埋点为例,= = 其实我觉得埋点这个栗子并不太好,妹的都传了这么多参数过来了,还在乎在这里调用一下自己的tracker?不管了,没有栗子会让本次hook感觉很无力,希望各位同学看过后能对hook不再懵逼,其实和自定义View一样简单的啦。 Sample代码已同步到github上,有问题可以提issue => https://github.com/JeasonWong/ClickTracker
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 线程池伴随着线程的产生而产生,主要用于线程复用,减少内存泄露。 线程池中使用Thread作为执行体,使用Runnable接口作为执行者,一个个执行者以任务的方式在执行体里完成。 任务以下指一个实现Runnable接口的Worker对象,任务放在Thread中被执行 原理: corePoolSize:活跃线程数量,以下简称core(不能小于0) maxPoolSize:线程池总数量,以下简称max(不能小于0,core必须小于max) keepAliveTime:超过core个线程的空闲时间,超时被停止,以下简称keep(不能小于0) ctl:配合isRunning方法,用来标记线程数和运行状态,如不符合则线程池被关闭。 一个新任务被加入,如果线程数量小于core,则新建一个线程,直到数量等于core; ExecutorService: newCachedThreadPool 无界线程池,可动态改造,默认执行器,以下简称Cached newFixedThreadPool 固定大小的执行器,以下简称Fixed,默认ExecutorService newSingleThreadExecutor 单线程执行器,以下简称Single Queue: SynchronousQueue 无需等待,直接进入线程执行,要求使用Cached,否则一时无线程则任务被抛弃 LinkedBlockingQueue 无界队列,用于core繁忙时等待,任务之间互相无影响,优势是可以短时间接受大量任务 ArrayBlockingQueue 有界队列,使用大池子和小队列,来减少资源消耗(CPU、IO、线程切换) Policy: RejectedExecutionHandler 当ExecutorService shutdown时,抛出拒绝的异常 CallerRunsPolicy:减缓任务的执行速率 DiscardPolicy: 直接抛弃 DiscardOldestPolicy:位于栈顶的任务被抛弃,会重新执行此任务,则抛弃老的任务 Worker: 使用AbstractQueuedSynchronizer锁 第一、保证操作去唤醒一个等待的任务,而不是中断一个正在执行的任务 第二、作为一个互斥锁,不使用ReentrantLock,希望在调用setCorePoolSize方法后,不必使worker重新获得锁 execute方法: 任务将会在一个线程中被处理,线程可能是一个新线程(线程数小于corePoolSize),也可能是一个已经存在的线程(线程数大于corePoolSize,小于maxPoolSize) 4种情况会被拒绝执行: 第一、线程池已经shudown(主动执行shutdown,或被interrupt) 第二、线程数已经超过maxPoolSize(可设为Integer.MaX_VALUE以防止发生) 第三、线程池为空 第四、超时 拒绝执行,如果使用的handler为RejectedExecutorHandler(默认),则任务被抛弃,并报错 线程没有RuntimePermission,则会被停止 执行前后,分别会运行beforeExecute和afterExecute 执行: 小于core则new新thread 大于core则优先进入queue等待 如不能则new新thread 大于max则任务默认被拒绝 shutdown方法: 顺序关闭所有任务(任务池的线程仍会执行完毕,但新任务不再被接受) executor长久不被调用,同时无剩余线程,会自动执行shutdown; 实现方式: 目的让空闲线程被杀掉 方式有二,设置keep时间短一点,其次设置core小一点同是allowCoreThreadTimeOut为true shutdownNow方法: 立即停止所有任务,包含正在执行的,并返回这些“正在执行的任务”的list集合 remove方法: 停止未执行的任务;如果使用submit把任务当成future类型,则不会停止 purge方法: 清除已经被停止的future任务,用于存储功能改造;如果被其他线程干扰,则会失败。 判断启动corePool的数量: prestartCoreThread:启动一条core线程 ensurePrestart:同上,如果corePoolSize为0也启动一条线程 prestartAllCoreThreads:将所有core线程均启动 Tips: 1、 如果任务数大于core但小于max,则新线程被创建(建议max为Integer.MAX_VALUE) 提示:core和max应该被动态的设置,keep用于有效减少资源占用 将allowCoreThreadTimeOut设为true,可以让core同样有此功效(时间需要大于0,超时后一个个被interrupt),设置后直接把运行的空闲任务全部清除 2、 为获得更大效率,一般IO读写设置core为2*CpuSize,逻辑较多避免上下文切换,设置为CpuSize 3、 submit比 exexute仅多一个返回值,标识完成。
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 大的方向上 第一、使用进程共享的方式,往往使用android:process=remote,这样开启一个新的进程,使得所有进程都可以访问这个进程,使服务可以在多进程共享;而android:process=:remote相当于给当前进程一个私有进程,用来维护其自身的业务处理。开启新进程可以用在activity、service、broadcastReceiver、ContentProvider等组件。 If the name assigned to this attribute begins with a colon (':'), a new process, private to the application, is created when it's needed and the service runs in that process. If the process name begins with a lowercase character, the service will run in a global process of that name, provided that it has permission to do so. This allows components in different applications to share a process, reducing resource usage. 第二、使用广播的方式,可以设置category、component、package来区别广播,同时使用自定义权限来做限制 方法1、使用AIDL的方式进行 第三、使用Message+Binder 第四、使用Socket 第五、共享内存,如ContentProvider linux有管道(Pipe)、信号(Signal)、报文(Message)、跳跃(Trace),其次信号量(Semaphore)、ShareMemory Binder能支持跨进程通信的原因,是它实现IBinder接口,系统定义实现此接口即赋予进程通信的功能。 优势:做数据拷贝只用一次,则Pipe、Socket都需要两次;其次安全性高,不会像Socket会暴露地址,被人替换; 通信机制:Service向ServiceManager注册,得到虚拟的Uid和Pid;Client向ServiceManager请求,得到虚拟的Uid和Pid,以及目标对象的Proxy,底层通过硬件协议传输,使用Binder通讯。 通信时Client手持Proxy,ServiceManger进行转换,调用到Service。三者运行在三个独立进程中。Client/Sever全双工,互为Sever/Client 获得进程是否位于前台: public static boolean isRunningForeground(Application application) { ActivityManager activityManager = (ActivityManager) application.getSystemService("activity"); List appProcessInfos = activityManager.getRunningAppProcesses(); Iterator var3 = appProcessInfos.iterator(); ActivityManager.RunningAppProcessInfo appProcessInfo; do { if (!var3.hasNext()) { return false; } appProcessInfo = (ActivityManager.RunningAppProcessInfo) var3.next(); } while (appProcessInfo.importance != 100 || !appProcessInfo.processName.equals(application.getApplicationInfo().processName)); return true; }
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! Instant Run为我们提供了增量更新的方式,支持 gradle2.0和Android4.0以上版本,让构建项目变的快速,同时由于multiDex出现,增量更新也变的简单,但现实状况是Studio编译依然卡卡。 Facebook的Buck是不错的,但它只支持Linux开发机;而Alibaba 开源的Freeline却可以在Windows机器上使用,根据依赖关系充分利用缓 存最少编译,并开启SocketServer保证crash同时也可以进行编译,最终实现全量编译和增量编译同时进程,提高2-3倍的编译速度。 注意事项 1、只能使用2.2以上的gradle版本和python2.7及以下版本,配置好环境变量 2、使用jcenter编译而非mavenCenter,因为某些gradle后者没有 3、此文件里的gradle版本需要更换,csdn限制只能传80M以内的文件,大家只好到官网下载,地址:gradle3.3 4、 主项目的build.gradle加入如下内容: buildscript { repositories { jcenter() } dependencies { classpath 'com.antfortune.freeline:gradle:0.8.5' } } apply plugin: 'com.antfortune.freeline' dependencies { compile 'com.antfortune.freeline:gradle:0.8.5' } 目前最高版本是0.8.5 5、配置完,在Android Studio的Terminal里执行如下命令,代表从国内镜像下载,要快的多。 gradlew initFreeline -Pmirror 其他特色Features 支持标准的多模块 Gradle 工程的增量构建 并发执行增量编译任务 进程级别异常隔离机制 支持 so 动态更新 支持 resource.arsc 缓存 支持 retrolambda 支持 DataBinding 支持各类主流注解库 支持 Windows,Linux,Mac 平台 以下列表为 Freeline 支持的热更新情况: Java drawable, layout, etc. res/values native so add √ √ √ √ change √ √ √ √ remove √ √ x - Freeline 已经分别在 API 17,19,22,23 的 Android 模拟器、Android 6.0 Nexus 6P 以及 Android 4.4 锤子手机上经过测试。如果想要充分体验 Freeline 增量编译的速度的话,最好使用 Android 5.0+ 的设备。 限制Limitations 第一次增量资源编译的时候可能会有点慢 不支持删除带 id 的资源,否则可能导致 aapt 编译出错 暂不支持抽象类的增量编译 不支持开启 Jack 编译 不支持 Kotlin/Groovy/Scala 欢迎 大家提意见! 官方地址:https://github.com/alibaba/freeline/ 另外项目打包工具Jenkins也值得研究一下:http://blog.csdn.net/mabeijianxi/article/details/52680283
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! Git教程 最近用Git比较多,做出教程一份,供大家参考。 1、 安装Git,并配置环境变量 2、 配置用户名,邮箱 3、 切到工作目录下,下载源码,先执行git init 初始化一个环境,然后 4、 修改并推送到本地,最后推送到远程 5、 其他 ,Git是分布式工具,每个人电脑上都是一个仓库,大家通过局域网互换文件,达到协同办公的目的。 命令: git reset –hard commitId回滚commitId这次提交 git branch dev 无则生成,有则删除分支dev git branch 查看本地分支 git remote 查看远程分支 –v查看更详细信息 git checkout dev 切换到dev文件下 git rm build.gradle 本地删除build.gradle文件 git status 查看工作目录的状态,是否有删除或未提交等等 git checkout build.gradle 本地恢复build.gradle文件 git log 查看push提交日志 cat 文件名新建文件 :q 进入Vim命令后退出操作 git checkout dev 切换到dev文件下 git checkout –b dev 无则创建,切换到dev文件下 git merge dev 合并 dev分支修改(git pull origin dev一样可行) git branch -d dev 删除dev分支 git stash 保存现场,从master切分支修复bug git stash list 保存了哪些现场 git stash apply 恢复 drop删除 git stash pop 恢复并删除 git push origin master/dev 推送本地到远程 feature同分支功能,主要开发新功能使用 使用方法例: git checkout –b feature-Iot新建feature git checkout –d feature-Iot 删除feature git tag 列出所有tag git tag v1.0 –m “v1.0” 打1.0的tag git push origin v1.0 推送分支到远程 git tag –d v1.0 本地删除tag git push origin :refs/tags/v1.0 -d 删除 -m 加标签 拉一个tag的代码 git clone --branch v1.6.5.7 https://github.com/ManaPlus/ManaPlus.git 6、 本地项目首次加入Git管理---慎用!!! Git push –u origin master 7、 修改远程项目为本地项目---慎用!!! 将目标项目拷贝到当前文件夹 Git init Git pull 删除本地的远程项目 添加并提交到远程 完成本次操作。 问题1:git clone/pull时,出现Permission Denied。 回答:主要问题1、在于权限未配置,请联系项目Owner配置权限;2、将本地.ssh文件夹中的id.rsa配置到远程的SSH的Keys下面;3、本地存在github账号,与公司项目冲突,使得Git.bash不清楚你要使用哪方的账号信息,那么需要在.ssh目录下加入一个config文件,并将如下信息配置(原理如DNS): # gitlabHost gitlab.YourCompany.comHostName gitlab.YourCompany.comUser YourName@YourCompany.comIdentityFile ~/.ssh/id_rsaPort YourServerCode 如8090 生成rsa文件 ssh-keygen -t rsa -C "youremail" 问题2:通过TortoiseGit来拉代码,出现让输入密码,无论怎么输入或正确密码,均不能通过 回答:原因在于在user目录下的.ssh文件夹生成,如果无则不会出现;TortoiseGit使用putty来拉,此时缺少ppk文件,需要使用puttygen.exe来使用id_rsa文件生成新的ppk文件,generate-save private key即可。最后把此文件加入设置进来,如下图-右键git clone,选择Load Putty Key选项即可 问题3、fatal: 'origin' does not appear to be a git repositoryfatal: Could not read from remote repository.Please make sure you have the correct access rightsand the repository exists. 解决方案: 1、查看配置 cat $(git rev-parse --show-toplevel)/.git/config 2、git remote add origin "url.git"
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 网络请求经过客户端请求,发送数据包、代理(转发)、隧道(信道)、网关(DNS),最终到服务器。我们都知道网址要通过DNS解析成IP才能到达服务器,也就说IP变化其实到达的是不同的服务器;而这里就涉及到三个问题,第一DNS怎样解析为IP,第二怎样防止IP被篡改,第三IP是否需要缓存。 首先DNS解析的目的是为了得到用户IP,将服务交给距离最近的服务器处理;使用IP直连可以减少解析等待时间,httpdns工具可以解析网址获得IP,例如DnsPod,好处是绕过运营商的LocalDNS,减少域名解析异常情况的出现。 其次IP被篡改,往往由于DNS解析过程出错或者被劫持,这时可以使用本地代理来处理网络请求,将网络请求的数据+签名信息进行校验,同时使用dnspod解析出ip进行校验是否是合法ip,最后使用https的方法发起网络请求。 接着DNS一般都存在缓存时间,放置在java.net.InetAddress和虚拟机层,如果没有则查询DNS服务器获取。通过如下代码设置虚拟机层的DNS缓存时间。 Security.setProperty("networkaddress.cache.ttl", String.valueOf(-1));//查询成功的缓存 -1表示久缓存 Security.setProperty("networkaddress.cache.negative.ttl", String.valueOf(0));//查询失败的缓存 0代表永不缓存 单位是秒,其他时间代表缓存时间,而4.0以前是永久缓存,4.0以后默认为2秒 顺便讲下Http请求 get可以加入书签在浏览器端做缓存,意思是从服务器获取数据(查) post不可以加入书签,意思是向服务器传输数据(改) put(增) delete(删) http://www.w3school.com.cn/tags/html_ref_httpmethods.asp 弱网情况下,只好通过压缩数据包来增加请求成功率,详见: http://www.cnblogs.com/answer1991/archive/2012/05/07/2487052.html UTC:原子时 GMT:格林威治时间 Http和Socket通信均属于TCP通信,均是三次握手,四次关闭。 关于网络安全与网络优化 JsConsole:body.contentEditable=true使Elements变的可写入 http://blog.csdn.net/u010983881/article/details/52692788
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 组件化与插件化:前者对功能进行拆分后,独立开发,打成一个包发布;后者对功能拆分,使用主包+分包,可以分别独立发布。 热更新:两种方式,一种是multidex,一种是修改指针;前者需要重新启动,后者无需重新启动。指下载patch包,修复错误的一种方式。 热加载:即针对插件化的实现,当用户用到此功能,再去下载对应patch包的一种实现方式。 热部署:无需要重启Application和Activity(修改指针更新方式) 温部署:需要重启Activity(修改指针更新方式) 冷部署:需要重启Application(multidex更新方式) Android热加载出现的原因在于:第一5.1出现之前没有好的办法解决App方法数超过65536的问题,第二启动特别慢,因为加载的模块比较多。本质上还是虚拟机支持JIT的加载机制。 AndroidDynamicLoader 是最早分析的动态加载框架,主要使用activity当壳fragment当内容的方式,来使用空壳activity的应用,将满是fragment的apk加载进来,使用activity的生命周期来控制fragment的加载,其中原来自己做的一个自动化测试框架也是同样原理。 small 目前最新的热加载框架,支持切割dex实现动态加载,方便业务模块、公共的升级。 multiDex使用时,会判断系统是否支持multiDex,然后判断有无二级dex要安装,将二级dex解压到secondary-dexes目录,通过反射注入ClassLoader的pathList中,完成完整安装。 HotFix出现的原因在于:从出现Bug,解决Bug,再发版,再升级,这个过程过于漫长,而且有的用户不愿意升级,影响功能的使用以及产品的体验。 AndFix 采用差分包的方式,将要修复的文件打成dex包,通过注解的方式定位到要修改的文件,最终用jni在c层替换掉原文件的指针达到热修复的目的(这是一种So库的Hook方式,其他Hook方式多采用反射方式实现)。 nuwa QQ空间使用类似办法,通过往classloader的pathlist里加入一个dex,采用覆盖的方式来替换到原来的模块。 增量更新是谷歌提出的一种App更新方式,Instant Run主要应对更新包过大,耗费流量的问题。 老版本2.0,新版本2.1,使用bsdiff工具分解出两者的差异包patch.jar,将patch发布,在客户端使用native的patch方法将data/app目录下的拿出的apk与patch.jar合并,之后再重新安装即可;此时的apk与原2.1的apk的md5一致。 优点:下载包小,流量耗费少 缺点:patch时间有点长,需要异步处理 Tinker,原理跟增量更新一样 相比而言类似Qzone的Nuwa是最好的 关于热修复,也被称为插件,目前比较流行的有HotFix、Nuwa、DroidFix、AndFix等,这些框架均可以在github或其他地方找到,原理如上,方法多样,有覆盖的、有重定向的等等,通过配置、设置action等方式;而作为插件需要满足以下条件: 1、可以独立安装,但不可独立运行 2、具有向下兼容性,即可拓展性 3、只能运行在宿主程序中,而且可以被禁用、替换 使用场景包括修复线上bug、做手机皮肤、开发应用商店等系统提供的接口 Android 中有三个 ClassLoader, 分别为URLClassLoader、PathClassLoader、DexClassLoader。其中 URLClassLoader 只能用于加载jar文件,但是由于 dalvik 不能直接识别jar,所以在 Android 中无法使用这个加载器。 PathClassLoader 它只能加载已经安装的apk。因为 PathClassLoader 只会去读取 /data/dalvik-cache 目录下的 dex 文件。例如我们安装一个包名为com.hujiang.xxx的 apk,那么当 apk 安装过程中,就会在/data/dalvik-cache目录下生产一个名为data@app@com.hujiang.xxx-1.apk@classes.dex的 ODEX 文件。在使用 PathClassLoader 加载 apk 时,它就会去这个文件夹中找相应的 ODEX 文件,如果 apk 没有安装,自然会报ClassNotFoundException。 DexClassLoader 是最理想的加载器。它的构造函数包含四个参数,分别为: dexPath,指目标类所在的APK或jar文件的路径.类装载器将从该路径中寻找指定的目标类,该类必须是APK或jar的全路径.如果要包含多个路径,路径之间必须使用特定的分割符分隔,特定的分割符可以使用System.getProperty(“path.separtor”)获得. dexOutputDir,由于dex文件被包含在APK或者Jar文件中,因此在装载目标类之前需要先从APK或Jar文件中解压出dex文件,该参数就是制定解压出的dex 文件存放的路径.在Android系统中,一个应用程序一般对应一个Linux用户id,应用程序仅对属于自己的数据目录路径有写的权限,因此,该参数可以使用该程序的数据路径. libPath,指目标类中所使用的C/C++库存放的路径 classload,是指该装载器的父装载器,一般为当前执行类的装载器 Tinker后台搭建:https://github.com/baidao/tinker-manager Android6.0以后的内联策略会给热修复带来一定的影响:http://dwz.cn/4O15FS 每次构建时 ProGuard 都会输出下列文件: dump.txt 说明 APK 中所有类文件的内部结构。 mapping.txt 提供原始与混淆过的类、方法和字段名称之间的转换。 seeds.txt 列出未进行混淆的类和成员。 usage.txt 列出从 APK 移除的代码。 压缩APK: 1、减少ProGuard的keep数量,例:去掉openmobileapi明显26M降到25M 2、官方https://developer.android.com/topic/performance/reduce-apk-size.html 简单Hook方式:https://www.diycode.cc/topics/568原理:Java高级之虚拟机加载机制 待研究项目: https://github.com/limpoxe/Android-Plugin-Framework https://github.com/kaedea/android-dynamical-loading
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 架构这个话题比较大;从组织结构来说,CEO是一家公司的架构师,将公司划分为销售部、市场部、技术部、行政部、财务部、客服部等,每个部门履行特有的职能并相互配合,最终实现“产品”的有效输出;技术架构也同样如此。 项目架构主要目的是解耦、灵活开发,让后端成为产品的瓶颈,而不要让客户端成为产品的瓶颈,所有的业务均 可实现“热加载”;大的项目需要进行插件式开发,必须减少依赖关系,这样编译和执行才会快,用户体验才会好。 问题: 1、如何保证http请求从App这里拿到相应的缓存数据,而不再请求服务器? 客户端与服务端商定Get请求的时效,可以有效解决部分并发问题。 Android项目框架升级尝鲜OkHttp 2、应用如何保活? Android初级第八讲之应用如何保活 3、如何优化电量和内存? Android高级之十二讲之内存、电量、卡顿、流量 4、安全问题:定制执行特定操作、目录白名单、灰度发布等方式来安全加载应用。 5、缓存问题:LruCache-Least Recently Used Cache,清除最近最少使用的缓存。 原理:设计缓存大小,使用LinkedList存取数据;get时使用数+1,put时使用数+1,同时监测内存是否超界; 如果超界,则开启死循环,清除最少使用的缓存(通过linkedHashMap的eldest找到),然后bread;继续。 Android高级之十三讲之安全方案 灰度发布: 主要是A/B测试,分功能模块、地区、用户比例来测试,同时可以及时停止测试,防止意外发生,同时又 避免影响线上用户体验。 其次对比实验,用数据来确定采用哪套方案。 架构的主要工作有哪些? 0、构造设计模式,MVC、MVP或MVVM,数据存取、网络工具、加载过程等实现。 1、设计基础功能模块 就像生活必备水电煤、米油盐一样,有这些日子才能过起来,项目也一样。设计模式、组件通信、加载过程、网络框架、图片框架、线程池管理、UI框架、必要组件、必要功能、基础组件等均在初期需要制订。 2、设计基本功能模块 细化项目框架和组件,以及样式,设计项目的核心功能,分配功能模块,灵活设计可插拔模块 3、减少业务耦合度 AOP思想,面向切面编程,即专注处理自身模块,通过scheme来告诉外界自己的功能,通过Filter来过滤可以处理细化的信息。 Android中可以使用RxBus和自定义路由来实现。 Otto据说比EventBus、handler、BroadcastReceive和interface更简单,回头要研究一下。 将订阅者及内部的方法以键值对的形式存入RxBusFactory,事件源发送事件根据消息类型交由不同的订阅者处理。 4、保持调用灵活性 接口和类要全面适配,如参数类型、参数数量、API版本、so库支持等 5、良好的编译工具和打包工具 编译工具:Android最佳编译工具介绍 打包工具:Ant、Gradle、Python三种打包方式的介绍 Gradle加速编译:http://droidyue.com/blog/2017/04/16/speedup-gradle-building/index.html Lint用于找出应用中的异常以及不规范的操作 http://tech.meituan.com/android_custom_lint.html https://github.com/shwenzhang/AndResGuard 6、项目管理工具,建议用Git,跟Svn的最大区别在于:分布式管理,每个人的电脑都是服务器。 Git教程及问题解析 架构师的修养:阿里中间件需要怎样的架构师? Live地址:知乎 服务搭建:https://www.diycode.cc/topics/738 FindViewById的快速工具:http://www.cnblogs.com/klcf0220/p/5924440.html 谷歌开源检查应用错误工具:Error-prone markdownpad: 先安这个: http://markdownpad.com/download/awesomium_v1.6.6_sdk_win.exe 再安这个:http://markdownpad.com/ MVVM:http://download.csdn.net/detail/liuxian13183/9884386 网站使用www.example.com和example.com的区别: 后者携带该网站所有cookie信息,不够安全;一般通过301设置,将后者跳到前者。一方面有利于统计访问(对SEO无影响),另一方面不会给用户造成困扰(后者未作配置,无法访问)。 Fiddler使用:保证手机网络和电脑网络处于同一个路由下面,同一个IP段。
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 架构这个话题比较大;从组织结构来说,CEO是一家公司的架构师,将公司划分为销售部、市场部、技术部、行政部、财务部、客服部等,每个部门履行特有的职能并相互配合,最终实现“产品”的有效输出;技术架构也同样如此。 项目架构主要目的是解耦、灵活开发,让后端成为产品的瓶颈,而不要让客户端成为产品的瓶颈,所有的业务均 可实现“热加载”;大的项目需要进行插件式开发,必须减少依赖关系,这样编译和执行才会快,用户体验才会好。 问题: 1、如何保证http请求从App这里拿到相应的缓存数据,而不再请求服务器? 客户端与服务端商定Get请求的时效,可以有效解决部分并发问题。 Android项目框架升级尝鲜OkHttp 2、应用如何保活? Android初级第八讲之应用如何保活 3、如何优化电量和内存? Android高级之十二讲之内存、电量、卡顿、流量 4、安全问题:定制执行特定操作、目录白名单、灰度发布等方式来安全加载应用。 5、缓存问题:LruCache-Least Recently Used Cache,清除最近最少使用的缓存。 原理:设计缓存大小,使用LinkedList存取数据;get时使用数+1,put时使用数+1,同时监测内存是否超界; 如果超界,则开启死循环,清除最少使用的缓存(通过linkedHashMap的eldest找到),然后bread;继续。 Android高级之十三讲之安全方案 灰度发布: 主要是A/B测试,分功能模块、地区、用户比例来测试,同时可以及时停止测试,防止意外发生,同时又 避免影响线上用户体验。 其次对比实验,用数据来确定采用哪套方案。 架构的主要工作有哪些? 0、构造设计模式,MVC、MVP或MVVM,数据存取、网络工具、加载过程等实现。 1、设计基础功能模块 就像生活必备水电煤、米油盐一样,有这些日子才能过起来,项目也一样。设计模式、组件通信、加载过程、网络框架、图片框架、线程池管理、UI框架、必要组件、必要功能、基础组件等均在初期需要制订。 2、设计基本功能模块 细化项目框架和组件,以及样式,设计项目的核心功能,分配功能模块,灵活设计可插拔模块 3、减少业务耦合度 AOP思想,面向切面编程,即专注处理自身模块,通过scheme来告诉外界自己的功能,通过Filter来过滤可以处理细化的信息。 Android中可以使用RxBus和自定义路由来实现。 Otto据说比EventBus、handler、BroadcastReceive和interface更简单,回头要研究一下。 将订阅者及内部的方法以键值对的形式存入RxBusFactory,事件源发送事件根据消息类型交由不同的订阅者处理。 4、保持调用灵活性 接口和类要全面适配,如参数类型、参数数量、API版本、so库支持等 5、良好的编译工具和打包工具 编译工具:Android最佳编译工具介绍 打包工具:Ant、Gradle、Python三种打包方式的介绍 Gradle加速编译:http://droidyue.com/blog/2017/04/16/speedup-gradle-building/index.html Lint用于找出应用中的异常以及不规范的操作 http://tech.meituan.com/android_custom_lint.html https://github.com/shwenzhang/AndResGuard 6、项目管理工具,建议用Git,跟Svn的最大区别在于:分布式管理,每个人的电脑都是服务器。 Git教程及问题解析 架构师的修养:阿里中间件需要怎样的架构师? Live地址:知乎 服务搭建:https://www.diycode.cc/topics/738 FindViewById的快速工具:http://www.cnblogs.com/klcf0220/p/5924440.html 谷歌开源检查应用错误工具:Error-prone markdownpad: 先安这个: http://markdownpad.com/download/awesomium_v1.6.6_sdk_win.exe 再安这个:http://markdownpad.com/ MVVM:http://download.csdn.net/detail/liuxian13183/9884386 网站使用www.example.com和example.com的区别: 后者携带该网站所有cookie信息,不够安全;一般通过301设置,将后者跳到前者。一方面有利于统计访问(对SEO无影响),另一方面不会给用户造成困扰(后者未作配置,无法访问)。 Fiddler使用:保证手机网络和电脑网络处于同一个路由下面,同一个IP段。
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! Android适配需要考虑方方面面,主要是图片字体大小和API,但也要考虑其他场景下的一些问题。 先熟悉一下Android设备的dpi(dots per inch),设计上叫ppi(pixel per inch),即1inch中有多少个像素 1、考虑横竖屏幕,注意屏幕布局资源的替换和页面数据的保存和重加载,配合onSaveInstance和onRestoreInstance使用 以及适配的屏幕尺寸,通过在主配置文件的Application处,设计supports-screen,normal、large、xlarge等 以下摘抄自Google官方:https://developer.android.com/guide/practices/screens_support.html 屏幕尺寸 按屏幕对角测量的实际物理尺寸。 为简便起见,Android 将所有实际屏幕尺寸分组为四种通用尺寸:小、 正常、大和超大。 屏幕密度 屏幕物理区域中的像素量;通常称为 dpi(每英寸 点数)。例如, 与“正常”或“高”密度屏幕相比,“低”密度屏幕在给定物理区域的像素较少。 为简便起见,Android 将所有屏幕密度分组为六种通用密度: 低、中、高、超高、超超高和超超超高。 方向 从用户视角看屏幕的方向,即横屏还是 竖屏,分别表示屏幕的纵横比是宽还是高。请注意, 不仅不同的设备默认以不同的方向操作,而且 方向在运行时可随着用户旋转设备而改变。 分辨率 屏幕上物理像素的总数。添加对多种屏幕的支持时, 应用不会直接使用分辨率;而只应关注通用尺寸和密度组指定的屏幕 尺寸及密度。 密度无关像素 (dp) 在定义 UI 布局时应使用的虚拟像素单位,用于以密度无关方式表示布局维度 或位置。 密度无关像素等于 160 dpi 屏幕上的一个物理像素,这是 系统为“中”密度屏幕假设的基线密度。在运行时,系统 根据使用中屏幕的实际密度按需要以透明方式处理 dp 单位的任何缩放 。dp 单位转换为屏幕像素很简单: px = dp * (dpi / 160)。 例如,在 240 dpi 屏幕上,1 dp 等于 1.5 物理像素。在定义应用的 UI 时应始终使用 dp 单位 ,以确保在不同密度的屏幕上正常显示 UI。 支持的屏幕范围 从 Android 1.6(API 级别 4)开始,Android 支持多种屏幕尺寸和密度,反映设备可能具有的多种不同屏幕配置。 您可以使用 Android 系统的功能优化应用在各种屏幕配置下的用户界面 ,确保应用不仅正常渲染,而且在每个屏幕上提供 最佳的用户体验。 为简化您为多种屏幕设计用户界面的方式,Android 将实际屏幕尺寸和密度的范围 分为: 四种通用尺寸:小、正常、 大 和超大 注:从 Android 3.2(API 级别 13)开始,这些尺寸组 已弃用,而采用根据可用屏幕宽度管理屏幕尺寸的 新技术。如果为 Android 3.2 和更高版本开发,请参阅声明适用于 Android 3.2 的平板电脑布局以了解更多信息。 六种通用的密度: ldpi(低)~120dpi ~density=0.75 mdpi(中)~160dpi ~density=1 hdpi(高)~240dpi ~density=1.5 xhdpi(超高)~320dpi ~density=2 xxhdpi(超超高)~480dpi ~density=3 xxxhdpi(超超超高)~640dpi ~density=4 以上是对屏幕密度的描述,希望大家明白一点:分辨率越高的手机,指的是密度越高,即单位区域内像素越多。 从 Android 3.2(API 级别 13)开始,以上尺寸组已弃用,您 应改为使用 sw<N>dp 配置限定符来定义布局资源 可用的最小宽度。例如,如果多窗格平板电脑布局 需要至少 600dp 的屏幕宽度,应将其放在 layout-sw600dp/ 中。声明适用于 Android 3.2 的平板电脑布局一节将进一步讨论如何使用新技术声明布局资源。 如果未配置到相应尺寸,则高分辨率的会向高配置的资源扩展,低分辨率的会向低配置的资源扩展。举个栗子:只有hdpi、xhdpi三种资源,则mdpi的手机默认使用hdpi,而xxhdpi使用xhdpi,以实现屏幕的最佳显示效果。 关于图片、布局和密度,根据上述参数可以做如下配置 上面是对大体的尺寸做适配,而安卓的厂商不要太多,各家规范又不太一致,因此可以对具体的尺寸做适配。 以上即对图片、布局,甚至具体尺寸做的适配。 适配的部分代码 switch (unit) {case COMPLEX_UNIT_PX: return value;case COMPLEX_UNIT_DIP: return value * metrics.density;case COMPLEX_UNIT_SP: return value * metrics.scaledDensity;case COMPLEX_UNIT_PT: return value * metrics.xdpi * (1.0f/72);case COMPLEX_UNIT_IN: return value * metrics.xdpi;case COMPLEX_UNIT_MM: return value * metrics.xdpi * (1.0f/25.4f); }return 0; 2、字体、布局大小 字体尽量用sp,间距和尺寸尽量用dp,理解density就可以明白,这样字体、间距和尺寸就会随着屏幕密度而自动改变为px 3、不同系统API不同-如setBackground,碎片化问题 举个栗子:不同系统对从webView本地拿图片方法不一样 public void openFileChooser(ValueCallback<Uri> uploadMsg) { listener.showConfirmDialog(uploadMsg); } // For Android 3.0+ public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) { listener.showConfirmDialog(uploadMsg); } // For Android 4.1 listener.showConfirmDialog(uploadMsg); } 更多详见:http://blog.csdn.net/reboot123/article/details/52461897 4、Cpu不同-如arm、mips、x86 一般不同手机,会适配不同硬件,而选择合适的cpu是驱动不同硬件达到完美状态的关键,arm是主流。厂商会根据不同功能如拍照美颜、视频流畅度选择不同cpu,有的比较放松如Nexus,有的比较严格如Oppo;严格的结果就是,如果你没有适配它的so包,则相关功能就不可用,银联3.2.5版本即是如此。 一般情况下只适配arm-v7a即可,x86会自动转译,mips几乎可以忽略 更多关于cpu的介绍:http://blog.csdn.net/reboot123/article/details/51601368 5、状态栏、导航栏、菜单栏的长度 比如:http://blog.csdn.net/u010156024/article/details/48321485 6、横竖屏切换,且各生命周期不再重新调用 1)Activity设置 android:configChanges="keyboard|screenSize|orientation|layoutDirection" 2)在Activity中实现onConfigurationChanged方法,重新布局和初始化数据(注意全局变量数据依然存在) @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); mScreenChanged.set(true);//记录横竖屏发生过变化 if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { //land setContentView(R.layout.activity_land); initLand();//初始化横屏数据 } else if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { //port setContentView(R.layout.activity_port); initPort();//初始化竖屏数据 } } 3)在点击事件,强制执行横竖屏操作 @Override public void onClick(View v) { switch (v.getId()) { case R.id.iv_close: setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);//强制为竖屏 break; case R.id.iv_stretch: setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);//强制为横屏 break; } } 7、图片等比例缩放,需要maxWidth、maxHeight和adjustViewBounds设置为true同时进行才可以注意,maxWidth和maxHeight不能直接设为屏幕宽高,否则等比例缩放会失败,因为某一方达到最大值,则另一方也会执行同样操作photo = new ImageView(mContext);ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);photo.setLayoutParams(lp);photo.setMaxWidth((int) (screenWidth * 0.99));photo.setMaxHeight((int) (screenHeight * 0.99));photo.setScaleType(ImageView.ScaleType.FIT_CENTER);photo.setAdjustViewBounds(true);如果需要左右滑动、拉伸缩放,做图片预览,那么可以用下面的资源:http://download.csdn.net/detail/liuxian13183/9846522ImageView的绘画,先后执行顺序为: @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); } @Override public void onGlobalLayout() { } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); }前三个可能执行多次,最后一个仅执行一次。 8、H5调试方案:https://segmentfault.com/a/1190000009240637 更多技巧:http://blog.csdn.net/reboot123/article/details/46127797
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! Android是开源的,内部API相对比较透明,因此App的威胁会多一些,了解一些安全知识,有助于用户体验和产品稳定性。 1、App组件方面,慎用android:export=true(Activity、Service、BroadcastReveiver如果组件有超过一个intent-filter则默认为true;ContentProvider在16即4.1系统及以下默认为true,以上为false;),防止被程序恶意调用;对放出的组件,进行权限限制和数据校验,Intent设置包名。 2、WebView使用webView.addJavaScriptInterface的方式注入开放给H5的接口,使用注解的方式@JavaScriptInterface来标明接口。 4.2以下防攻击方案:http://blog.csdn.net/zhouyongyang621/article/details/47000041 3、防止反编译,造成接口被攻击、数据泄露,进行混淆加固处理;同时可以将apk的md5发给服务端进行校验,如果二次打包,则可以分辨出;用Cipher对数据库包括SharePreference加密,后者使用MODE_PRIVATE;核心功能写入so,通过jni调用。 4、防止DNS劫持,使用https加密传输;升级和下载时往往容易被劫持链接而下载别的App,同时防止被hook导致安装其他路径的安装包。升级和下载使用https,安装时要比较下载包的Md5与服务端返回是否一致、包名是否一致来保证下载包正常;将数据设置失效时间。 5、接口数据校验,注意频繁请求某一接口、虚拟注册、验证码接口被刷,防止服务端被拖垮。 6、打包上传后验证签名信息,下载应用市场apk,解压出META-INF下CERT.RSA文件,使用如下命令查看签名 keytool -printcert -file META-INF/CERT.RSA 7、数据传递 Collections.unmodifiableList(list); 上面的方法可使当前list无法再添加对象,保持数据传递的安全性。 8、某些敏感Api为防止hook,可以设置setAccessiable(false),使得无访问权限来保证业务层实现安全。 9、设置包名和权限,防止部分攻击(仅打开此包下,具备此权限的页面) Intent intent = new Intent("com.android.settings.action.SWITH_AGED_MODE"); intent.setPackage("com.android.settings"); sendBroadcast(intent,"com.android.settings.permission.SWITH_SETTING");并对四大组件中传递的intent进行合法性校验。 10、防御措施一般如下三种: 第一、寄托在破坏攻击者的工具。第二、寄托在Java层代码逆向、调试和阅读的难度。第三、寄托在c层代码逆向、调试和阅读的难度上。 11、对于动态注册的广播,尽量使用LocalBroadcastReceiver来进行本地注册,这样只在本应用内通信;如果使用registerReceiver()则要做好权限控制。 附硬件信息查询方式,通过拨号盘输入如下参数即可 12、onPause时,通过ProcessManager.getRunningAppProcess来检测自己是否位于栈顶,防止第三方应用悬浮 13、使用TrustMangerFactory导入证书,自签校验 14、getRecentTask,使自己的进程不可见 在AndroidManifest.xml配置android:exclueFromRecents=false或Intent设置此flag,仅5.0以下有效,以上底层已经封掉getRecentTask方法 15、android:allowBackup="false" 防止设为true后,通过 adb backup 和adb restore来备份和恢复数据。 16、敏感信息不要使用socket通信,使用可以检验身份的方式会更好。 通用功能 代码 功能描述 备注 *#*#7780#*#* 恢复手机出厂设置,清除应用数据以及设置,移除手机绑定的谷歌账号,卸载下载的应用 不会删除系统内置应用,不会删除SD Card上的文件 *2767*3855# 重新安装手机操作系统,恢复手机出厂状态,删除包括安装在内部存储上的所有APP并清除设置 *#*#197328640#*#* 进入调试模式 *#*#4636#*#* 电话基本信息,电话使用情况,电池信息 *#*#34971539#*#* 摄像头系统信息 注意不要点击不要点击升级摄像头系统信息,小心1秒变砖 *#*#7594#*#* 改变电源按键的功能,允许直接关机而不是询问用户选择操作(无声模式,飞行模式,关机) *#*#273283*255*663282*#*#* 备份所有的媒体文件,打开文件拷贝界面让你能够备份图片,视屏和音频等媒体文件 *#*#8255#*#* 启动GTalk服务监控 *2767*4387264636* 显示产品信息 *#0228# 显示电池状态 *#12580*369* 软件和硬件信息 *#32489# 查看加密信息 *#273283*255*3282*# 数据创建菜单 *#3282*727336*# 数据使用状态 *#8736364# OTA升级菜单 ##778 显示EPST菜单 *#228# ADC读取菜单 WIFI,GPS和蓝牙检测 代码 功能描述 备注 *#*#526#*#* WLAN检测 *#*#528#*#* WLAN测试 *#*#232339#*#* WLAN测试 *#*#232338#*#* 显示WIFI网卡的MAC地址 *#*#1472365#*#* 快速GPS测试 *#*#1575#*#* 不同类型的GPS测试 *#*#232331#*#* 蓝牙测试 *#*#232337#*#* 显示蓝牙设备地址 系统固件版本信息 代码 功能描述 备注 *#*#1111#*#* FTA软件版本 *#*#2222#*#* FTA硬件版本 *#*#4986*2650468#*#* 硬件信息 *#*#1234#*#* PDA和手机系统信息 *#2263# 基带选择 *#9090# 诊断配置 *#7284# USB模式控制 *#872564# USB日志控制 *#745# RIL日志输出菜单 *#746# 调试日志输出菜单 *#9900# 系统日志输出模式 *#*#44336#*#* 显示构建时间,更新列表 *#03# NAND闪存串号 *#3214789# GCF模式状态 *#4238378# GCF配置 工厂测试 代码 功能描述 备注 *#*#0283#*#* 网络数据包回路测试 *#*#0*#*#* LCD测试 *#*#0673#*#* 音频测试 *#*#0289#*#* 音频测试 *#*#0842#*#* 震动与背光测试 *#*#2663#*#* 触摸屏测试 *#*#2664#*#* 触摸屏测试 *#*#0588#*#* 近距离感应器测试 *#*#3264#*#* 内存硬件版本 *#0782# 实时钟测试 *#0589# 光感应器测试 *#7353# 快速测试菜单 *#3214789650# LBS测试 *#8999*8378# 测试菜单 *#07# 测试历史 PDA和电话 代码 功能描述 备注 *#*#7262626#*#* 场测试 *#06# 显示手机IMEI号码 *#*#8351#*#* 打开语音拨号记录日志 *#*#8350#*#* 关闭语音拨号记录日志 **05***# 从紧急拨号屏幕解锁PUK码 *#301279# 网络制式HSDPA HSUPA控制菜单 *#7465625# 查看手机锁定状态 *7465625*638*# 配置网络锁定MCC/MNC *7465625*782*# 配置网络锁定NSP *7465625*77*# 插入网络锁定键SP *7465625*27*# 插入网络锁定键NSP/CP *#272886# 自动接听选择 其他 代码 功能描述 备注 *#0*# Galaxy S3服务菜单 Samsung *#1234# 软件版本 Samsung *#12580*369# 硬件与软件信息 Samsung *#0228# 查看电池状态 Samsung *#0011# 打开服务菜单 Samsung *#0283# 网络回路测试 Samsung *#0808# 访问USB服务 Samsung *#9090# 打开服务模式 Samsung *#7284# FactoryKeystring菜单 Samsung *#34971539# 访问摄像头系统 Samsung *#7412365# 摄像头固件菜单 Samsung ##7764726 Motorola DROID 隐藏服务菜单 Motorola 默认密码6个0 1809#*990# LG Optimus 2x 隐藏服务菜单 LG 默认密码6个0 3845#*920# LG Optimus 3D 隐藏服务菜单 LG 默认密码6个0 3845#*850# LG G3 诊断测试菜单 LG AT&T 5689#*990# LG G3 诊断测试菜单 LG Sprint 3845#*851# LG G3 诊断测试菜单 LG T-Mobile ##228378 LG G3 诊断测试菜单 LG Verizon 3845#*855# LG G3 诊断测试菜单 LG 网络变种 *#*#3424#*#* HTC 测试功能 HTC ##8626337# 运行VOCODER HTC ##33284# 场测试 HTC ##3282# 显示EPST菜单 HTC ##3424# 运行诊断模式 HTC ##786# 反转诊断支持 HTC ##7738# 协议修订 HTC *#*#786#*#* 硬件重置 Nexus 5 *#*#7873778#*#* 启动Superuser应用 Nexus 5 *#*#1234#*#* 启动Superuser应用 Nexus 5 *#123# 是否连接到家庭网络,仅用于加拿大和美国 Nexus 5 *#*#2432546#*#* 检查系统升级 Nexus 5 日志输出办法: adb logcat -b main -v time>app.log 打印应用程序的log adb logcat -b radio -v time> radio.log 打印射频相关的log adb logcat -b events -v time 打印系统事件的日志,比如触屏事件 6、权限问题 try { PackageManager packageManager=mContext.getPackageManager(); Drawable drawable=packageManager.getResourcesForApplication("com.tencent.qq").getDrawable(0x7f020110); mOnlineTv.setBackground(drawable); }catch (Exception e){ showToast(e.getMessage()); } 如上代码可以获取QQ的一张图片来设置到自己的App来,原因在于QQ设置了export=true,破解其App得到资源即可实现。延伸讲一个activity的安全性,一般通过action、设置应用包名可以直接打开,那么可以做的就是给activity设置一个权限,同时将传过来context的package做判断,如果在后台注册过则同意打开,否则拒绝打开。 再举一例: Intent intent = new Intent(Intent.ACTION_MAIN); ComponentName cn = new ComponentName("com.tencent.qq", "com.tencent.qq.activity.LoginActivity"); intent.setComponent(cn); startActivity(intent); 7、安全一点的intent(设置selector为null,chrome默认设置component为null) // convert intent scheme URL to intent object Intent intent = Intent.parseUri(uri); // forbid launching activities without BROWSABLE category intent.addCategory("android.intent.category.BROWSABLE"); // forbid explicit call intent.setComponent(null); // forbid intent with selector intent intent.setSelector(null); // start the activity by the intent context.startActivityIfNeeded(intent, -1); 防止被注入选项和component进入其他app,被截取数据。劫持后如何防止用户数据被抓取 主要因为html中被插入js,同时弹出新的伪装页面 http://www.cnblogs.com/alisecurity/p/5408588.html 遇到下面这种,还是要自己多注意些 8、zip包里尽量不要出现../../file,对解压包的目录进行判断是否多级目录,防止数据被覆盖。 String entryName = entry.getName();if (entryName.contains("..")){throw new Exception("unsecurity zipfile!");} 9、伪基站传播分析 伪基站能够伪装成电信运营商的服务号,向手机用户群发钓鱼短信,收到的钓鱼短信跟正常短信显示在一起,真假难辨。钓鱼网站的仿真度很高,并抓住了人们贪小便宜的弱点,先收集用户信息,再引导安装仿冒应用。 美国联邦调查局认为:密钥长度需要设置56位,几乎不可破解,而30位以下可以通过暴力破解-入侵的艺术。
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 安卓应用的内存往往是有限的,从开始的8M到16M,24M,32M,48M,64M等逐步变大,但内存的变大是由于分辨率的提高导致,并不意味着内存的可使用量随之增加,而不及时回收(即使Java有自己的垃圾回收机制),容易造成内存过高,引起手机变卡,体验流畅性下降)。 降低应用内存消耗的办法有以下几种常见办法: 1、图片声明使用的context使用Application,回收时清除ImageView的drawable 但是如果使用context.startActivity会报一个错 android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want? 因此如果确定用application启动activity,要注意加个FLAG_ACTIVITY_NEW_TASK这个flag 同时ProgressDialog的生成也不能用application,因为它依赖着当前页面 2、使用viewStub占位,避免经常使用gone方法,减少对象的加载和初始化 3、使用merge把能合并的布局统统合并,在hierachyviewer里面可以看到布局的复杂度 如果使用include,给布局加margin的话,需要同时设置include标签的宽高 4、去掉decorView和window的背景,往往由于应用有自身的色调搭配 5、通过canvas的clip方法,避免在看不到的地方画图,通过quickReject方法来在确定的区域比如矩形内绘制, 跳过非既定区域内绘制 6、使用9path文件和自定义图片,以及透明背景,来防止过度绘制 7、列表可以给定一个高度(根据item的高度来动态设置),来防止重复计算高度和执行布局方法 8、合理选择组件,选择简单的而非复杂的组件(原因,如果你自定义过复杂组件自己就会明白) 9、开启新进程作为服务进程和工具进程-最大招,有效降低当前应用的内存消耗 10、避免在onDraw频繁调用。 11、 在动画结束或onPause方法里,清除控制的动画效果:mImageView.clearAnimation() 避免内存泄露的几个办法: 1、及时清除对象或回调引用的context(或使用Application),降低引用链长度 /** * 清除页面的ImageView的引用链 * @param view */ public static void unbindDrawables(View view) { if (view.getBackground() != null) { view.getBackground().setCallback(null); } if (view instanceof ViewGroup) { for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) { unbindDrawables(((ViewGroup) view).getChildAt(i)); } ((ViewGroup) view).removeAllViews(); } } 2、Bitmap用前根据屏幕dpi或自定义要求进行压缩,过后及时回收;使用inBitmap复制前面图片的内存,使整个屏幕 只展示当前页面图片所占的内存。 使用inBitmap需要注意几个限制条件: 在SDK 11 -> 18之间,重用的bitmap大小必须是一致的,例如给inBitmap赋值的图片大小为100-100,那么新申请的bitmap必须也为100-100才能够被重用。从SDK 19开始,新申请的bitmap大小必须小于或者等于已经赋值过的bitmap大小。 新申请的bitmap与旧的bitmap必须有相同的解码格式,例如大家都是8888的,如果前面的bitmap是8888,那么就不能支持4444与565格式的bitmap了。 我们可以创建一个包含多种典型可重用bitmap的对象池,这样后续的bitmap创建都能够找到合适的“模板”去进行重用。如下图所示: 另外提一点:在2.x的系统上,尽管bitmap是分配在native层,但是还是无法避免被计算到OOM的引用计数器里面。这里提示一下,不少应用会通过反射BitmapFactory.Options里面的inNativeAlloc来达到扩大使用内存的目的,但是如果大家都这么做,对系统整体会造成一定的负面影响,建议谨慎采纳。 3、Cursor对象及时关闭,避免对数据库对象的长期引用 4、关键地方做空判断,页面关闭时及时回收对象 5、context尽量使用application,避免页面关闭时,由于引用存在而不能及时回收对象,尤其getResource功能, 在fragment中特别容易引起泄露,出现not attach的问题 6、避免在for循环中声明对象(一下子无数个对象产生,内存暴增),引用能写在外面最好,如array.length,直接用 int size获取值,再遍历 7、打开开发者模式中的CPU绘制选项,根据屏幕显示的红黄蓝来辨别页面的绘制情况 8、handler(等内部类)往往引用context,使用弱引用的方式处理 public WeakHandler handler = new WeakHandler(this); public class WeakHandler extends Handler { WeakReference<Context> mContextWeakReference; public WeakHandler(Context context) { mContextWeakReference = new WeakReference<Context>(context); } @Override public void handleMessage(Message msg) { if (mContextWeakReference.get() == null || msg == null) { return; } boolean handled = !handleMessageDelegate(msg.what, msg.obj); if (handled) { if (msg.what < 0) { handleErrorMessage(msg); } else { handlePtrMessage(msg); } } } } 9、一般webView也会有内存泄露的问题出现,往往由于引用未删除,自身的view仍然存在,在进程一系列操作后,仍可以使用开启新进程来降低应用内存(通过Aidl进行通信。) /** * 优化内存最后一招-开启新进程 */ @Override protected void onDestroy() { if (mWebView != null) {// remove webView, prevent chromium to crash ViewParent parent = mWebView.getParent(); if (parent != null) { ((ViewGroup) parent).removeView(mWebView); } // 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错 mWebView.getSettings().setJavaScriptEnabled(false); // 解决Receiver not registered: // android.widget.ZoomButtonsController mWebView.setVisibility(View.GONE); mWebView.removeAllViews(); mWebView.clearCache(false); mWebView.stopLoading(); mWebView.destroy(); mWebView = null; setConfigCallback(null); } super.onDestroy(); } /** * 删除引用 * @param windowManager */ public void setConfigCallback(WindowManager windowManager) { try { Field field = WebView.class.getDeclaredField("mWebViewCore"); field = field.getType().getDeclaredField("mBrowserFrame"); field = field.getType().getDeclaredField("sConfigCallback"); field.setAccessible(true); Object configCallback = field.get(null); if (null == configCallback) { return; } field = field.getType().getDeclaredField("mWindowManager"); field.setAccessible(true); field.set(configCallback, windowManager); } catch(Exception e) { } } 10、静态匿名内部类相比内部类来说,后者会直接引用父类,而前者不会,因此前者不会引起内存泄露后者则会。 11、注册的组件要在生命周期结束时要注销掉。 检查内存泄露的工具有:Lint(inspect code-performance)、Mat(case gc-分析hprof文件)、LeakMemory(Log日志弹窗)、As自带(Monitor-Dump Java Heap),更多介绍 图片更多:Android ImageView设置图片原理(下) 电量: 1、一般情况下耗电可能是死循环引起的,不断的请求,不断的失败,又不断的重试,总之业务引起的占多数。 2、其次系统加入低电耗的模式,如果你的应用不断唤醒cpu,执行一些耗时操作,那自然电量消耗过多 3、接口和产品设计不合理,一般情况下能一个接口返回的数据不要两个,请求数据包的封装也要占一大部分流量,另外产品的YY需求,而非用户真实需求,往往会造成实现的复杂度。 4、同步数据尽量在用户充电时或电量较多时 流量: 1、同步数据尽量在wifi下 2、接口能合一则合,产品设计简单极致 3、做好本地数据缓存,以及和服务端的同步机制;例如get请求和cache-control 4、webview做好本地缓存,加载时优先加载本地资源,当然也需要服务端支持 修改http服务器中的配置,使其支持text/cache-manifest,我使用的是apache服务器,是windows版本的,在apache的conf文件夹中找到mime.types文件,打开后在文件的最后加上“text/cache-manifest mf manifest”,重启服务器 5、压缩H5的图片大小,以及改变图片格式为webp,或者把背景图改为jpg,将纯色图用xml来写。 关于流量再讲下IOS吧,一般应用安装后都会开启后台刷新功能,导致流量耗用的特别快,开着移动流量三四天一百兆一点也不夸张(应用安装上百个),在设置-通用-后台应用刷新,关掉后台刷新,只把微信打开就够了。 卡顿: 1、ANR的出现在于在UI线程操作过久(Activity5s,BroadReceiver10s),则耗时操作尽量放在子线程处理,完毕后通知主线程 2、页面重绘多次,划定绘画区域如canvas.clipRect;控制的宽高不定多次计算,可以设置定值 3、页面复杂度过高,产品或自己的原因,内存不注意回收 4、点击后的背景尽量用透明,减少图片绘制的复杂度 5、避免onDraw频繁调用 使用leakCanary来监控泄露,并上传 https://www.liaohuqiu.net/cn/posts/leak-canary-read-me/ https://github.com/square/leakcanary 使用ProGuard做apk压缩 https://developer.android.com/studio/build/shrink-code.html 其他工具:MAT、Emmagee 页面渲染时,被绘制的元素最终要转换成矩阵像素点(即多维数组形式,类似安卓中的Bitmap),才能被显示器显示。 硬件加速的主要原理,就是通过底层软件代码,将CPU不擅长的图形计算转换成GPU专用指令,由GPU完成。
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 在基本的工作内容开发中,算法不会显得那么重要,而在百万级别的时候,差距非常大,今天带大家研究下常见的字符串反转算法。 public class StringReverse { public static String reverse1(String orig) { char[] s = orig.toCharArray(); int n = s.length - 1; int halfLength = n / 2; for (int i = 0; i <= halfLength; i++) {// 2分法+替换算法 char temp = s[i]; s[i] = s[n - i]; s[n - i] = temp; } return new String(s); } public static String reverse2(String s) {// 2分法+异或算法 char[] str = s.toCharArray(); int begin = 0; int end = s.length() - 1; while (begin < end) { str[begin] = (char) (str[begin] ^ str[end]); str[end] = (char) (str[begin] ^ str[end]); str[begin] = (char) (str[end] ^ str[begin]); begin++; end--; } return new String(str); } public static String reverse3(String s) {// jdk提供 return new StringBuffer(s).reverse().toString(); } public static String reverse4(String s) {// 2分递归算法 int length = s.length(); if (length <= 1) return s; String left = s.substring(0, length / 2); String right = s.substring(length / 2, length); return reverse1(right) + reverse1(left); } public static String reverse5(String s) {// 傳統从后往前加 int length = s.length(); String reverse = ""; for (int i = 0; i < length; i++) reverse = s.charAt(i) + reverse; return reverse; } public static String reverse6(String s) {//傳統與5雷同 char[] array = s.toCharArray(); String reverse = ""; for (int i = array.length - 1; i >= 0; i--) reverse += array[i]; return reverse; } public static String reverse7(String s) {// 利用栈先进后出的特性 char[] str = s.toCharArray(); Stack<Character> stack = new Stack<Character>(); for (int i = 0; i < str.length; i++) stack.push(str[i]); String reversed = ""; for (int i = 0; i < str.length; i++) reversed += stack.pop(); return reversed; } public static void main(String[] args) { String str = "hello world !"; int length = 10000000; long curTime = System.currentTimeMillis(); for (int i = 0; i < length; i++) { reverse1(str); } System.out.println("reverse1:" + (System.currentTimeMillis() - curTime)); curTime = System.currentTimeMillis(); for (int i = 0; i < length; i++) { reverse2(str); } System.out.println("reverse2:" + (System.currentTimeMillis() - curTime)); curTime = System.currentTimeMillis(); for (int i = 0; i < length; i++) { reverse3(str); } System.out.println("reverse3:" + (System.currentTimeMillis() - curTime)); curTime = System.currentTimeMillis(); for (int i = 0; i < length; i++) { reverse4(str); } System.out.println("reverse4:" + (System.currentTimeMillis() - curTime)); curTime = System.currentTimeMillis(); for (int i = 0; i < length; i++) { reverse5(str); } System.out.println("reverse5:" + (System.currentTimeMillis() - curTime)); curTime = System.currentTimeMillis(); for (int i = 0; i < length; i++) { reverse6(str); } System.out.println("reverse6:" + (System.currentTimeMillis() - curTime)); curTime = System.currentTimeMillis(); for (int i = 0; i < length; i++) { reverse7(str); } System.out.println("reverse7:" + (System.currentTimeMillis() - curTime)); } } 其他有趣的算法: 如赛马问题,25匹马,5个赛道,如何在不用工具的情况下,找到最快的5匹。问题可以简化为3匹马,3个赛道。 分A B C三组马,各赛一场,结果如下: A1 A2 A3 B1 B2 B3 C1 C2 C3 3场第1名赛一场,得出第1名 如果是A1,则A2跟B1和C1比1场,其他同理,找出第2名 如果是B1,则A2跟B2和C1比1场,其他同理,找出第3名 比赛结束,共需要6场 同理可算出,25匹马需要10场。 如运煤问题,3000吨煤,用载重1000吨的火车,从原点运往终点,路程1000公里,每公里消耗1吨煤,到终点时煤剩几何? 粗略一算,第一种方案: 第一轮, 第1次,先拉1000吨到A,卸下500吨,返回,行程250公里 第2次,再拉1000吨到A,卸下500吨,返回,行程250公里,同理第三次 第3次后,A地剩下1500吨煤,行程250公里 第二轮 继续第4次,同上,到B地行程187.5公里,每次卸下375吨货物 两轮后,行程437.5公里,剩下货物750吨 第三轮 一次性运到,共剩余312.5吨 分析:由于第三轮每次只运750吨,运量未满,导致最终运送较少。 粗略一算,第二种方案: 第1次,先拉1000吨到A,卸下500吨,行程250公里,返回 第2次,再拉1000吨到A,加上250吨满载,剩下250吨;走250公里到B,卸下250吨,总行程250公里,返回 第3次,再拉1000吨到A,加上250吨满载;再走250公里到B,加上前两次剩下的共500吨,满载1000吨继续 直行到终点,共剩余500吨 分析:似乎比较完美,每次都是满载。 粗略一算,第三种方案: 变下思路,将此题变成n千吨煤,可以走多少公里,即消耗多少 那么1000吨走1000公里,2000吨运两次,向前3次向后2次,向前只能多走1/3公里,即1000*(1+1/3)公里, 同理可得3000吨走1000*(1+1/3+1/5)公里,n千吨可走1000*(1+1/3+1/5+……+1/(2n-1))公里 那么3000公里剩余多少呢?前两句已经讲了3000吨可走的公里,每公里1吨,多出多少公里即多出多少吨 即1000*(1+1/3+1/5)-1000=1600/3吨,即533.33吨。功夫不负有心人,这就是最终答案!
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处!熟悉网络请求路径,网址经过浏览器的URL验证,是否正确证书是否失效,经过host文件处理,以及Dns解析,如有缓存则直接返回,否则交给Dns服务器,最后解析出ip地址,否则一般返回微软搜索页,如果浏览器缓存直接返回数据,否则通过隧道,通过网关,将数据解析为服务器可识别的协议,经历GFW过滤,到达服务器某些烦人的小广告通过入侵host载入另外一些通过劫持dns载入,建议改为全网dnsphp的echo带有网络请求时间,才返回客户端,并不是简单的打印优化往往考虑三端,客户端,传输过程,服务端客户端,优化代码逻辑传输过程,防止dns劫持,做memcache或radis接下来我们主要说服务端的一些优化流程。服务端又主要是数据库的问题数据库可以从以下几个方面来说: 第一,合并操作。比如查询和搜索。 第二,做热点搜索,把热点的一些数据单独拎到一张表当中来做搜索。 第三,并非所有的数据都是要让用户看到。比如嗯,分页查询,最多给到用户100页就够了,而没有必要十分准确。重要的数据如用户名,性别首先拿出来,必要数据放到第二张表中拿出,即做主要次要数据分表查询。 第四,砍需求,做减法,并非所有的功能都需要加上 第五,避免querycache,缓存过多也容易出问题
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处!即时通信:前端获得消息发送到服务端,服务端处理后通过推送的方式,发给接收方;Android使用长连机制,联通网络长连十几分钟,电信仅五六分钟,因此需要根据测试的芯片类型,为了保活,可能要三四分钟就要去连一次,叫心跳机制;IOS通过APN机制推送。即时通讯是在一种平等、开放情况下的互动,这点很重要。推送:采用增量推送的方式,设置一个sequence,服务端一个客户端一个,每次同步时客户端将cur_seq发给服务端,获得增量数据同步到本地。每个seq都是long型占8byte,考虑到微信用户6亿,Qps达到千万级别,则每秒要处理100兆的IO,相对来说比较大,如何降低呢,微信有一个AllocSvr和StoreSvr两个服务,分别来处理分配和存储,设计一个max_Seq和步长,将一定数量的用户比如连续ID一万个,设计在同一个Section,加上一个max_Seq,步长设为10000,此时可以10^3个等级的数据量,相对AllocSvr处理就简单一些,所以任何一个简单的事情在海量数据下,都会变成一个复杂的问题。另外添加步长,就涉及Old AllocSvr和New AllocSvr,需要根据已知配置文件,有哪些服务器可以切换,考虑到容灾还要做备份服务器,因此做互为备份是服务器能力不浪费的优秀设计;路由的切换也是根据seq的方式,使用路由表来切换的。红包:活动前将资源通过消息的方式同步到客户端,防止活动当天同步资源造成浪费。每十万级的红包放一个包裹,加入票据,争取更多的资金能够进来,将分配规则写入客户端,然后将红包写入用户,异步对账后同步到账户里;要避免1、不存在的红包分配给用户2、红包分配金额有误 3、红包发给不存在的人 4、红包重复发给一个人 5、红包重复发给其他人6、防止黑客攻击,每个包裹写一个公私钥,同时可以手工屏蔽某些密钥对,保证其中一对密钥被盗,其他仍然可以正常使用。以及采用qos将请求主动失败,分两种系统失败可以重试,逻辑失败则失败 ;由于对大广告主如5000万以上的做过系统配合,但也要在参与用户少的时候,保证用户抢红包的流畅性,进行降级处理。Android:刚开始业务为主,随着业务量增大,逐渐改为功能为主,然后规划多个dex,甚至将相应服务新开进程执行;秉承用户体验的观点,复杂的逻辑一般放在服务端做,客户端仅做展示功能;安全方面,每次登录给予一个票据,用于服务端检验发送的信息是否合法;将主业务与工具和后台业务分开,分多个进程处理,可以明显降低内存和电量的消耗。斑马广告:采用对一群人画像如1万人,而不对1个人进行画像分析,每个人加入标签,如年龄、旅游、科技等,如果需要5000万定向客户,而实际标签不足时,需要通过lookalike系统查找潜在的和之前尚未分析出的客户,准确率达到16%左右;标签标的有:朋友圈发的消息、广告的点击和互动、公众号的类型、店家wifi的登入等,对聊天记录腾讯有天然的敏感性,不进行分析。朋友圈:自己的timeline即自己发的信息,好友的timeline即公用区域发的信息,这个都以用户为单位,将timeline分两类,自己和好友,自己的直接显示,好友的插入自己的消息,这是实现的第一步;第二位是交互,好友发一条消息,第一:哪些人可见哪些人不可见,好友这边呢,操作是直接插入到可见好友的timleline,不可见好友收到的是相反的消息即不插入,第二:哪些是共同的好友,评论和点赞彼此收的到,实际出现三个对象,你、我、其他人,三者均需要一个消息推送(实际情况更复杂,多个直接跟你互相的人即是你,你的好友中非彼此好友的人即是其他人),最终采用增量推送的方式。后端:消息分五类,红包,文本和语音,图片和视频、群消息、朋友圈,优先保证第一项服务可用,然后保证第二项服务可用,最后再保证朋友圈可用,这是消息优先级,在信息量巨大时可以人工触发开关处理;考虑到国内外通讯,香港地区延时100-200ms,欧美约300-500ms,国外的消息即就近处理,并且放在就近的服务器上,上海保证北方区,深圳保证南方区,香港保证东亚区,加拿大保证欧美区,这样一方面保证应用的国外战略得以实施,另一方面消息分流减轻服务器的压力;值得讲的一点是不同区之间的消息通讯,比如北方区的A和东南亚区的B,A发送消息,存储在上海服务器,然后推送到香港服务器,由香港区推送给B,减少https公网的等待时间,另外一点如果此人经常全球跑,则数据会漫游到国内数据中心处理,否则经常切换数据中心和服务备份,会浪费大量资源,增加系统冗余。 接下来聊几个服务端的点 数据接入-数据处理(逻辑处理-数据读写)-数据持久化(数据存储) Qos:quality of service 服务质量,网络请求会分发到很多数据中心进行处理,而每个路由都有一个buffer,超过后则丢弃,否则数据积压导致各方数据均不能有效处理,引起各种服务瘫痪;传输顺序出错和其他出错,需要有相关协议保证重试。 Cgi:common gateway interface 大量用户发送的请求,后台会有无数个cgi程序都保证正确处理,比如消息推送和消息同步 容灾:设计3的倍数个数据中心,保证每个数据中心有2/3的数据处理峰值,这样在其中1/3个中心瘫痪时,其他2/3的中心可以接收它的处理能力。 埋点:与测试团队沟通容易出错的地方,做key-value增加策略,key为关键点的id,value为关键点数据+1的值,在后台展示和处理,达到预警则及时处理,达到早发现、早处理的目的,这也是容灾的一部分;另一部分是与产品团队,共同开发出业务的使用频率,为后面的产品设计和架构设计提供良好的数据支撑。 RPC:Remote Procedure Protocal 同步处理的消息往往是有限的,异步可以大限度的压榨服务器的处理能力,如微信开发的SvrKit,用户发出请求后,交付中间件异步处理,并提供出错重试协议,保证消息被正确处理。 数据IO:读写在大量数据交互的应用上显示尤为重要,提供memcache防止频繁访问数据库,提供多Master-Slave提供数据读写服务,如海外A的消息存储在加拿大,国内B的消息存储在上海,这就是两个Master,两者通信通过RPC推送到对方数据中心即可,Slave用在Master出问题时的备用存储方案,事后要两者要互相同步。 图片:用户发送后放到CDN处理,返回大图和缩略图链接,加到消息对象中,返回客户端。同步:采用seq增量下发消息的方式,对邮件、漂流瓶等进行key-value的判断拉取数据。安全:每次登录都带有票据,票据用密钥对+ID来处理,可以随时定向失效密钥。 本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处!
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! Android系统不断的升级,从基础到中级再到高级,逐步升级是软件工程敏捷开发的一个重点,在每个版本中升级不同功能,以满足越来越丰富的用户需求,作为一名开发者,熟悉各个版本之间的异同,有助于做产品适配、安全等各方面业务。 2.3,加入陀螺仪等一些传感器支持和前后相机支持(Android变得越来越流行,尤其是经典的三星I9100) 3.0,加入Fragment支持(主要为支撑平板而研发) 4.0, 1、多核处理器优化,据说比3.0快1.8倍(多线程能不快嘛) 2、支持HttpResponseCache(本地缓存,有效降低服务器压力) 3、加入activityLifeCycle,有效监听activity生命周期 4.2,引入多用户支持(微信、微博、QQ、支付宝都可以当成手机账号) 默认content provider是私有的 4.3,提供SELinux,防止应用把自身文件改成公共的,以免产生漏洞 4.4, 1、提供访问多个外存设备的功能,虚拟机改为ART,好处在于:答案; 2、AlarmManager在低电耗时可以通过setWindow和众多定时事务一起发生,也可以通过setExact来在特定时间发生, 而之前的setRepeating方法将不再准确;提供完善的打印框架,短信只有默认程序才可以读写信息 3、 之前版本的webView可以随意被人用js调用本地方法,这个版本要求必须对调用方法加入@JavaScriptInterface才 有效,那么可以选择性的将方法给H5端。 地址:https://developer.android.google.cn/about/versions/android-4.4.html 5.0,支持arm、x86和mips,全面兼容64位,声音和振动通过Notification来添加 ,ART全面替换Dalvik 地址:https://developer.android.google.cn/about/versions/lollipop.html 使用JobSchedule来执行后台轮循操作,减少操作不当引起的内存和电量消耗 5.1.1, 1、支持多dex的multidex出现 2、FileProvider出现,敏感权限需要申请 https://developer.android.google.cn/reference/android/support/v4/content/FileProvider.html 用来临时赋予权限,只要获得权限的组件(activity或Service等)不回收,则权限一直存在。可以通过 Intent.setFlags来设置权限 6.0,主要改动,需检查自己需要的权限是否被赋予,低电耗禁止JobSchedule、网络请求和同步、AlarmManager(设置setAndAllWhileIdle或setExactAndAllowWhileIdle(),来让闹钟来临前几分钟唤醒屏幕),去掉对Apache的支持,如需要则在gradle添加下面代码 android { useLibrary 'org.apache.http.legacy' } 地址:https://developer.android.google.cn/about/versions/marshmallow/android-6.0-changes.html 7.0:主要改动面向6.0以下的应用,后台服务会被直接干掉,并限制锁屏后对Cpu和网络的调用。 地址:https://developer.android.google.cn/about/versions/nougat/android-7.0-changes.html 8.0: 1、加入Fragment的LifeCycleCallback,有效监听其生命周期 2、无法在manifest里注册隐式广播 3、修复权限组一个权限被授予,其他也被授予的问题(比如storage的read和write) 地址:https://developer.android.google.cn/preview/behavior-changes.html 总结:虽然新的系统会让手机性能更佳,但默认也会给应用添加许多限制,比如4.4添加ART后对定时器的限制,6.0对文件写入sd卡的限制(即使用户把所有权限都给到你,那稍后默认又会收回你的部分权限)。新的系统对用户来说是更好的体验,对开发者来说是更多的工具,但同时也是新的挑战,所以Gradle中要慎重添加下面这句代码targetSdkVersion 24。 打包方面:正式包一定要加下面这句。 release { // 如果要支持最新版的系统 Android 7.0 // 这一行必须加,否则安装时会提示没有签名 // 作用是只使用旧版签名,禁用V2版签名模式 v2SigningEnabled false} 如果要使用新的签名,可以参考http://blog.csdn.net/reboot123/article/details/51261558 网上找了个系统对文件读取和存储的权限处理方法,可见系统对权限管理的敏感性。 对SharePreference的存储权限做过限制。java.lang.SecurityException: MODE_WORLD_READABLE no longer supported 读取屏蔽号码,阻止来电和不必要的短信骚扰 提供新的签名方式 APK Signature Scheme v2,加速apk安装过程 在主配置文件设置的流量、图片和视频监控广播无效,但代码实现依然有效。 读写私有文件不再允许设置MODE_WORLD_READ/WRITEABLE权限 使用FileProvider 使用FileProvider的大致步骤如下: 第一步:在manifest清单文件中注册provider <provider android:name="android.support.v4.content.FileProvider" android:authorities="com.jph.takephoto.fileprovider" android:grantUriPermissions="true" android:exported="false"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> 心得:exported:要求必须为false,为true则会报安全异常。grantUriPermissions:true,表示授予 URI 临时访问权限。 第二步:指定共享的目录 为了指定共享的目录我们需要在资源(res)目录下创建一个xml目录,然后创建一个名为“file_paths”(名字可以随便起,只要和在manifest注册的provider所引用的resource保持一致即可)的资源文件,内容如下: <?xml version="1.0" encoding="utf-8"?> <resources> <paths> <external-path path="" name="camera_photos" /> </paths> </resources> <files-path/>代表的根目录: Context.getFilesDir() <external-path/>代表的根目录: Environment.getExternalStorageDirectory() <cache-path/>代表的根目录: getCacheDir() 心得:上述代码中path="",是有特殊意义的,它代码根目录,也就是说你可以向其它的应用共享根目录及其子目录下任何一个文件了,如果你将path设为path="pictures", 那么它代表着根目录下的pictures目录(eg:/storage/emulated/0/pictures),如果你向其它应用分享pictures目录范围之外的文件是不行的。 如果设为".",则整个文件夹都被共享,但fileProvider的目录是只共享自己目录,而且只让自己应用知悉;而非让所有应用都得知此文件,进而产发安全隐患。 第三步:使用FileProvider 上述准备工作做完之后,现在我们就可以使用FileProvider了。 还是以调用系统相机拍照为例,我们需要将上述拍照代码修改为如下: File file=new File(Environment.getExternalStorageDirectory(), "/temp/"+System.currentTimeMillis() + ".jpg"); if (!file.getParentFile().exists())file.getParentFile().mkdirs(); Uri imageUri = FileProvider.getUriForFile(context, "com.jph.takephoto.fileprovider", file);//通过FileProvider创建一个content类型的Uri Intent intent = new Intent(); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //添加这一句表示对目标应用临时授权该Uri所代表的文件 intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);//设置Action为拍照 intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);//将拍取的照片保存到指定URI startActivityForResult(intent,1006); N:敏感权限全部要App申请用户同意,大体分两类运行前申请和运行中申请两种;编辑框的文字可以定义除开复制、粘贴、全选这些功能;通知栏可以让用记回信息,点赞-支持快速回复;添加系统级电话黑名单;支持VR https://developer.android.com/reference/android/support/v4/content/FileProvider.html 谷歌官方博客:https://android-developers.googleblog.com/全英文 NDK开发:https://developer.android.com/ndk/guides/stable_apis.html
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 公司进行技术部拆分,以项目制作为新的开发模式,前端+移动端+后端,于是加速Api开发变得很有必要,准备使用mock加速进程,使各端可以并行开发。 优势: 1、前端确定要修改接口,跟后端和产品经理沟通后可以直接改mock,不用再等服务端 2、测试人员测试接口非常方便,不用再用fidder抓包修改,直接修改mock返回数据即可 Mock介绍:http://wiremock.org/ 第一步配置Java环境 地址:http://blog.csdn.net/reboot123/article/details/6631229 第二步:下载standalone.jar 地址:https://github.com/tomakehurst/wiremock 第三步:写Bat工具(py等其他亦可),本文使用1.46版本,9991为端口号 @echo on java -jarwiremock-1.46-standalone.jar --port 9991 @echo off 运行生成mappings和__files文件夹,mappings目录下写映射文件first-mapping.json { "request": { "method": "GET", "url": "/api/login" }, "response": { "status": 200, "bodyFileName":"login.json", "headers": { "Content-Type":"application/json", "Cache-Control":"max-age=86400" } } } _files目录下写请求结果login.json { "working": "YES" } 第四步浏览器访问:http://localhost:9991/api/login获得请求结果 资源:下载
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 1、App如果被定义一个有参数构造函数,那么需要再定义一个无参数的,如果不则会在某些情况下初始化失败 2、include Java Exception stack in crash report 原因:webview未被destroy和置空,快进快出容易导致Crash 3、Json解析一直有反斜杠,如 "Detail": "[\"1476438611831,120.011798,30.294126,6.5461546E-4,2.9163063E-4,0.0034914017,-0.05355559,0.080890246,9.80617,-9.765625E-4,2.1362305E-4,1.8310547E-\"]"而实际上需要这种 "Detail": [ [ "1476440497243", "120.011839", "30.294298", "0.0013065673", "0.010251816", "-0.07671833", "-0.04659828", "0.06689003", "9.806312", "-5.79834E-4", "6.866455E-4", "-0.0027008057" ] ] 原因:前者使用sdk本身提供的Json资源,后者使用gson提供的Json资源,如果要上传jsonArray数据只能用Gson。默认Json使用put方法设置数据,如果值有引号则自动加上反斜杠做转译 第1张是Json直接把value以Object的方式放入LinkedHashMap,第2张是Gson把Value分类(主要对Charater即String的接口)放入LinkedTreeMap 第二个方案: 存入数据的时候,以json保存到本地(由于引号的存在,反斜杠是必要的),上传的时候读取本地文件存入jsonArray,导致对原json的反斜杠进行转译,解决方案是:在上传前使用String.replace方法把反斜杠都去掉。 4、ScrollView通过addView加入Lv,结果焦点到达页面底部,这是由于Lv获得焦点引起的,所以把Lv的焦点去掉即可 lv.setFocusable(false); 5、网络请求返回数据一直是回车+空格的组合数据,报错:java.lang.StringIndexOutOfBoundsException: length=0; regionStart=0; regionLength=1 原因:请求的接口没有收到数据,是请求框架自身返回的数据,所以格式不正确;很可能是内网接口用外网连接导致 6、 Error:Execution failed for task ':mergeAnzhiDebugResources'. > Error: java.lang.RuntimeException: Crunching Cruncher img_bubble_default.9.png failed, see logs 主要原因是.9图片的长边是放内容的,小点代表拉伸的,原来的位置刚好相反,改过来就好。 7、华为手机读取文件后截取,再获得图片失败。 分析:这个错误很奇怪,以前开发中经常不会遇到,而目前相对来说,各大厂商对权限的要求越来越严格,更甚者在安装时即主动屏蔽若干权限,同时部分权限用时需要申请;说到这里你可能就明白了,这里主要是权限被剥夺的问题,要断点到出错的位置,提示赋予应用读写权限。 8、4.0以上版本,RadioGroup两个子RadioButton均处于选中状态 原因在于:设计某一个radio为true时,另外一个没有id,因此系统原因导致两个均被选中。 需要做的就是给radio设置id。 9、list不能addAll空数据,否则报空指针异常。系统层次的错误。 10、获得system权限,实现静默安装。设置 android:sharedUserId="android.uid.system" ,在当前模块的MakeFile中添加LOCAL_CERTIFICATE := platform,然后在安卓源码环境下使用原生make命令编译 11、App界面的Editext被弹出的输入法窗口挡住,使用android:windowSoftInputMode="adjustResize"(让键盘浮在界面上)可以处理,也有人用adjustPan(键盘将界面顶上去),但后者在webView中有编辑框非全屏情况失效,沉浸式时两者设置皆无效,可以通过下面方法来解决 AndroidBug5497Workaround.assistActivity(this) 原理前两者让屏幕主动适应键盘,后者将界面的可视部分强制减少一个键盘的高度 private int computeUsableHeight() { Rect rect = new Rect(); mChildOfContent.getWindowVisibleDisplayFrame(rect); // rect.top其实是状态栏的高度,如果是全屏主题,直接 return rect.bottom就可以了 return (rect.bottom - rect.top); } 详见:https://www.diycode.cc/topics/383 Lint自定义工具,用来查找需要继续的基类,而未继承的 http://alexq.farbox.com/post/andrlintwatchdog-custom-lint-zi-ding-yi-lint-ti-gao-dai-ma-zhi-liang 12、5.1以后,短信接收权限会被剥夺而出现安全错误,如果添加需要谨慎处理。 android.provider.Telephony.SMS_RECEIVED 13、apk安装失败 INSTALL_FAILED_USER_RESTRICTED :厂商系统原因 if (isUserRestricted(UserHandle.getUserId(uid), UserManager.DISALLOW_INSTALL_APPS)) { try { observer.packageInstalled(, PackageManager.INSTALL_FAILED_USER_RESTRICTED); } catch (RemoteException re) { } return; } INSTALL_FAILED_CANCELLED_BY_USER:有的机型默认会认用户选择是否安装,不点击则取消 14、获得简单的Root权限 android:sharedUserId="android.uid.system"将当前应用进程设置为系统级进程(不推介随意这么做,会产生很多隐患)。拥有此属性后,我们的应用就可以无视用户,无法无天地处理很多事情,比如擅自修改手机system分区的内容、静默安装等。之前开发过一个类似切换多套开关机动画和音效的模块,添加此属性后,就可以明目张胆地将我们的数据节点存在system分区,可以让用户恢复出厂设置都清空不了我们的数据。但是添加此属性后,我们需要在当前模块的MakeFile中添加LOCAL_CERTIFICATE := platform,然后在安卓源码环境下使用原生make命令编译才能生效(原生编译虽然比使用ide工具麻烦很多,但是却能使用很多ide工具无权限使用的api)。如果非要在ide工具中使用则必须通过系统密钥重签名生成的apk才行(未亲自验证)。 系统中所有使用android.uid.system作为共享UID的APK,都会首先在manifest节点中增加android:sharedUserId="android.uid.system",然后在Android.mk中增加LOCAL_CERTIFICATE := platform。可以参见Settings等 系统中所有使用android.uid.shared作为共享UID的APK,都会在manifest节点中增加android:sharedUserId="android.uid.shared",然后在Android.mk中增加LOCAL_CERTIFICATE := shared。可以参见Launcher等 系统中所有使用android.media作为共享UID的APK,都会在manifest节点中增加android:sharedUserId="android.media",然后在Android.mk中增加LOCAL_CERTIFICATE := media。可以参见Gallery等。 15、AbsListView的notifysetChanged与notifysetInvalidate()方法之间的区别,大家都知道listview刷新主要是因为list发生变化,而前者用于刷新局部的改变,后者用于list失效后刷入新的list(想像一下整个listf都失效,还能部分部分的刷新吗?) /** * This method is called when the entire data set has changed, * most likely through a call to {@link Cursor#requery()} on a {@link Cursor}. */ public void onChanged() { // Do nothing } /** * This method is called when the entire data becomes invalid, * most likely through a call to {@link Cursor#deactivate()} or {@link Cursor#close()} on a * {@link Cursor}. */ public void onInvalidated() { // Do nothing } 前者最终调用onChanged(此方法在整块数据发生变化时-就像调用cursor查询列表) 后者最终调用onInvalidated(此方法在整块数据失效时-就像关闭cursor,重新取值) 16、RecyclerView使用Glide加载图片,出现错乱的问题 初级方案,将其变成ListView 设置holder.setIsRecyclable(false); 终极方案:由于ImageView是异步加载的,而Holder不断的被回收,则判断设置图片时ImageView的tag与传过来的tag是否一致即可。 //Holder加入 holder.mTitleIv.setTag(R.id.img_url, shop.ListImg);//Glide加载 final T sourceTag = source;//解决异步加载错乱的问题 mGlide.with(context).load(source).into(new SimpleTarget<GlideDrawable>() {//防止第一次图片未显示 @Override public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) { Object tag = imageView.getTag(R.id.img_url);//解决异步加载错乱的问题 final boolean isTagEqual = tag == null || sourceTag.equals(tag); if (isTagEqual) imageView.setImageDrawable(resource); } }); 17、出现Caused by: java.lang.UnsupportedOperationException 原因在于Arrays.asList产生的ArrayList是Arrays$ArrayList,而非util包下的ArrayList,不具备add和remove功能 方案:解析出其中的个体,放入一个新的ArrayList中去(可使用List.AddAll方法操作)。 18、SharePreference.Editor中apply和commit的区别 前者是异步处理,没有返回处理结果;后者同步处理,会返回处理结果。 19、 java.lang.ExceptionInInitializerError Enum中初始化时,使用Application的单例,实际上它未初始化 20、setTextColor(0xff0000ff),0x|ff|00|00|ff 指0x代表16进制,ff代表透明度alpha,0000ff代表rgb值 21、open failed: EROFS (Read-only file system) 第一、是否有写入权限 第二、文件是否存在(参数要求是文件路径) 22、Error:Could not get unknown property 'release' for SigningConfig container. 解决方案:处理放置先后次序即可,1-SigningConfigs,2-buildTypes,3-defaultConfig 23、cordova中前端无法调用Android端,报错:CordovaBridge: Ignoring exec() from previous page load. 解决方案: 第一、判断url是否是内部APP的url,将此权限放开即可。 public Boolean shouldAllowNavigation(String url) { //judge url return true; } 第二、给feature设置onload=true <feature name="Service"> <param name="android-package" value="ServicePlugin"/> <param name="onload" value="true"/> </feature> 24、startActivityForResult,无返回,即在onActivityForResult里收不到返回值。 原因由于两个activity不在同一个栈中,导致收不到信息;修改方案:将跳到的activity的launchMode改为startdard,不要用singleTask或singleInstance。 优化方案: 1、能用FrameLayout的,都换成这个(因为Android会对此layout进行merge操作),同时能不用layout的也尽量不用2、纯色图片,能用xml写的,换用xml写,图片命令均以img_x_x.png3、中文都写到string文件里,有相同中文的,去掉其中一个,命名尽量短且通用,尽量用Html的from格式和String的format格式来处理字符串,两个方法均可在CMYStringUtil方法里找到4、上述两者不用的,你们暂时可以不处理,且命令尽量通用,如可以命名为alipay_str_finish就不命令为alipay_str_service_finish5、文字输入框,只能输入哪些类型,做一个限制android_limit,另外,监听一下回车键,看应该跳下一行就把回车键命名为“下一行”,当前表单填完,命名为“完成”,并触发提交按钮,数据进行验证6、Exception尽量去掉,原因是要找到错误处理掉,而非简单的加try..catch,并且加入log日志,并上传日志。7、写intent传递数据,对象用TransferData传递,基本数据类型用putExtra来传递,注意key都要用IConstants里的intent_key8、方法都命名为有意义的命名,具体参考“新人必读”里的规则,以及变量名,全局、局部、静态变量等,另外不用的字段都删掉9、findId尽量都改成getEelement,使用Ctrl+F来替换,尤其适配器里,尽量用getInflateView和getEelement来代替10、把公用的布局或代码提出来,尽量多用include、viewStub、merge 11、有网络请求的,都要加上onRefresh方法,避免请求失败,需要重新加载,当前页面无方法 12、优化语法逻辑,全面考虑出现问题的情况,具备产品思维 读这篇文章同时也要注意一下系统升级的问题,里面同样埋有不少坑:Android高级第十一讲之不同系统间的区别 另外内存是个亘古不变的老难量,参考:Android高级之十二讲之如何降低应用内存消耗 开发小技巧: 1、开发xml文件时,使用如下代码,既可以清楚的显示文字,又可以打release版本时自动去掉 tools:text="1" 更多:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0309/2567.html tools:text="1"
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 安卓未来的发展和命运几何? 现在VR和AR各种火爆,是否安卓能够在洪流中屹立不倒呢? 你好,其实这个问题,我也想知道,之前没做过思考,那今天我们分析一下。 VR和AR主要是虚拟现实和增强现实技术的应用,目前看到的应用场景有电影、博物馆(Android 3D技术也可以实现)等,展示性的应用刚开始会多一些。 符合事物发展由浅入深的规律,前瞻可以看到应用于医学领域,VR+机器人远程做手术,更加便捷人家的生活;而从以上场景也可以看到,VR是一个工具,在特定场景下才会想起的工具,而手机也同样如此,打电话、拍照、发短信、导航的时候才会用到。而VR和AR都需要一个发射器,可能是手机,可能是眼镜,可能是激光射头的东西,它们不是操作系统,首先避免了被直接取代的危险;但也有一个危险,跨界取代才是最致使的,如汽车、火车取代马车,那安卓是马车,VR和AR会不会是汽车和火车呢?答案往往事情发生前很难预测,就像杜鲁门竞争过杜威,安卓和苹果取代诺基亚一样,往往舆论缺乏透过现象看到本质的能力,结论具有很难的可预测性,个人也只能讲讲一孔之见。 VR和AR会取代很多东西在特定场景发挥的作用,不光是安卓,因为它起到了便捷性、低廉性、智能性等这些优点。 如果替代,先说说手机的几大特性,(关于安卓,当然不能只讲手机,还有房卡、智能设备,目前仅拿手机作为例子),打电话用眼镜来操作,感知人眼球的动作,就像HOME键和音量键一样,把人身结构也当作特定按键行不行?或者有人愿意使用,但我肯定不愿意使用,试想在工作、玩乐时要带着眼镜,那简直太束缚我的个人感受了; 其次发短信,可以通过VR技术,把操作界面映射到墙上,人用手指来操作和发送,当然打电话、导航同样可以,拍照呢?手机取代部门照像机的功能,依然不能完全取代,相信VR也很难取代吧。 将手机以主要功能分分类 提醒型的:电话、短信 功能型的:导航、拍照、字典 联系型的:微博、微信、QQ 通知型的:新闻、办公软件 大体两类提醒和功能型的,前者需要一个“管家”,后者需要一个“超人”,管家功能的手机不会消失,超人功能的安卓应用场景可能会被取代,如果看过《黑客帝国》,相信对那个黑人手里拿着一个汽车钥匙类的东西,操作后出现一辆AR版超级摩托车印象深刻,AR有可能会产生超能物质,然后把大东西变小,如车子、房子等,就像《三体》最后一部宇宙外的小宇宙一样,AR会不会制造出小宇宙也或可未知,但这确实可能是人类开启未来的一把钥匙。 回过头来再讲安卓,仅是一个智能时代的操作系统,用在一些实物上,AR操作的可能就是分子类的物质,而安卓操作的是文件,两者不属于同一量级但在短时间内,如果技术突飞猛进,安卓的功能可能仅放入手表用来操作通知、电话和时间等,人体中注入芯片来实现脑电波VR操作,遥远的未来,安卓能实现的可能就跟现在的豆浆机一样的功能;技术具有时代性,但不具有不可替换性,被取代是迟早的事,但短时间内人类尚且不能进入那个时代,而那个时代至少会有星际旅行,可能会出现跟外星交易的市场星球。 安卓和苹果用来制造一些小玩艺儿,新生技术来取代现在智能时代工具所起到的作用,有生之年应该是看不到的。O2O概念如此火爆,以至于说要取代B2C,可现在的美团还是美团,淘宝还是淘宝直播概念如此火爆,以至于人们纷纷显露隐私,减少人们之间的神秘感,只能说是社会的进步,仅不具有可持久性,观赏是一时的,巨型摩天轮不是每天都想坐的吧。 VR和AR也具有这些特性,但更重要的是发展的可持续性和潜力,我们更应该看重后者,如果你是个物理学家、生物学家、医学家,你应该为此尖叫,但作为一个工程师,可能仅在执行层,需要推测进化的未来,需要大量阅读和学习,在VR和AR浪潮席卷而来的时候,不妨加入他们,一起创造更新的产品和更好的生活,因此作为工程师,你不用担心失业,要做好的准备是,好好学习,适应变化,做好决策,争取以后成为决策者,而非执行者。 最后,这两个技术如此火爆的今天,仅是虚拟现实和增强现实,它不是真的现实,真的现实是人还需要喝水、吃饭、睡觉,现实的生活中,它们仅是更方便,但依然不能取代游乐场、电影院,人类是群居动物,需要不定时的聚居和发泄,准备好你的资本,不论金钱、健康、时间、家庭还是身体,适应时代的潮流,争做时代的弄潮儿! 后记:如果要一个确切的答案,那“砖家”就简单预测下,未来十年内安卓不倒,三十年安卓不灭,五十年后的事情,也不知道了。 如果要再加上一句时代的新词,用来表明严谨性,我想说:上面所说的一切都是错的。
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 问1:关于职业发展以及团队管理?(正能同學_) 请问在二线城市的小公司里,普通Android开发者的职业发展应该怎么向管理层过渡(具有前端以及后台开发经验)?以及当团队中有Android技术方面比你更好的新人进来时,怎么管理? 答1: 你好,二线城市,一般指省会所在地,按照目前国家总体经济形势来看,还是不错的。普通Android开发者,刚开始是需要自我提升的,努力做项目,和同事搞好关系,年轻不要气盛,尽量积累好人品 其次项目要多涉猎一些,像流行的框架多用用,最好自己分析下,不行就看看别人的理解,推荐几本书《Java编程思想》是必须看的,《Android内核剖析》和《深入理解Java虚拟机》可以帮你更好的认识Android和Java,买来如果看不懂,可以放着,过段时间再看 等你看的懂,驾轻就熟的时候,说明水平也达到了,在这之前都好好做项目,先做这门技术一万小时,满足此定律。然后一般只在一家公司,不能学到更多东西,薪资也会受到非常大的限制,可以跳一跳,平均2年跳一回,差的1年多也可以,这个时候你拥有比较多的工作经验,同时也可以逐步向前端和后台深处学习,俗话说:一样通样样通,就是说一样说通了,其他道理都是相通的,会学的很快,这时设计模式也需要熟悉,最好可以做一些项目架构,同时形成自己的架构风格和管理风格,如果有大公司的就职经验更佳。 当你工作5年,一般是职业的一个阶段期,需要稳定下来,沉淀职业生涯,积累财富同时自己也该考虑成家,这时最好找一个有前途的公司,或者说跟自己性格吻合的,之前找到那是最好,工资水平处于当地中上等,最好有股份或者期权,以期待未来有更大的收获。 现在回答你第2个问题,如果团队中有新人进来,而且技术比你好-首先三人行必有我师,比你强这是好事,这样才可以互相学生和切磋,然而天下无不散的筵席,不是你先走就是他先走,关键是自己得到什么成长和锻炼,学到什么东西;只要你坚持做这一行,总有一天你能成为行业的顶尖,因为中途总有人转行去做其他职业,或者转型,都再所难免。所以不要担心这个问题,和他好好相处,取长补短,共同把公司项目做好,谁说的方案好听谁的,咱们做技术的不搞形式主义,事情做不好是最糟糕的,而且方案永远没有最好,只有更好,也方便你们培养一个上进的环境。而对于个人而言,如果已经是管理岗位,可以让对方在擅长的领域多发挥一些,鼓励他承担和挑战难题,一方面他的能力得到发挥,另一方面你的工作也会轻松许多,而且你也可以从中学到很多,用人之长,宽容大度;能管4个人,你就是主管,12个人就是经理,管的人能力越强,你个人的成就也会越大,千万要往大处想,不可斤斤计较。 问2:关于职业规划和未来发展的问题(提问者:alanjet) 您好,我是一名在读大三学生,个人酷爱android开发,但由于自己当初没有选择读计算机专业,而是读了通信工程。很多编程方面的知识都是自学而来,android 自己玩了一年左右,因为要兼顾学业,目前只做过三个实际的项目,能力还是跟很多计算机专业科班出身的同学有差距。现在纠结一个问题,我是应该努力跨专业考一个计算机专业的研究生来补足自己的非科班出身造成的短板呢,还是应该去就业,哪怕是进不了大公司,也去积累工作经验也许更重要呢?很纠结,您给点意见和看法吧,谢谢您。 答2: 你好,前面两篇已经讲了兴趣和工作的事情,好在你刚大三,选择会多一些。举个小例子:之前一个同事大二就出来工作,直到毕业前夕,通过做外包,自己挣到人生第一个一百万,这只是个例,当然现在情况没有以前好了,即使通过做项目达到高级的状态,也很难有这样机会,除非有政府关系。 至于选工作还是读研的这个问题,不同人选择不同,如果你没有坚定的目标去工作,那还是选择读研吧。相当于你起点又高一些,而且读研一般一生就这一次机会,机会成本也比较高,第三如果你想准备大公司面试:一般要求研究生起,除非校招特别优秀,学院前三名那种,算法、计算机原理、单片机、基本的Java和Android知识、安全、通信等这块需要熟悉,可以去网上搜一下历年的面试题,大同小异(每年题目变化多端,不建议刷题,去了解背后的原理吧)。 就读研的专业来说,工作中见到通信工程来做软件开发的最多,计算机本专业的反而很少,因为自己是计算机专业的,而通信工程见到最多的是北京邮电大学和华中科技大学,相信这两所学校在计算机这块应该属于属一属二的专业吧。如果明确目标那就去准备,如果犹豫不决,就先一再二 当然时间成本可能会很高。如果大学时你能把上面讲的一些知识弄懂的话,那么可以参加明年9月份的校招,多去ACM上刷题,时间只有一年,不知道你能否把这些都学的比较纯熟,成为完全的技术男;一般情况下,这时也该准备读研了,按照自己的想法来。还有就是积累工作经验,看你是动手型还是理论型,如果动手能力比较强,去工作会有更多机会,理论能力比较强,读研会更好;但计算机专业比较特殊,学习的时候不多,去读读也是不错的,建议你去读研,考上最好,考不上自己努力过也不会再后悔,当然如果你一心想考那也是可以的,最倒霉的看到一个校友四年才考上,其中一年工作,人生很长,现在很短,许多选择做完就不能再回头,希望你做好自己的抉择。真不行看看开复老师的《世界因你而不同》讲的也是这个问题。 至于你现在酷爱Android开发,这跟计算机专业相关性不太大,比如软件工程、算法、Java等,主要是自成体系的一些东西,看看书-前面也有推荐,自己可以学的很好,加上自己的实践,因为了解到我的高中同学,读计算机研究生第一年往往在校学习理论,第二年实习、第三年论文,如果你的导师自己没有项目的话,一句话:主要靠自己。而且你也看到,研究生读起来性价比也比较高,只用读1年,但前不久看的一本书是哈工程的两位老师张国印 吴艳霞编写,那个书差的真是一塌糊涂,虽然前不久有人在微博上吐槽哈工程不如哈工大,那水平还真是不行,写书完全不用心,其他也不用期待了,所以读研最好选个好学校。 至于你已经做过三个项目,这已经比同龄人强很多,不能说计算机专业的就一定行,这个时候也面临实习的机会,如果实习应该找到工作问题不大,选择你心底最深的期待吧,工作还是读研,选择内心的真实呼唤。PS-如果不懂,就自虐吧,比如去跑步,一个不熟悉的地方,等到自己累到死时,自然会知道内心的想法-这也是跌入低俗的办法,往往激起内心 的真实想法。 alanjet 谢谢您,听了您的一番话,我感触很多。我决定了去找工作,也许这不见得是一个一定正确的选择,但很多选择,做了才知道。听您的,遵从我内心最大的声音,也许我去不了大公司,但该我走的路,会拼命认真地走。没想到能与您这样的专家交流,真的很感谢您指点迷津。谢谢。 问3:关于职业规划,以及工作发展方向的问题(提问者:Stanny_Bing) 你好,我目前是做Android开发的,算上实习期,目前开发了一年了,不算实习期的话,才毕业开始工作两个月的时间。最近,我慢慢地开始考虑我的未来规划,有两条路,一条是学习Java后台方向,Android和Java后台兼修,另一条是学习IOS方向,走移动端开发路线。但是做IOS需要考虑的一是开发设备的问题,二是语言的问题,在OC上我基本没什么了解了。我个人比较倾向兼修IOS,但是又比较纠结。请问,我该怎么去选择我的学习方向。另外就是,我所在的公司做的项目重心在网页端,移动端的看重不大,还有就是,我们是给政府做软件,对界面要求不高,我做了好几个项目都是用的同一个项目复制出去的大体框架,版本也还停留在4.2上,很多高版本的东西没机会去涉及。我应该换么。还是再累计一两年经验,或者其他的什么方法。 答3: 你好,目前看你的情况,确实会比较纠结和迷茫,不过还好优势和劣势比较起来,差距比较大,也容易做出选择。 先说选择的方向,一般来说,个人建议选择自己优势去参与竞争,即去找工作,凭借你的劣势去找工作,根本找不到,现在公过来最好能直接上手做项目,不用教;而OC更像你的兴趣,但你要选择的是根身立命的技能,就不能因兴趣,而因优势,在发达国家如丹麦、瑞典除外,人家是全民收入基本相等,类似共产主义社会,也容易出现优质的人才,像我国竞争这么激烈,不凭优势,基本生活会很有问题。 就工作技能和兴趣来讲,一般选择技能来生活,兴趣业余培养,当你的兴趣已经优于你的技能所提供的财务价值时,那么就可以选择由兴趣产生 技能,或者其他第二技能或者叫作业务。所以你本身是学习Java的,又做Android一年,显然Android更容易找工作,而就今年的面试情况而言,IOS求职者要远多于Android,就业也有优势,其次如果公司提供你学习后台的机会,那是最好,不过一般不过有,因为公司雇佣你的优势,而且只雇佣你的优势,如果要发挥比较全面,可以去创业公司,缺衣少药,野蛮生长,全靠自己一肩挑起,当然有些创业公司开出的薪水不比大公司低,显然对你的要求会更高,且行且观察吧,可以私下搭建一个个人网络,来实践你后端的技术,比如使用JFinal。 而对于未来,三五年之后,最好Android和IOS都会,这样你可以做到移动端总监,否则你只能是个主管或者经理,阻碍你成为一个部门的负责人,而这也正是我目前的现状,当然你如果没有那个想法,一心做好自己的事,把事做精当然也可以,是走技术方向的体现,刚我说的是走管理路线, 通常做技术的也仅有这两种路线可以选择;30岁前如果有去大公司的机会,尽量去,可以看的更全面,更立体,扩大视野,30岁后建议去创业公司,将自己的技术放大,拿到的期权或许三五年后,就值个几百上千万。因此,当你工作稳定之后,有些余钱,建议你更换开发设备,自己换Mac Pro和苹果手机,业余自己搞些苹果端开发,而公司通常不会给你换,大公司除外,像蘑菇街和支付宝入职送笔记本的。你学通Android后,IOS上手也会比较快,语言都是相通的,自己再做些项目,写写博客,逐步做积累,相信有一天,你会成为行业大牛。 最后,对于你目前的公司,一般国企和外包企业是不能选择的,学的东西少和职业生涯不稳定,对你将来益处较少;需要到一个重视移动端的公司,最好主要业务来源于移动端,比如百度地图,这样你的话语权会大些,薪水会高些,晋升也会快一些;一般情况下在小公司,要一两年换一份工作,因为原来的业务基本已经固化,除非规模较大,但那又是大公司了,你的技术提升有限;而且换工作也是提升薪水的有效方式,至少工作前五年,之后就需要稳定发展,不要轻易跳槽,而上面的问题也有讲过类似的话题,就不再过多阐述。
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 问1:请问大牛对功能和框架的认识有哪些?(提问者:执笔记忆的空白) 比如对于一个小公司来说,什么样的框架最适用,您经历过的小、中、大公司对于框架是如何处理的?自己封装框架?还是什么选择市面上流行的框架? 另外,对于一个团队,您觉得什么最重要?对于一个leader,怎样管理好自己的team。 答1: 你好,大牛这个称呼,一直都是给予别人的,我在坚持的事就是把复杂的东西,简单化的介绍给大家。 对于框架来说,刚开始在小公司的时候,没在于过框架,大部分逻辑写在Activity里,然后加上model和自定义view就完成一个项目;跳到中型公司的时候,也是原始期,项目刚开始,只是加入了controller,封装db管理器和逻辑管理器等;而到大型公司的时候,可谓真的是使用框架,从两位大牛身上学到比较多的东西,详见支付宝黎三平《支付宝钱包客户端技术架构》和天猫嗷啸《漫谈天猫架构设计》,网上应用都能搜到分别介绍支付宝项目依赖开发和各种层次的设计,到目前所在公司经过两年多积累形成一套自己独特的架构体系。 自己做架构,主要以公司的业务结构为基础搭建,遵循MVC或MVP或MVVP等模块,使用设计模式对整体进行一个定义,让后续的同学理解你的用意,一方面有利于项目快速开发,另一方面也有利于项目的稳定,同时可以分拆其中的一些模块进行细化和再造;当然在满足自己公司的业务要求时,可以寻求一些更好的体验或者优化方案,而项目最重要的莫过于网络框架和图片加载框架,而数据埋点也很重要,只是目前没发现什么样去做这块的框架; 现在常用的架构层级是:View层、activity层、model层、fragment层、service层、core层、依赖层、数据解析层、数据请求层、加载过程层、适配器层、工具层等,还有一些独立的功能和模块,以及分享和相似功能继承和抽象等。 作为项目负责人,或者叫架构师,你需要理解每套框架背后的设计原理,比较其优劣性和稳定性,做出合理的选择;以免选择后没源码不方便维护,或者框架本身还有明显bug未解决;是否满足项目的业务如加载中,如缓存在sd卡和内存等一些feature;这些都是要考虑的核心内容,最终改造成你自己的东西,像大公司一般不会轻易用别人的框架,可能存在漏洞泄漏自己的数据等等,但小公司也不担心那些,还不足够大到让人重视,暂时可以把性能和效率放在第一位,重视用户体验。 而对于一个团队,自身的管理能力,技术水平,都是限制团队发展和项目完成情况的硬性条件,比如平常项目期个人采用的日清法,可以有效推动进度;跟产品经理沟通一些需求实现方式,跟后端和UI沟通一些接口和设计问题;设计并维护架构,方便小组成员的开发工作,并在必要时给予提醒和帮助;做代码Review,保证代码质量,制定相应的项目要求和代码要求等;对团队进行定期培训和项目总结,争取福利也蛮重要的;对于某方面比较厉害的同事,可以将项目中一些模块或问题交给他,帮助他成长和满足其价值实现,尽量使用前沿技术和开发工具,使团队走在时代前列,自身也会有优越感;带团队呢,最主要的是:给大家提供良好的团队氛围,使每个人都能得到成长和价值实现,帮助大家争取好的机会和福利,以及做好面试把握好什么样的人可以加入团队-价值观、合作态度、学习能力、目标性等等吧。 问2:关于个人开发app有什么指导建议(提问者:承宇mom程序媛) 因为我们知道开发一款app,需要设计到很多方面,前端UI,后端服务,还有Android app开发。我想请问下大师有什么建议吗? 比如UI设计,有什么好的工具,好的app设计网站或者搜素材的地方推荐? 比如后端服务,有什么好的框架可以使用? 或者说我有一个很好的思路,怎么能一个人做下来一个能用的,好用的app? 希望大师能指点迷津,因为我相信只有实践才能学得更快,而且是有成果才能有成就感激励自己。谢谢!! 答2: 你好,首先呢,不知道你有没有开发过app,显然你说的那么多知识,感觉你的出发点是所有东西都自己做,这样工作量好大,不如将一些事情外包,比如产品设计、UI设计,后端也让搭一套简单接口出来,应该不到5000块吧。 其次一件事情要做精,个人经验至少要5年吧,至少编程如此,UI设计也是全新的一门技术,后端嘛可以3年学好; UI设计,个人没有设计过,一般有原画师画,或者用PS,公司同事都是用PS做图的,个人以前在学校学过简单的,但UI设计确实没做过; 个人觉得第一、用系统提供的图片吧,framework层提供大多数软件所需要的图片,前进后退、删除等等;第二、自定义shape;第三、可以在网站上找素材比如:http://blog.csdn.net/reboot123/article/details/8611382这篇博文有一些介绍;第四、图片压缩,使用webphttp://blog.csdn.net/reboot123/article/details/46552437,和tinypng.com 关于后端服务,大学时学的SSH,感觉好麻烦,但后端同事说团队合作嘛,还是要用这个,另外可以加上JBoss,可能协作性比较强;如果个人开发, 比较建议使用JFinal,个人也在学习中,希望学会好,做个个人网站,有兴趣也可以一起学习交流经验。 至于App开发,有一些经验之谈,比如:http://blog.csdn.net/liuxian13183/article/category/1157874可以参考个人博客项目管理系列的文章,如果有兴趣还可以再看看项目架构和设计模式,对产品和android系统有个通体的了解,开发会比较快速哦。 最后再给个建议,个人做app嘛,可以做个不需要后端的,简单一点的,比如照相机,比如播放器,本地素材+开源库就足够了。看看github上的项目,都写的很棒的。产品呢,要简单,操作方便,功能清晰,最好你家小朋友都可以马上学会使用,这样当然是最好的了。 问3:Java基础---应该有什么样的准备和多深的积累?(提问者:jheee) 其他方面的基本都问到 了,就问下Java基础相关的吧。看完您回答的其他问题,感觉很棒。 如果以后打算从事Android应用开发,或大数据方向,就Java基础而言,应该有什么样的准备和多深的积累? 谢谢。 答3: 你好,先谢谢你的肯定。 关于Java基础,其实就那一些东西,简单的概念,简单的算法,简单的容器,简单的设计模式,很简单的知道就可以开始安卓开发了。 安卓开发,运用比较多的是安卓上的概念,比如四大组件,四大布局,基本参数,窗体,View,开源框架,会用这些也就满足基础的开发任务。 至于想更高效一些,更多的是经验之谈,每个人可能都不太一样,可能你做新闻客户端,他做视频播放,我做金融客户端,还有做游戏的,每个方向重心不同,自然所学的技术也就不同;个人是专注应用程序开发,几乎没做过游戏客户端,所以提供的也仅是应用层的经验。 比如线程:它的定义、多进程的实现和管理,同步和异步的实现原则,如何实现并发,高效的原则,甚至虚拟机的定义,内存大小,缓存机制的设计等 比如界面:如何画出,每个动作会触发哪些底层和表层操作,内存占比,动画,与多层界面的交互,如View、ViewGroup、Window、Activity等 比如布局:内边距、外边距、weight比例、tableLayout缩哪行哪列,hierachy层级关系,字体色值,样式,自定义属性等 当然还有很多,最近准备出一本书,估计要等的比较久,是写这些东西的,可以先参考下本人的博客,基础篇、进阶篇、高级篇、源码篇。 关于大数据,自己没做过,只是有朋友在做,那天他在依据算法,对一些byte流、char等字符做操作,这个可能对Java底层要求严格一些,比如位、内存存储和引用及使用、回收机制、算法等等,由于是界面化的,你可以买本大数据的书看一看。
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 前段时间写了不少博客,在问答页面也陆续回答几十个问题,之后Csdn乙同学找到我,说要推荐我参加问答类目作答主,解释一些同学的疑惑,本着分享精神,加上多年来一直默默的使用网上大家的贡献,于是写出一部分个人见解,大家仁者见仁,一起讨论,如果能给出更好的方案,那是再好不过的。 问1:有关于后台服务的问题?(提问者:qq_26763799) app中有个功能,玩家通过我们app启动另一款应用(大部分是游戏类,所以比较占内存),我们app开启后台服务倒计时计算用户使用时长,当到达规定时间就发放奖励,但是当内存紧张或者回收机制比较霸道的手机上,总会失效,用户经常反应玩了规定时长,还是拿不到奖励,有没有什么能优化的方法或者地方或建议? 答1: 玩家时间,建议使用token+游戏包名+毫秒的方式保存 关于后台倒计时,最霸道的做法,我们可以开一个进程:里面专门运行一个Service来做这个倒计时功能,使用Aidl的方式操作,Service要开放权限给到你的主进程。一个进程比如只有24M,你本进程即使死掉,它依然可以运行,下次进来继续从此进程中读取用户时间。 其次,你可以把发放奖励的玩家时长配置到文件中,然后判断只要应用未关闭就执行倒计时,并叠加写入文件,比如隔2s就写入文件中,即使有所些微差距也是可以的,这样即使下次重启,你也可以提取出上次玩家玩的时间,继续执行倒计时-当然可以存在sd卡、内存卡、数据库、SharePre甚至网络服务器端 最后,如果能把你们耗时的程序打成so包运行,或者放到其他进程运行,把自己的应用内存消耗减至最少,可以使用RxJava的schedule方法操作,这样给用户奖励的事情,最好将时长写入文件来配置,当然最靠谱的是写入服务端数据库,这样可以有效保护用户的权益,减少对用户权益的损害。 问2:有关于定时提醒的问题?(提问者:qq_26763799) 应用中某些功能模块需要做到定时提醒功能(类似部落冲突这款游戏建造时间到了自动菜单栏通知提示),用alarmmanager可以做到监听系统时间,到点了发送,但是在某些手机rom下(特别是三星,GC回收有点过分,灭屏就全回收)灭屏或者当前app进程被杀后将收不到提示,有些甚至定时太长也会导致收不到,或者一亮屏就一股脑全部提醒,有没什么好的优化或者保活策略? 答2: 那首先,你可以参加上一个同学的答案: AlarmManager是个不错的方案,让依托于系统,来实现轮循,是比较靠谱的方式,问题主要在于如何保证自己一直执行,这是一个策略,即保证轮循任务不管怎样能一直执行 实现方式多种多样,这里介绍几种:通过Aidl另启进程、通过Application启动时即开启轮循任务+Demon任务核心思想,就是把任务写入文件,或者写入服务器,通过本地轮循的方式,来操作即将触发的任务;从整个系统来考虑,所有的功能都是用文件来实现的,再加上驱动和触屏,所以文件是编程的基础,存在这里,外加服务端备份,是处理棘手问题的利器。 以下资料来自个推: 第三方系统管理软件限制收不到推送的消息 需要用户手动操作第三方 ROM 的管理软件 EMUI OS(华为) 自启动管理:需要把应用加到【自启动管理】列表,否则杀进程或重新开机后进程不会开启,只能手动开启应用 后台应用保护:需要手动把应用加到此列表,否则设备进入睡眠后会自动杀掉应用进程,只有手动开启应用才能恢复运行 通知管理:应用状态有三种:提示、允许、禁止。禁止应用则通知栏不会有任何提醒 Flyme OS(魅族) 自启动管理:需要把应用加到【自启动管理】列表,否则杀进程或重新开机后进程无法开启 通知栏推送:关闭应用通知则收到消息不会有任何展示 省电管理: 安全中心里设置省电模式,在【待机耗电管理】中允许应用待机时,保持允许,否则手机休眠或者应用闲置一段时间,无法正常接收消息。 Funtouch OS(VIVO) 自启动管理:需要将应用加入“i管家”中的【自启动管理】列表,否则重启手机后进程不会自启。但强制手动杀进程,即使加了这个列表中,后续进程也无法自启动。 Color OS(OPPO) 冻结应用管理:需要将应用加入纯净后台,否则锁屏状态下无法及时收到消息 自启动管理:将应用加入【自启动管理】列表的同时,还需要到设置-应用程序-正在运行里锁定应用进程,否则杀进程或者开机后进程不会开启,只能手动开启应用 MIUI OS (小米) 自启动管理:需要把应用加到【自启动管理】列表,否则杀进程或重新开机后进程无法开启 通知提示设置:应用默认都是显示通知栏通知,如果关闭,则收到通知也不会提示 网络助手:可以手动禁止已安装的第三方程序访问2G/3G和WIFI的网络和设置以后新安装程序是否允许访问2G/3G和WIFI的网络 MIUI 7 神隐模式: 允许用户设置后台联网应用,开启后应用即可在后台保持联网,否则应用进入后台时,应用无法正常接收消息。【设置】->【电量和性能】->【神隐模式】
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 既然写到后记篇,就代表本系列到此为止,暂时告一段落;其他一些Manager随后有时间再补,就像源码的一些翻译一样,有时间总会补上的,同时也希望大家一如既往的喜欢,直言不讳的提出宝贵意见。 后记将讲述输入模块和编译系统内容,以及对整个系统的总结。 一言不合就来图,且看下图,有关硬件消息传递-以下是消息在硬件中处理后如何传递到Window层的过程 硬件的消息,由触屏和按键触发,通过驱动处理,形成Up、Down、Move这样的事件,然后再把消息分发到KeyQ类中, 由InputDispatcher读取后发给用户,API层处理消息形成长按、点击、轻触等系统定义事件,到底是Window层先处理还是View层 先处理,这由手机原始设计决定,除特殊配置按键外,均由View层处理,然后到ViewGoup,到Activity,这样传递出来。 有关资源访问: data/app:apk被复制后的目录,系统应用放在system/app目录下 data/dalvik-cache:class.dex的存放目录或odex data/data:数据安装的路径 attr用来设置一些视图的属性 资源的访问可以通过context或packageManager的方法实现(id小于0x1000,0000是系统资源,大于0x7000,0000的是应用资源, 而更换系统主题也可以使用这招,判断获取的资源是01开头,则返回07开关的;同样可以替换AssetManager的路径,通过修改 framework-res.apk实现,不建议使用) String OrginalName=getResourceEntryName(orginalId); int newId=getIdentifier(OrginalName,……); String newName=getString(newId); 程序包管理:包含三块,提供具体组件的intent,进行权限检查,提供安装删除的癌 PMS和其他Manager一样,运行在SystemServer进程中,使用/system/etc/permissions管理系统和应用权限和platform.xml管理应用的pid和uid,使用/data/system/packages.xml保存安装包基本信息:名字、路径、权限等,使用DefaultContainerService将apk复制到data/app目录下,使用Installer服务(localSocket)将文件解压出来、创建数据目录,data/dalvik-cache和data/data 应用程序的权限有四种,默认为普通,危险会提示,签名要一致,系统指系统签名才可以;证书指meta-inf下默认文件,可指定可多个,首个需要key值为2390个16进制值来自证书;签名一个应用只能有一个,可包含多个证书;shared-user指定共享用户id对应的签名和权限 platform包含group标签(应用群组)、permission标签、assign-permission标签(将权限加入系统,用于群组)、library标签(依赖文件)、feature标签(wifi camear location sensor bluetooth touchscreen); 提取dex过程包含,解析platform.xl、验证签名到保存data/dalvik-cache中,最后提取Service、Receiver、Activity、Broadcast等;安装过程包含检查package.xml是否存在,然后读取mSettings.mPackages,决定是否创建package.xml,加入并生成新的package.list,由handlerParams下两个InstallParams和MoveParams完成。 软件的安装和删除,基本上应用层也做不了什么,因此也不再画图和详细介绍,仅仅讲一些知识点。安装使用PackageManger的installPackage方法卸载deletePackage方法,当然后续都是异步执行的,检查权限、删除目录和缓存、发出广播、终止进程,完。packageURI是安装路径,系统安装可以用adb push放置apk到system/app目录下,而一般应用只能用adb install 安装到data/app目录下。 关于如何启动四大组件,应用被安装后,这些组件的intent-filter均被放入ContentResolver,通过intent的query方法找到一一对应的组件。 输入法框架-Input Method Framework 输入法有效贯彻Jni的使用原则,由服务端和客户端两块组成,即保证安全性又保证流畅性,详见: Android高级第十讲之AIDL与JNI IMF的核心思想类似:使用Service后台进程的方式运行和窗口创建,将监听到的输入内容传递到编辑框。 咱们从以下几个重要组件说起: IMF:Input Method Framework 输入法框架 IM:Input Method 具体输入法 IMS:Input Method Service 具体输入法服务。记录输入法是否添加、输入区是否显示、当前窗口显示状态等具体事宜 IMM:Input Method Manager 具体输入法管理实例。包含两个Binder,一个将按键消息发给编辑框,一个用于IMMS访问客户 进程、管理IM的显示和隐藏。记录输入法的名字、是否已经启动、当前服务窗口 IMMS:Input Method Manager Service 具体输入法管理服务:记录IMM对象、焦点窗口、连接两者的Binder IME:Input Method Engine 具体输入法引擎(泛指以上内容) Binder对象对应的类如下: InputConnection:接口,定义编辑框需提供的函数,被IM通过Binder调用 IInputMethodClient:aidl,Binder本地代理,指向IMMS接口,以便向客户端传递与输入法相关的信息 InputMethodSession:接口,定义IMM直接访问IM的接口 InputMethod:输入法提供的API接口 如何交互、传递参数、解析数据 输入法操作分三块:启动、显示和切换,启动指IMS(两个服务Binder,一个IMS创建,一个客户端创建,分别用于和对方 交互),显示指与客户端交互(将焦点从Wms那里拿过来、调用IMM的showSoftInput、调用IMM的windowGainedFocus-位置 固定显示在编辑框下面),切换不言而喻(不同输入法即apk,通过setting设置) 如何自定义输入法?-厂商可自定义的三个Binder IInputContext:编辑框的Binder对象,不能自定义但可以重写onCreateInputConnection来返回自定义的InputConnection窗口, 用于读取、插入和替换字符,以及添加和删除字符 IInputMethod:IMS的Service启动后返回的Binder,被IMMS调用,用于直接与输入法交互 IInputMethodSession:IMMS请求IMS创建的Binder,用于客户端调用 KeyBoard.java用于将按键位置转换成键值,再由IMS转化成相应的提示字符,输入法是一个新窗口或者叫系统窗口,区别于 Activity的应用窗口。 Rom编译知识: .mk文件和各种shell脚本共同定义编译框架,基于make概念;几年前用cygwin做完一些so库的编译,非常麻烦,可能没做过C 开发的原因,写一些“.h”、“.c”文件,定义c与java交互原则,最终被放弃了。 源文件包括:资源、aidl源、java源、java静态库、Java共享库,使用aapt命令将资源文件编译成.二进制,再通过dx工具将 jar打成dex文件;关于签名,是用来认证的,而且可被多次签名;zipalign优化Apk内部存储,对内部数据进行边界对齐。 Framework包含的重要文件 framework.jar:.java变.class,最终变dex的文件-系统apk,可以不用;包含android.jar core.jar:Java库文件 ext.jar:扩展类库 framework-res.apk:需要使用的各种资源 上面有讲资源01开头的是Framework资源,07是应用资源,而02指 非应用资源。01-attr资源,02-drawable资源, 03-layout资源,04-string资源,使用public.xml记录id与资源对应关系 Rom有两种,linux内核和Android所需的;Cpu包含地址总线和数据总线,前者用于输出CPU访问的地址信息,后者用于传输 CPU要读写的数据;CPU采用ARM内核,地址总线32位,支持最大存储空间4G: SRam:最早期异步存储器,512kb、1M的映射存储 Nand Flash:U盘,SD卡,一般仅需4个地址就能支持16G数据映射,因为它有二次引导内存 SDRam:同步RAM,访问速度快,仅比SRAM好一点 外设地址:显示器、键盘等,PC分南桥(低速器件)和北桥(高速器件)、中内存这样三组总线,而嵌入式仅使用一组总线,这是由 CPU运行速度和特别支持决定,CPU上会有一小段引导程序无法改变 一般NAND支持的二次引导内存在2M以内,存储在NAND设备中,不能格式为FAT32(否则失效),可以识别以太网口,具备 USB接口驱动,使用fastboot,读取Ext分区中的update.zip,这也是通过USB连接PC完成的,也是为什么系统更新包只能放在SD卡 中而不能放入内存卡中的原因,其中的内存卡映射路径: boot:linux内核,空间为8M recovery:保存boot原始数据,也是8M,相当于boot原始复制品 Radio:无线通讯模块,程序处理器提供给用户界面,基带处理器提供底层无线通讯;多媒体处理器完成音视频解码 system:内核,各种所需驱动库、应用程序,大小约500M data:应用程序所需或解压出的文件,约1G sdcard:外置内存卡 一般将boot.img、system.img写入Rom,格式非固定,叫做刷机过程。
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 前面讲过Wms、Ams与Activity的一系列交互,包括创建过程、消息传递、窗口展示等,紧接上篇介绍最终的实现者-窗口和View,上篇对窗口已经有了比较多的介绍,本篇我们再对View再更深一步的了解。 首先明确下View的功能,主要用来展示画布即交互的图片背景等,而且承接各种手势动作消息,以及这两者之间的一系列内容;其他的属于窗口的概念;也可以说内容展示除了窗口就是View。 消息分发指:1、将触摸、键盘输入等消息转化成操作系统可识别的信号2、判断按键消息直接发给当前窗口进入View,触摸消息根据坐标匹配窗口3、最终处理消息。 界面绘制指:1、计算视图大小执行measure方法2、为视图分配位置执行layout方法3、将视图绘入窗口即draw方法。而View所有的功能将围绕这两点展开。 就第一点先说说,用户消息类型指Wms将硬件物理消息转化成统一格式消息,分为三类:按键消息、触摸消息和轨迹球消息(此消息API的Demo中可见,游戏中比较常见)。而消息的组成由以下三项:Action(上和下)、KeyCode(键代码0-9a-z)、Repeat(重复次数)。PS:由于安卓系统没有苹果系统全面,按键消息不断发出,安卓需要自己定义滑动速度和动作逻辑如消息延迟动作+延迟时间(500ms),因此安卓开发者也可以对此进行拓展。按键消息只有上和下,而触摸消息比较多样化,下面就拿它作为例子讲一讲: Action:屏幕一般支持多点触控,比按键多出POINTER_DOWN2、POINTER_UP2等 EventTime和DownTime:消息发生时间和按下时间,用以区别按键和滑动事件 Pressure:力度大小,可大于1 Size:电容触摸的面积大小,0-1之间 getX(size)和getY(size):触摸点的x和y坐标 按键消息派发过程:比较简单,不再用图形展示,直接写步骤;先说长按事件 1、生理长按(native C++中定义,区别于长按延迟500ms)时间,首次按下不松手,会启动二次长按 2、按下后,如View类无处理,且来自于DPAD_CENTER,则View启动异步消息加调onLongClick事件 再说点击事件 1、底层得到按键消息后,回调ViewRoot的InputHandler中的handleKey函数,再调用dipatchKey函数,发送DISPATCH_KEY消息,让deliverKeyEvent方法处理: 一、执行mView.dispatchKeyEventPreIme():在输入法之前处理,这样重写此方法返回true,可以拦截Ime 二、派发消息到输入法中 三、执行deliverKeyEventToViewHierarchy,传递给真正要处理的视图;此时还要做几件事:判断消息是否导致离开触摸模式、将消息给到根View如应用窗口即PhoneWindow的DecorView,依次处理音量键、系统快捷键、菜单键、Activity到ViewGroup或非应用窗口直接到ViewGroup(如状态栏),未处理消息最后回传到PhoneWindow的onKeyEvent(Up或Down)方法或ViewGroup的onTouchEvent。 PS:上面讲到派发事件到根视图,其中有项是到Activity,先执行dispatchKeyEvent(回调onUserInteraction、Window对象的superDispatchKey、KeyEvent.diapatch如无调用否则停止-调用state.startTracking对消息跟踪和回调receiver.onKeyLongPress完成长按处理代码,继承后不执行下步),再执行onKeyDown(View中-按键消息是DPAD_CENTER或KEYCODE_CENTER代表确定,判断是否可按,是否可长按,longClick和showContextMenu发生在这里;Activity中-处理Back键,判断mDefaultKeyMode如DEFAULT_KEYS_DISABLE什么也不干、DIALER拨号程序、SHORTCUT快捷键、SEARCH_LOCAL/GLOBAL本地或全局搜索处理相关逻辑,键转字符keyMode,启动相关Activity;PhoneWindow-记得前面讲过,再讲一遍,依次判断执行音量键、播放器键(一般没有)、相机键、菜单键、拨号键、搜索键)和onKeyUp(同前者) 按键与触摸最大区别在于: 前者需要先经过Wms(如上面括号里执行的),后者直接进入View; 其次前者是父视图(super.dispatchKeyEvent)先处理消息,然后才是子视图,后者恰恰相反; 前者有系统键,后者要确实处理View,而查收的方法。 触摸消息发生时,区分应用窗口和非应用窗口, 前者对应PhoneWindow的DecorView类型,如果存在Callback对象,调用dispathTouchEvent,则执行Activity的,然后再执行Window的,最后调用mDecorView.onTouchEvent(当然之前要计算是否拦截),不存在则直接调用ViewGroup的 后者对应ViewGroup类型,如果onInterceptTouchEvent未拦截(默认不拦截),则直接分发给子View。 PS:如利用onTouchEvent却没有调用父类此方法,则触摸、点击和长按事件均不会触发,过程tap->press->longpress。 View的绘制上面已讲,那么诱因是什么?1、内部状态发生变化调用rqeustFocus 2、添加或删除子View调用requestLayout 3、大小发生变化调用invalidate,后两者可见性发生改变时也会涉及到。 列举几个比较常见的方法作为结束: refreshDrawableList:为视图赋予不同的Drawable对象 onFocuseChanged:处理焦点变化的逻辑,输入法与View间通过window来交互 setVisibility:设置可见性,可见到不可见不会调用requestLayout,而到GONE则需要调用 invalidate:对View树重绘,一般requestLayout之后均会调用,顺序先根后子 requestFocus:使某视图获得焦点,直接调用或方向键移动 measure:measuredHeight=bottom-top measuredWidth=right-left EXACTLY=MATCH-PARENT,不可重载 AT_MOST=WARP_CONTENT,UNSPECIFIED不确定;慎用weight(重写指子类功能变异,重载指子类功能变多) generateDefaultLayoutParams:如ViewGroup无重载,则设置margin无效;生成默认LayoutParmas layout:布局控件,获得子View宽度、根据gravity决定位置、为子View分配位置 draw:绘制界面;dispatchDraw绘制子视图;需要Surface配合,分两种,一种是显卡一种是CPU模拟的,从中获得Canvas对象;onDrawScrollBar绘制滚动条;设置scale和matrix,以及translatet和rotate View的功能和特色就如上所述,至于发挥的空间,则在各大App均有表现,需要自己慧眼识珠了。 最后给出屏幕绘图的过程 有关消息在硬件层的分发到此为止,View内分发的分析,请看下面 实例分析:Android中级第十一讲之MotionEvent的分发、拦截机制分析 有的同学嫌本篇介绍的太少,那再就拿一张图来讲讲消息分发机制 注:此View写在xml中,未写布局包含,是一个LinearLayout。 大家可以看到当View执行dispatchTouchEvent后,一般未处理会传给基类处理(默认是framelayout,而mainView还有linearlayout父类),然后传给Activity,最后传给phoneWindow的DecorView处理;最下面绿色圈住的,指下一节使用MessageQueue来处理用户消息, 然后执行ActivityThread和ZygoteInit的main方法 而执行这一事件分发,需要MainThread、两个Binder和一个RenderThread,RenderThread负责分发硬件消息,由一个Binder发出,通过IPC机制传递给窗体层的Binder,窗体层Binder再调用MainThread执行上面一系列流程,实际上也就清楚了底层消息如何从硬件层到窗体层的。 剩下一个问题,空Activity的convertView到底有几个层级? 默认的Activity的convertView有三个层次,其实最低层次id为content,跟tableActivity类似。 消息先由DecorView处理,如果不处理,则分发到下面的ViewGroup和View;如果还没处理则上传给PhoneWindow,最后给Activity. 另外关于消息传递,通过InputDispatcherThread来执行,由InputReader读取,通过InputChannel,由InputDispatcher来传递, 最后调用ViewRoot的dispatchMotion和dispatchKey来传递给页面。 如何自定义各种形状的View?定义多种形状如Rect、Triangle在onDraw方法画入即可。 事件被消耗的方式:一种在onTouch方法里,一种在dispatchTouch里拦截不再向下传递。 Binder传输性能高、安全性高、CS架构,通信由Client、ServiceManger、Binder驱动和Server组成,只有驱动在内核空间,其他 均在用户空间。 通信机制:Service向ServiceManager注册,得到虚拟的Uid和Pid,使用Binder通信;Client向ServiceManager请求,得到虚拟的Uid和Pid,以及目标对象的Proxy,底层通过硬件协议传输,使用Binder通讯。 通信时Client手持Proxy,ServiceManger进行转换,调用到Service。三者运行在三个独立进程中。Client/Sever全双工,互为Sever/Client 解决View事件冲突的两个办法 1、重写父布局的onInterceptTouchEvent方法,需要向下传递则return false,否则return super.onInterceptTouchEvent. 2、重写子布局的dispatchTouchEvent方法,加入getParent().requestDisallowInterceptTouchEvent(true)方法,告诉父布局不要拦截,同时在处理事件结束,再设为false;或者不重设为false,让子View一直优先处理事件。
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 上一篇我们主要讲了Ams,篇幅有限,本篇再讲讲Wms,即WindowManagerService,管理窗口的服务。主要负责窗口的创建、删除、状态等与手机交互的事情,与Ams配合使用,在SystemServer中创建,用来保持窗口层级关系方便SurfaceFlinger绘制屏幕,和传递窗口信息给InputManager调用InputDispatcher将输入消息派发到顶层窗口。 Activity、View与Window的区别与联系,View是最底层的显示控件,Activity包含一个DecorView+Window实现类引用PhoneWindow,而Window是一个接口-用来将手势动作转化为Activity可识别的信号。至于什么叫窗口,每个Activity就是窗口、每个Toast也是一个窗口、每个系统弹窗同样是一个窗口。再比如Windows操作系统的任务栏多窗口,比较Android系统当前应用的单窗口。而窗口由什么组成,状态和界面。哈哈,听起来是不是很简单?也就是说你所有的操作会产生一个状态,这个状态会在界面上得以体现,比如后退关闭当前页面。前言介绍有点多,接下来说重点。 1、前面讲的界面是Surface,展示由SurfaceManager来管理,状态由WindowState来管理;布局两种层叠与平铺,Windows的平铺,Android的层叠; 2、布局对应两种实现方式:独立进程式和库共享式。作用是用来绘制屏幕和消息处理,前者独立后者依赖。这样即使一个应用崩溃系统依然完好。 3、应用窗口的高度=屏幕-状态栏,对比苹果的屏幕-菜单栏。 4、关于焦点,一般情况下最前面的窗口获得焦点,二般情况下系统按键获得焦点,支持窗口切换、添加动画效果。 跟View的操作差不多,Wms也有几个动作,assignlayer给窗口分配层值越大越靠前(动态的),performlayout根据输入法窗口、状态栏和窗口动画来计算可用的大小、placesurface像draw方法一样将窗口展示在屏幕下。 介绍几个跟Wms关联比较紧密的类 1、WindowManagerPolicy:接口,约束Wms能干什么。 2、WindowManager:Activity通过它调用remove和addView,Wms通过Binder类型的W类以IPC方式传给ActivityThread(UI线程),由Handler类型的ViewRoot类转成本地的一个异步调用。 3、SurfaceManager:调动SurfaceFlinger(linux驱动)绘制屏幕,使用芯片的图形加速器引擎完成工作 4、InputManager:获得输入消息,拥有Wms引用;执行时先执行InputMonitor,使用InputWindow保存寻找焦点所需信息 5、WindowState:真正的窗口,记录大小、层值、动画状态;每个窗口均有一个它;WindowToken用来实现IPC交互,一个窗口只有一个,子窗口均指向它;AppWindowToken指App在Wms的token用来最终管理交互。 6、Animation:Dim和Fade,分别是变暗和渐进动画,是窗口用的最多的效果 7、PolicyThread:WindowManagerPolicy类型,主要Wms使用异步操作的一个工具 8、Session:SufaceSession(用于向SurfaceFlinger添加删除窗口)的包装类,显然用来保存渲染的缓存信息,主要有uid、pid等 9、WaterMark:作用一防篡改二加通用背景,保存在/system/etc下,格式是content% 10、VMThread:SystemServer的异步线程 Wms最重要的就是添加和删除窗口,还是要具体讲一下的,先讲添加窗口 创建ViewRoot对象,调用setView方法,通过IPC执行Session中的add方法,然后addWindow;在这一步需要 执行一些前置条件判断比如参数是否合法、窗口是否已存在、将屏幕大小给到InputManager应用输入法墙纸窗口的 attr.token和type一致;再添加窗口相关数据,如新建WindowState(包含session、ViewRoot的W对象、隶属窗口 等)加入mWindowMap、传递touch焦点等;最后执行后置判断将窗口状态变化反映到相关数据中,比如将焦点给予 可交互的窗口、计算并重新分配层值。 接着再讲一下删除窗口,与Windows不同,用户不能直接关闭窗口,分两种显性删除直接调用WindowManager 的remove方法隐性删除指执行finish回调隐性删除:先发送一个消息,通知关闭窗口,removeViewImediate负责 删除activity窗口,closeAll负责删除Activity相关如菜单、对话框窗口,删除自身的Session;然后看是否执行一个 动画、尝试把焦点转移到另一个窗口,相当于反向执行add过程。 接下来讲讲窗口大小,mContainingFrame(整屏)> mContentFrame(窗口实际)> mFrame(屏幕上显示), 使用layoutWindowLv来计算窗口大小,如果是输入法则是可用大小,否则情况有三:1、考虑状态栏2、全屏3、 是否排序输入法窗体大小;输入法窗口仅允许被添加一次、且下面不允许有内容,调用ViewRoot.W的resize方法, 一般使用下面方法,防止其获得焦点 getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN); SOFT_INPUT_ADJUST_PAN:键盘会将内容整体向上顶 SOFT_INPUT_ADJUST_RESIZE:键盘会浮在内容整体上 影响窗口可视状态因素有二:1、动画,2、是否显示 最后SurfaceFlinger对窗口进行重绘,操作Surface窗口,对视图View进行变换,而动画有两种Tween (tranlslate、scale、rotate、alpha),对Surface操作进行变换,主要对界面进行不间断的重绘和对View操作 进行任意变换的Frame。 多种情况下Ams会调用Wms如增删appWindowToken、设置窗口可见、动画切换等,少数情况下Wms会调用 Ams如暂停App切换、横竖屏切换、杀死App等。 横竖屏切换主要由三种情况引起: 1、ActivityStack执行resumeTopActivityLocked 2、Wms执行window操作 3、人为旋转设备。 最后放出来两张图,activity启动过程和停止过程: ApplicationThread在ActivityThread中生成,最后执行scheduleLaunchActivity启动。
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 讲到实战,就不得不拿两个例子来说明,本篇想拿的是应用最广泛的两个:Ams和Wms,一个管理activity,一个管理窗口,而前面我们已经讲了不少,本篇不再赘述。 关于Ams对activity的管理,无非这几个方面:启动哪个activity、物理按键对activity处理、内存骤减时activity的回收规则,以及暂停activity的一系列操作。 先说如何启动activity?有哪些知识点。 Ams调度activity,运行某activity需要通过Ams决定,一般情况下仅限允许均可执行;记录运行的页面,保存activity一些状态,以便下次启动更加快速,一般放在ActivityRecord,以前叫HistoryRecord;做进程管理,清楚运行哪些进程,各自的状态和信息集合 当然Ams除了管理activity,还会管理其他两个主要数据类,如Process和Task;Process同样也有个ProcessRecord用来记录进程运行信息和状态以及内部有哪些组件(指service、provider这样的),多个apk可以运行在同一进程中,可以通过android:process来设置,一般情况下还是不要,因为手机默认64M内存(不同分辨率不同),内存一般也仅够一个apk使用;而TaskRecord记录activity所在的栈的id、intent和activity的数量,用于对栈进行管理。 AMS常见的默认系统常量: 1、MAX_ACTIVITIES=20: 通过硬件设置,当前页面仅有一个activity存活,其他20个被缓存起来,超过则杀死优先低的 2、MAX_RECENT_TASKS=20:通过硬件设置,栈最多存储20个,超过后最先舍弃最早的栈 3、PAUSE_TIMEOUT=500:启动activity最长时间为500ms,否则关闭该页面,一般表现是“屏幕黑一下” 4、LAUNCH_TIMEOUT=10*1000:启动进程最长10s,否则舍弃 5、PROC_START_TIMEOUT=10*1000:启动进程后10s内必须报告AMS启动状态,否则AMS不承认此进程的存在 以上是最基础的变量,相信在多年开发中,大家对其引起的现象已经历历在目 其次在启动和暂停activity过程中,需要一些变量来保存状态,主要因为AMS是通过Service机制动作,比如 1、mPendingActivityLaunched:即将启动的activity列表 2、mStoppingActivities:A跳入B,B启动后A即被加入此队列,区别于mHistory的A跳入即加入 3、mHistory:所有后台activity,A跳入B即A被加入,或按Home键B被加入,回来按回退键则B被清除;finishing=true 4、mFinishingActivities:上面列表中执行完finish方法的activity,此时并未完全杀死等待被回收 5、mLRUActivities:最近使用的activity,包含mHistory里的activity和已经清除的activity 此外还有HistroyRecord记录mPausingActivity(record对象,记录执行onPause方法的activity),mResumeActivity、mFocusedActvity和mLastPausedActivity等。 上面讲完基础,接下来进程流程,讲讲如何启动Activity? 一般调用方式有四种,点击手机图标、执行startActivityForResult(startActivity同理)、注册硬件监听启动、被action启动 以下是启动Activity的过程和startActivityForResult的启动(startActivity传一个默认的requestCode-1,最后仍然调用startActivityForResult方法,所以放在一起讲) 启动前的权限检查和准备工作 启动Activity的时候往往还需要进行权限检查,以查看其是否符合启动条件。查询不满足条件则返回,否则继续;检查Component是否存在,不满足返回,满足继续,启动startActivityLocked(caller,intent)方法,完成以下几件事 1、检查如果启动自己则直接返回 2、会加入INTENT_FLAG_FORWARD_RESULT标志,用于从C直接返回结果给A,使用方法A调用startActivityForResut->B调用startActivity->C调用setResult直接回到A 3、检查call-ActivityRecord是否存在且有指定权限 4、如果Ams中有IActivityController对象,则通知Controller进行相应控制 5、创建临时HistoryRecord对象,不一定加入mHistory列表,如不关闭则加入 6、检查是否允许切换activity,否则加入mPendingActivityLaunched 7、判断pendingActivity是否有要启动的activity,有则先执行,无则执行该intent 如想了解launchMode,请移步: Android基础之Activity launchMode详解 如想了解Intent,请移步: Intent中的四个重要属性——Action、Data、Category、Extras 以上是对intent的介绍,接下来会再介绍一下task,也就是如何启动,以什么样的规则启动和退出。以下均指launchFlag,标记均以FLAG_ACTIVITY_开头,介绍时会忽略,请注意一下;启动时会依次判断如下标识 1、NO_USER_ACTION:含义无用户交互;基本不用,主要防止一段时间后执行onUserLeaving方法;接下来如果立即启动,就把r.delay.Resume设为true 2、PREVIOUS_IS_TOP:含义上个intent是否位于栈顶,基本不用;然后为activity赋予权限加入缓存;此时区别于launchMode和launchFlag,前者指activity自己声明的启动方式,后者是明显启动者想让activity如何启动,能过intent设置,但两者有相通性 3、NEW_TASK、SINGLE_TASK、SINGLE_INSTANCE:使用这三者均不建议使用startActivityForResut,而只限于使用startActivity即r.resultTo=0,不需要回传数据的情况下;第1个是会新起一个task,即两个task之间最好不进行数据回传;2和3的相同在于,如果已经存在这样的task和component以及其他相同数据如intent,则均跳到相应栈中,不存在则声明一个新的task;不同点在于:2的task里可以包含多个activity,而3仅能包含一个;可能跟每个task内存大小有关,不同功用的activity可以申请不同的task使用,这一点也可以看上面“Android基本之Activity LaunchMode详解” 4、CLEAR_TOP、REORDER_TO_FRONT:前者如自己存在,则清除该栈上面的其他activity;后者仅把自己放在最上面;举例A1->A2->A3,前者启动A2则变成A1->A2,后者启动A2则变成A1->A3->A2 5、NO_HISTORY:不要保存自己,设置A3,则A1->A2->A3->A4,执行完还是A1->A2,皮之不存,毛将焉附。 一般情况下,以上如果调整栈的顺序,那么是可以执行的;如A、B、C三个栈,分别有2个activity,如果从C切换到B,那么是这样的A1->A2->B1->B2->C1->C2,调整后A1->A2->C1->C2->B1->B2。 系统还提供另外一种办法来设置activity的启动方式,那就是 1、android:clearTaskOnLaunch=true/false:是否清除task里的其他activity 2、android:finishOnTaskLaunch:是否关闭task中已有的此activity 3、android:allowTaskReparent:是否将自己带入启动的task中 4、android:alwaysRetainTaskState:是否由系统维护activity状态;一般应用在根activity,每次可以打开最后打开的页面 最近讲的几个东西之间的关系,画了一张图,如下所示 正式启动工作(主要流程均为ActivityThread执行-在attach时初始化) 一、暂停当前activity 1、判断该activity是否存在;然后执行onUserLeaving方法,避免此activity再与物理按键交互,如后退键 2、调用performPauseActivity(告知暂停而非超时等情况,将prev指向自己),先onSaveInstanceState,再执行onPause ·3、报告AMS暂停完毕,通过IPC调用AMS的completePauseActivity方法 二、调用resumeTopActivity方法: 1、如果mHistory有记录且直接启动,否则执行startHomeActivityLocked方法启动主界面程序; 2、系统处于睡眠状态或当前activity未被暂停,则停止; 3、从mStoppingActivities和mWarningVisibleActivities里移除目标对象; 4、将被停止的activity设置为不可见状态,通知activty或task切换 5、判断目录进程是否存在并且activityThread存活,否则执行startSpecificActivityLocked方法;如果进程不存在,则调用 Process类启动新进程,设置pid加入ProcessRecord,启动完之后再通知Ams启动目标Activity,至于启动Process的过程跟调用 暂停或启用activity的过程无异,仅仅参数发生变化而已,以及变量不同和意义不同。当然我们再讲一点,启动进程毕 竟是个重要流程,提取odex文件,前面几篇文章有过介绍,指已经优化过的dex文件,通过Service、Provider和 Broadcast加入引用,创建完成。 6、最终调用performLaunchActivity,执行attach,执行setTheme,几个on方法,拿到DecorView加入Wms,设 置可见。 接下来咱们讲讲停止工作:一般情况下是长时间不使用,或者应用内存紧张,或者启动时设置no_history才会执行,执行过程 而应用与上面生命周期无异,也就是设置不可见,加个mStoppingActivities,异步通知Ams停止,最后调用onStop方法等等, 关闭activity也同样如此;至于关闭的优先级前面似乎没讲,接下来会着重介绍一下。 Android系统如何管理自己内存的?同样可以预习一下,原理协同,再做补充。 一般情况下优化级分为-16到15级,而android默认仅使用0-15级,越小优先级越高,当前可见activity最高为0。 为什么会产生这个优化级,主要android采用的是关闭而不退出,退出而不清理的原则,主要为了二次加载更加快速; 其次是上面停止activity的原因。而这个规则由Ams加权得出,有这样几个变量:是否展示在当前页或是持久化 activity、相应的配合组件、剩余内存量、HOME进程、活跃activity数量和组件等,就像JVM的内存管理规则一样, 详见:Java高级之虚拟机垃圾回收机制 由于Ams无法预知内存的变动(OOM除外),因而采用这套机制:最先是空进程(无activity进程),其次是 activity,再次是前台的配合组件,如Service、Receiver、Provider,最后才是前台activity;而这些操作由OOM Killer 进程直接管理,关于activity回收需要满足以下几种情况: 1、finish操作或crash或anr,通过updateOomAdjLocked方法让其指定优先级,动态调整 2、如果运行的activity超过20个,必须是已经stop的、不可见的、非常驻进程 因而不合理的手机架构也会造成系统的崩溃。PS:持久化对象的在ProcessRecord的persistent变量是否为true LocalActivityManager存在于Activity内部,用来装载和管理activity以及各种状态,维持一个最低的内存消耗;核 心在于它使用UI线程的activityThread来装载指定的 activity;有同学可能会问task越少或者histroyRecord越小,内存 会占用越小吗?这个问题跟手机里装一个app和装n个app重量是否增加是一样的道理,但是越过限制以后(task允许) 内存占用确实会变大,直到出现OOM。 以下是按键方面的内容(锁屏下,基本均不操作) 1、后退键:Activity里监听到onBackPressed方法;一般执行的是finish动作,执行performDestroyActivity方法 2、Home键:Acitivty的onKey方法无法截取它,属于设计原因;Wms中使用PhoneWindowManager类中的 interceptKeyTi截取消息,发现是它,再执行launchHomeFromHotKey;2.0之后添加另一个Home键执行 startDockOrHome方法来监听硬件。 与普通的启动区别在于,能启动特殊的intent,方法在ContextImpl里。而长按震动、关闭所有窗口、会弹出LRT- lasted recent task,可以调用Ams的getRecentTask来取出,通过弹出的窗体的点击事件,进入相应的应用。 总结一下: 进程启动:1、内核创建进程数据结构,指出其地址总线 2、装载函数,读取代码,拿到数据总线 3、将程序指针指向目标地址入口 虚拟机启动:首次是从Zygote进程fork出来一个子进程,用来加载资源,然后启动SystemSever,用来监 控手机按键和触摸事件,以及管理Ams、Pms、Wms等,最终根据配置文件的HomeActivity,启动它。 Activity启动,然后触发init.rc文件执行main函数,启动一个ActivityThread 这就是所谓的UI线程,由Looper声明一个MessageQueue,接着创建Activity对象,初始化ViewRoot和token,用来分 发消息和接收转换为本地消息,为后面执行Activity的生命周期,创建PhoneWindow,执行attach方法,初始化内部组 件,根据Wms返回的消息,适时的执行生命周期,执行setContentView创建DecorView(View内部也有ViewRoot来设置View的各种属性),由Wms加入到窗口中,设置Visiable,加载结束。 注:每个应用仅有一个ActivityThread来异步处理内部事务,如果出错则App Crash;具体应用启动后,会创建ApplicationThread和ActivityThread,分别用来处理应用事务和Activity事务,并且创建MessageQueue,不停的轮循;AMS通过判断加载某个Activity和资源的加载情况,将消息从手机端通过Binder发给当前应用的ViewRoot对象,通过handler把消息放入消息队列,轮循出来的消息处理,即推动Activity的生命周期执行。 问题1:在应用运行过程中Window有多个吗?是的,每个Activity都会创建Window(见1)。 问题2:在应用运行过程中WindowManager有多个吗?Activity的WindowManager通过系统的WindowManager创建(见2、3、4) Activity类 final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, Window window) { attachBaseContext(context); mFragments.attachHost(null /*parent*/); 1、 mWindow = new PhoneWindow(this, window); mWindow.setWindowControllerCallback(this); mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); mWindow.getLayoutInflater().setPrivateFactory(this); if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) { mWindow.setSoftInputMode(info.softInputMode); } if (info.uiOptions != 0) { mWindow.setUiOptions(info.uiOptions); } mUiThread = Thread.currentThread(); mMainThread = aThread; mInstrumentation = instr; mToken = token; mIdent = ident; mApplication = application; mIntent = intent; mReferrer = referrer; mComponent = intent.getComponent(); mActivityInfo = info; mTitle = title; mParent = parent; mEmbeddedID = id; mLastNonConfigurationInstances = lastNonConfigurationInstances; if (voiceInteractor != null) { if (lastNonConfigurationInstances != null) { mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor; } else { mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this, Looper.myLooper()); } } 2、mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); if (mParent != null) { mWindow.setContainer(mParent.getWindow()); } 3、mWindowManager = mWindow.getWindowManager(); mCurrentConfig = config; }Window类: public void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) { mAppToken = appToken; mAppName = appName; mHardwareAccelerated = hardwareAccelerated || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false); if (wm == null) { wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); } 4、 mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this); }WindowManagerImpl类: * Provides low-level communication with the system window manager for * operations that are bound to a particular context, display or parent window. * Instances of this object are sensitive to the compatibility info associated * with the running application.提供一个跟系统WindowManager低等级的沟通方式,用于特别的Context、Display或者父Window。 WindowManagerGlobal类的解释同上。 public final class WindowManagerImpl implements WindowManager { private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); @Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); } @Override public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.updateViewLayout(view, params); } private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) { // Only use the default token if we don't have a parent window. if (mDefaultToken != null && mParentWindow == null) { if (!(params instanceof WindowManager.LayoutParams)) { throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); } // Only use the default token if we don't already have a token. final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; if (wparams.token == null) { wparams.token = mDefaultToken; } } } @Override public void removeView(View view) { mGlobal.removeView(view, false); }
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 讲到实战,就不得不拿两个例子来说明,本篇想拿的是应用最广泛的两个:Ams和Wms,一个管理activity,一个管理窗口,而前面我们已经讲了不少,本篇不再赘述。 关于Ams对activity的管理,无非这几个方面:启动哪个activity、物理按键对activity处理、内存骤减时activity的回收规则,以及暂停activity的一系列操作。 先说如何启动activity?有哪些知识点。 Ams调度activity,运行某activity需要通过Ams决定,一般情况下仅限允许均可执行;记录运行的页面,保存activity一些状态,以便下次启动更加快速,一般放在ActivityRecord,以前叫HistoryRecord;做进程管理,清楚运行哪些进程,各自的状态和信息集合 当然Ams除了管理activity,还会管理其他两个主要数据类,如Process和Task;Process同样也有个ProcessRecord用来记录进程运行信息和状态以及内部有哪些组件(指service、provider这样的),多个apk可以运行在同一进程中,可以通过android:process来设置,一般情况下还是不要,因为手机默认64M内存(不同分辨率不同),内存一般也仅够一个apk使用;而TaskRecord记录activity所在的栈的id、intent和activity的数量,用于对栈进行管理。 AMS常见的默认系统常量: 1、MAX_ACTIVITIES=20: 通过硬件设置,当前页面仅有一个activity存活,其他20个被缓存起来,超过则杀死优先低的 2、MAX_RECENT_TASKS=20:通过硬件设置,栈最多存储20个,超过后最先舍弃最早的栈 3、PAUSE_TIMEOUT=500:启动activity最长时间为500ms,否则关闭该页面,一般表现是“屏幕黑一下” 4、LAUNCH_TIMEOUT=10*1000:启动进程最长10s,否则舍弃 5、PROC_START_TIMEOUT=10*1000:启动进程后10s内必须报告AMS启动状态,否则AMS不承认此进程的存在 以上是最基础的变量,相信在多年开发中,大家对其引起的现象已经历历在目 其次在启动和暂停activity过程中,需要一些变量来保存状态,主要因为AMS是通过Service机制动作,比如 1、mPendingActivityLaunched:即将启动的activity列表 2、mStoppingActivities:A跳入B,B启动后A即被加入此队列,区别于mHistory的A跳入即加入 3、mHistory:所有后台activity,A跳入B即A被加入,或按Home键B被加入,回来按回退键则B被清除;finishing=true 4、mFinishingActivities:上面列表中执行完finish方法的activity,此时并未完全杀死等待被回收 5、mLRUActivities:最近使用的activity,包含mHistory里的activity和已经清除的activity 此外还有HistroyRecord记录mPausingActivity(record对象,记录执行onPause方法的activity),mResumeActivity、mFocusedActvity和mLastPausedActivity等。 上面讲完基础,接下来进程流程,讲讲如何启动Activity? 一般调用方式有四种,点击手机图标、执行startActivityForResult(startActivity同理)、注册硬件监听启动、被action启动 以下是启动Activity的过程和startActivityForResult的启动(startActivity传一个默认的requestCode-1,最后仍然调用startActivityForResult方法,所以放在一起讲) 启动前的权限检查和准备工作 启动Activity的时候往往还需要进行权限检查,以查看其是否符合启动条件。查询不满足条件则返回,否则继续;检查Component是否存在,不满足返回,满足继续,启动startActivityLocked(caller,intent)方法,完成以下几件事 1、检查如果启动自己则直接返回 2、会加入INTENT_FLAG_FORWARD_RESULT标志,用于从C直接返回结果给A,使用方法A调用startActivityForResut->B调用startActivity->C调用setResult直接回到A 3、检查call-ActivityRecord是否存在且有指定权限 4、如果Ams中有IActivityController对象,则通知Controller进行相应控制 5、创建临时HistoryRecord对象,不一定加入mHistory列表,如不关闭则加入 6、检查是否允许切换activity,否则加入mPendingActivityLaunched 7、判断pendingActivity是否有要启动的activity,有则先执行,无则执行该intent 如想了解launchMode,请移步: Android基础之Activity launchMode详解 如想了解Intent,请移步: Intent中的四个重要属性——Action、Data、Category、Extras 以上是对intent的介绍,接下来会再介绍一下task,也就是如何启动,以什么样的规则启动和退出。以下均指launchFlag,标记均以FLAG_ACTIVITY_开头,介绍时会忽略,请注意一下;启动时会依次判断如下标识 1、NO_USER_ACTION:含义无用户交互;基本不用,主要防止一段时间后执行onUserLeaving方法;接下来如果立即启动,就把r.delay.Resume设为true 2、PREVIOUS_IS_TOP:含义上个intent是否位于栈顶,基本不用;然后为activity赋予权限加入缓存;此时区别于launchMode和launchFlag,前者指activity自己声明的启动方式,后者是明显启动者想让activity如何启动,能过intent设置,但两者有相通性 3、NEW_TASK、SINGLE_TASK、SINGLE_INSTANCE:使用这三者均不建议使用startActivityForResut,而只限于使用startActivity即r.resultTo=0,不需要回传数据的情况下;第1个是会新起一个task,即两个task之间最好不进行数据回传;2和3的相同在于,如果已经存在这样的task和component以及其他相同数据如intent,则均跳到相应栈中,不存在则声明一个新的task;不同点在于:2的task里可以包含多个activity,而3仅能包含一个;可能跟每个task内存大小有关,不同功用的activity可以申请不同的task使用,这一点也可以看上面“Android基本之Activity LaunchMode详解” 4、CLEAR_TOP、REORDER_TO_FRONT:前者如自己存在,则清除该栈上面的其他activity;后者仅把自己放在最上面;举例A1->A2->A3,前者启动A2则变成A1->A2,后者启动A2则变成A1->A3->A2 5、NO_HISTORY:不要保存自己,设置A3,则A1->A2->A3->A4,执行完还是A1->A2,皮之不存,毛将焉附。 一般情况下,以上如果调整栈的顺序,那么是可以执行的;如A、B、C三个栈,分别有2个activity,如果从C切换到B,那么是这样的A1->A2->B1->B2->C1->C2,调整后A1->A2->C1->C2->B1->B2。 系统还提供另外一种办法来设置activity的启动方式,那就是 1、android:clearTaskOnLaunch=true/false:是否清除task里的其他activity 2、android:finishOnTaskLaunch:是否关闭task中已有的此activity 3、android:allowTaskReparent:是否将自己带入启动的task中 4、android:alwaysRetainTaskState:是否由系统维护activity状态;一般应用在根activity,每次可以打开最后打开的页面 最近讲的几个东西之间的关系,画了一张图,如下所示 正式启动工作(主要流程均为ActivityThread执行-在attach时初始化) 一、暂停当前activity 1、判断该activity是否存在;然后执行onUserLeaving方法,避免此activity再与物理按键交互,如后退键 2、调用performPauseActivity(告知暂停而非超时等情况,将prev指向自己),先onSaveInstanceState,再执行onPause ·3、报告AMS暂停完毕,通过IPC调用AMS的completePauseActivity方法 二、调用resumeTopActivity方法: 1、如果mHistory有记录且直接启动,否则执行startHomeActivityLocked方法启动主界面程序; 2、系统处于睡眠状态或当前activity未被暂停,则停止; 3、从mStoppingActivities和mWarningVisibleActivities里移除目标对象; 4、将被停止的activity设置为不可见状态,通知activty或task切换 5、判断目录进程是否存在并且activityThread存活,否则执行startSpecificActivityLocked方法;如果进程不存在,则调用 Process类启动新进程,设置pid加入ProcessRecord,启动完之后再通知Ams启动目标Activity,至于启动Process的过程跟调用 暂停或启用activity的过程无异,仅仅参数发生变化而已,以及变量不同和意义不同。当然我们再讲一点,启动进程毕 竟是个重要流程,提取odex文件,前面几篇文章有过介绍,指已经优化过的dex文件,通过Service、Provider和 Broadcast加入引用,创建完成。 6、最终调用performLaunchActivity,执行attach,执行setTheme,几个on方法,拿到DecorView加入Wms,设 置可见。 接下来咱们讲讲停止工作:一般情况下是长时间不使用,或者应用内存紧张,或者启动时设置no_history才会执行,执行过程 而应用与上面生命周期无异,也就是设置不可见,加个mStoppingActivities,异步通知Ams停止,最后调用onStop方法等等, 关闭activity也同样如此;至于关闭的优先级前面似乎没讲,接下来会着重介绍一下。 Android系统如何管理自己内存的?同样可以预习一下,原理协同,再做补充。 一般情况下优化级分为-16到15级,而android默认仅使用0-15级,越小优先级越高,当前可见activity最高为0。 为什么会产生这个优化级,主要android采用的是关闭而不退出,退出而不清理的原则,主要为了二次加载更加快速; 其次是上面停止activity的原因。而这个规则由Ams加权得出,有这样几个变量:是否展示在当前页或是持久化 activity、相应的配合组件、剩余内存量、HOME进程、活跃activity数量和组件等,就像JVM的内存管理规则一样, 详见:Java高级之虚拟机垃圾回收机制 由于Ams无法预知内存的变动(OOM除外),因而采用这套机制:最先是空进程(无activity进程),其次是 activity,再次是前台的配合组件,如Service、Receiver、Provider,最后才是前台activity;而这些操作由OOM Killer 进程直接管理,关于activity回收需要满足以下几种情况: 1、finish操作或crash或anr,通过updateOomAdjLocked方法让其指定优先级,动态调整 2、如果运行的activity超过20个,必须是已经stop的、不可见的、非常驻进程 因而不合理的手机架构也会造成系统的崩溃。PS:持久化对象的在ProcessRecord的persistent变量是否为true LocalActivityManager存在于Activity内部,用来装载和管理activity以及各种状态,维持一个最低的内存消耗;核 心在于它使用UI线程的activityThread来装载指定的 activity;有同学可能会问task越少或者histroyRecord越小,内存 会占用越小吗?这个问题跟手机里装一个app和装n个app重量是否增加是一样的道理,但是越过限制以后(task允许) 内存占用确实会变大,直到出现OOM。 以下是按键方面的内容(锁屏下,基本均不操作) 1、后退键:Activity里监听到onBackPressed方法;一般执行的是finish动作,执行performDestroyActivity方法 2、Home键:Acitivty的onKey方法无法截取它,属于设计原因;Wms中使用PhoneWindowManager类中的 interceptKeyTi截取消息,发现是它,再执行launchHomeFromHotKey;2.0之后添加另一个Home键执行 startDockOrHome方法来监听硬件。 与普通的启动区别在于,能启动特殊的intent,方法在ContextImpl里。而长按震动、关闭所有窗口、会弹出LRT- lasted recent task,可以调用Ams的getRecentTask来取出,通过弹出的窗体的点击事件,进入相应的应用。 总结一下: 进程启动:1、内核创建进程数据结构,指出其地址总线 2、装载函数,读取代码,拿到数据总线 3、将程序指针指向目标地址入口 虚拟机启动:首次是从Zygote进程fork出来一个子进程,用来加载资源,然后启动SystemSever,用来监 控手机按键和触摸事件,以及管理Ams、Pms、Wms等,最终根据配置文件的HomeActivity,启动它。 Activity启动,然后触发init.rc文件执行main函数,启动一个ActivityThread 这就是所谓的UI线程,由Looper声明一个MessageQueue,接着创建Activity对象,初始化ViewRoot和token,用来分 发消息和接收转换为本地消息,为后面执行Activity的生命周期,创建PhoneWindow,执行attach方法,初始化内部组 件,根据Wms返回的消息,适时的执行生命周期,执行setContentView创建DecorView(View内部也有ViewRoot来设置View的各种属性),由Wms加入到窗口中,设置Visiable,加载结束。 注:每个应用仅有一个ActivityThread来异步处理内部事务,如果出错则App Crash;具体应用启动后,会创建ApplicationThread和ActivityThread,分别用来处理应用事务和Activity事务,并且创建MessageQueue,不停的轮循;AMS通过判断加载某个Activity和资源的加载情况,将消息从手机端通过Binder发给当前应用的ViewRoot对象,通过handler把消息放入消息队列,轮循出来的消息处理,即推动Activity的生命周期执行。 问题1:在应用运行过程中Window有多个吗?是的,每个Activity都会创建Window(见1)。 问题2:在应用运行过程中WindowManager有多个吗?Activity的WindowManager通过系统的WindowManager创建(见2、3、4) Activity类 final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, Window window) { attachBaseContext(context); mFragments.attachHost(null /*parent*/); 1、 mWindow = new PhoneWindow(this, window); mWindow.setWindowControllerCallback(this); mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); mWindow.getLayoutInflater().setPrivateFactory(this); if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) { mWindow.setSoftInputMode(info.softInputMode); } if (info.uiOptions != 0) { mWindow.setUiOptions(info.uiOptions); } mUiThread = Thread.currentThread(); mMainThread = aThread; mInstrumentation = instr; mToken = token; mIdent = ident; mApplication = application; mIntent = intent; mReferrer = referrer; mComponent = intent.getComponent(); mActivityInfo = info; mTitle = title; mParent = parent; mEmbeddedID = id; mLastNonConfigurationInstances = lastNonConfigurationInstances; if (voiceInteractor != null) { if (lastNonConfigurationInstances != null) { mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor; } else { mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this, Looper.myLooper()); } } 2、mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); if (mParent != null) { mWindow.setContainer(mParent.getWindow()); } 3、mWindowManager = mWindow.getWindowManager(); mCurrentConfig = config; }Window类: public void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) { mAppToken = appToken; mAppName = appName; mHardwareAccelerated = hardwareAccelerated || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false); if (wm == null) { wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); } 4、 mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this); }WindowManagerImpl类: * Provides low-level communication with the system window manager for * operations that are bound to a particular context, display or parent window. * Instances of this object are sensitive to the compatibility info associated * with the running application.提供一个跟系统WindowManager低等级的沟通方式,用于特别的Context、Display或者父Window。 WindowManagerGlobal类的解释同上。 public final class WindowManagerImpl implements WindowManager { private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); @Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); } @Override public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.updateViewLayout(view, params); } private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) { // Only use the default token if we don't have a parent window. if (mDefaultToken != null && mParentWindow == null) { if (!(params instanceof WindowManager.LayoutParams)) { throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); } // Only use the default token if we don't already have a token. final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; if (wparams.token == null) { wparams.token = mDefaultToken; } } } @Override public void removeView(View view) { mGlobal.removeView(view, false); }
博客出自:http://blog.csdn.net/liuxian13183,转载注明出处! All Rights Reserved ! 最近有些时间,但QQ群问的问题比较多,不能一一解答,如果有价值的,请来此文下方评论提问, 我会及时回复,并将精华部分收录进来,用来帮助更多人,谢谢! 1、入门的同学可以先查看环境配置, Android初级第一讲---Android开发环境的配置 然后下载安卓demo研究, 安卓API的Demo 接着再看博客分类中的初级-中级-高级,到底层代码以及其他类目,其他不懂的,来博客下评论。 2、Php学习,http://phpbook.phpxy.com 3、答CsdnBlogger问-关于定时和后台服务问题:http://blog.csdn.net/reboot123/article/details/52597333 4、答CsdnBlogger问-关于安卓入行和开发问题:http://blog.csdn.net/reboot123/article/details/52604824 5、答CsdnBlogger问-关于职业发展和团队管理问题:http://blog.csdn.net/reboot123/article/details/52605165 6、答CsdnBlogger问-关于VR取代安卓问题:http://blog.csdn.net/reboot123/article/details/52605470 7、魔鬼经济学:http://freakonomics.com/archive/ 8、inary XML file line #8: Error inflating class fragment xml文件里fragment路径没错,找的方法看着也没错呀 分析:这种问题最让人着急,明明设计的没错,却编译失败 回答:往往是编译工具没有重新编译所导致,clean一下就解决问题,建议使用android studio比eclipse好用一些 结论:inflate出错。activity在加载前,会通过inflate工具把你的xml文件转化成View,设置到Activity里,实际上Window层里,这样用户才能看见你设置的界面。 可以这么理解:xml文件是View的配置文件;inflate是把View按xml配置重新画出来,跟拿人的基因克隆出一个人一样,因为有规则,所以可以直接拿到规则设定的结果。 9、加密传输,Sever向Client发送一个A数据(公钥证书和hash值),结果I拦截,将A换成B数据(公钥证书和hash值);Client向Sever发送数据,I拿私钥将数据解密,然后再用公钥A加密,发送给Sever;感觉完全没有安全感啊! 原因:上面忽略一个数据,就是随机码,其次是加密算法。密钥=公钥*随机码,数据=密钥*参数串,即使知道公钥,不知道私钥,原则上也是获取不到真正随机码的,而每次传输的数据,都必须经过密钥加密,Sever才会识别;如果每隔几分钟随机码变一下,那破解的人更加抓狂了(从Session失效机制联想到的)。所以https请求是安全的。 https加密的关键在于算法,RSA算法是一种非对称密码算法。所谓对称,就是指该算法需要一对密钥,使用其中一个加密,则需要用另一个才能解密;RSA是不严格的非对称加密算法,原因是用私钥可以解出公钥包含的数据。RSA的算法涉及三个参数,n、e1、e2。其中,n是两个大质数p、q的积,n的二进制表示时所占用的位数,就是所谓的密钥长度。e1和e2是一对相关的值,e1可以任意取,但要求e1与(p-1)*(q-1)互质;再选择e2,要求(e2*e1)mod((p-1)*(q-1))=1。(n,e1),(n,e2)就是密钥对。其中(n,e1)为公钥,(n,e2)为私钥。[1] RSA加解密的算法完全相同,设A为明文,B为密文,则:A=B^e2 mod n;B=A^e1 mod n;(公钥加密体制中,一般用公钥加密,私钥解密)e1和e2可以互换使用,即:A=B^e1 mod n;B=A^e2 mod n; 对于密钥的安全性,还可以设计N对密钥,每对给予一个id,如果一旦泄露,可以将id失效,则安全性可以得到保证。 更多:http://blog.jobbole.com/48369/ 10、如何获知listView滚动到最下方? 在listView的onScroll方法里,监听LastVisiblePosition等于items数量减1,即滚动到最下方 11、图片变色怎么处理? 如果要降低图片的识别度,可以用设图片ImageView的Alpha值即可从0到1,可以取小数,依次识别度升高 改变图片的颜色,如各种滤镜效果,主要通过改变RGB值来解决,一般通过矩阵来操作比较简单 更多:http://blog.csdn.net/lpjishu/article/details/45533557 12、面试大厂技巧:https://cn.100offer.com/blog/posts/223 彩蛋
博客出自:http://blog.csdn.net/liuxian13183,转载注明出处! All Rights Reserved ! 最近有些时间,但QQ群问的问题比较多,不能一一解答,如果有价值的,请来此文下方评论提问, 我会及时回复,并将精华部分收录进来,用来帮助更多人,谢谢! 1、入门的同学可以先查看环境配置, Android初级第一讲---Android开发环境的配置 然后下载安卓demo研究, 安卓API的Demo 接着再看博客分类中的初级-中级-高级,到底层代码以及其他类目,其他不懂的,来博客下评论。 2、Php学习,http://phpbook.phpxy.com 3、答CsdnBlogger问-关于定时和后台服务问题:http://blog.csdn.net/reboot123/article/details/52597333 4、答CsdnBlogger问-关于安卓入行和开发问题:http://blog.csdn.net/reboot123/article/details/52604824 5、答CsdnBlogger问-关于职业发展和团队管理问题:http://blog.csdn.net/reboot123/article/details/52605165 6、答CsdnBlogger问-关于VR取代安卓问题:http://blog.csdn.net/reboot123/article/details/52605470 7、魔鬼经济学:http://freakonomics.com/archive/ 8、inary XML file line #8: Error inflating class fragment xml文件里fragment路径没错,找的方法看着也没错呀 分析:这种问题最让人着急,明明设计的没错,却编译失败 回答:往往是编译工具没有重新编译所导致,clean一下就解决问题,建议使用android studio比eclipse好用一些 结论:inflate出错。activity在加载前,会通过inflate工具把你的xml文件转化成View,设置到Activity里,实际上Window层里,这样用户才能看见你设置的界面。 可以这么理解:xml文件是View的配置文件;inflate是把View按xml配置重新画出来,跟拿人的基因克隆出一个人一样,因为有规则,所以可以直接拿到规则设定的结果。 9、加密传输,Sever向Client发送一个A数据(公钥证书和hash值),结果I拦截,将A换成B数据(公钥证书和hash值);Client向Sever发送数据,I拿私钥将数据解密,然后再用公钥A加密,发送给Sever;感觉完全没有安全感啊! 原因:上面忽略一个数据,就是随机码,其次是加密算法。密钥=公钥*随机码,数据=密钥*参数串,即使知道公钥,不知道私钥,原则上也是获取不到真正随机码的,而每次传输的数据,都必须经过密钥加密,Sever才会识别;如果每隔几分钟随机码变一下,那破解的人更加抓狂了(从Session失效机制联想到的)。所以https请求是安全的。 https加密的关键在于算法,RSA算法是一种非对称密码算法。所谓对称,就是指该算法需要一对密钥,使用其中一个加密,则需要用另一个才能解密;RSA是不严格的非对称加密算法,原因是用私钥可以解出公钥包含的数据。RSA的算法涉及三个参数,n、e1、e2。其中,n是两个大质数p、q的积,n的二进制表示时所占用的位数,就是所谓的密钥长度。e1和e2是一对相关的值,e1可以任意取,但要求e1与(p-1)*(q-1)互质;再选择e2,要求(e2*e1)mod((p-1)*(q-1))=1。(n,e1),(n,e2)就是密钥对。其中(n,e1)为公钥,(n,e2)为私钥。[1] RSA加解密的算法完全相同,设A为明文,B为密文,则:A=B^e2 mod n;B=A^e1 mod n;(公钥加密体制中,一般用公钥加密,私钥解密)e1和e2可以互换使用,即:A=B^e1 mod n;B=A^e2 mod n; 对于密钥的安全性,还可以设计N对密钥,每对给予一个id,如果一旦泄露,可以将id失效,则安全性可以得到保证。 更多:http://blog.jobbole.com/48369/ 10、如何获知listView滚动到最下方? 在listView的onScroll方法里,监听LastVisiblePosition等于items数量减1,即滚动到最下方 11、图片变色怎么处理? 如果要降低图片的识别度,可以用设图片ImageView的Alpha值即可从0到1,可以取小数,依次识别度升高 改变图片的颜色,如各种滤镜效果,主要通过改变RGB值来解决,一般通过矩阵来操作比较简单 更多:http://blog.csdn.net/lpjishu/article/details/45533557 12、面试大厂技巧:https://cn.100offer.com/blog/posts/223 彩蛋
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 看本篇文章之前,建议先查看: Android源码剖析之Framework层基础版 前面讲了framework的整体层次和基础定义与服务,接下来我们讲讲窗口的创建,底层与应用层界面的交互和管理。 一、窗口的创建 上一篇我们讲了三种窗口的类型,本篇接着讲窗口的创建过程,拿Activity创建窗口为例 此步骤优先启动Launcher,即桌面App,包含功能:HomeScreen、Widgets、壁纸、Apps、快捷方式、 文件夹,区别于其他应用在于它的intent-filter有一个android.intent.category.home 有很多公司也做这块的优化,相当于Window的桌面不好看,自己再做一个,如Nova、TouchWiz,以及 以华为为代表的桌面美图屏保等程序,主要用来更好的展示和管理手机应用,通过开启新Task启动应用程 序,即常见的将Intent装入程序包名,通过AMS判断的方法先生成ActvityThread,再生成ApplicationT read,根据App资源的加载情况,由AMS判断并确定Activity的生命周期,由WMS将窗口绘制出来,两 者通过 Binder通信,即每个 Activity都有一个Token,可能是父类的,可能是自己的,反正它是Window 的,即ViewRoot。而Binder的优点,前两期也已经讲过,也可以看“Android进程通信方式”了解。 最终声明一个新的 ViewRootImpl来承载新的View,执行setView方法,将给mPanelParentWindowToken赋值(窗口 是接收用户消息的最小单元,窗口可能是Acitivity,也可能是一个单独的view,每个窗口都有一个单独的windowManager,实现类是LocalWindowManager,而Activity的window也会有父window存在, 仅限于tabActivity),然后重绘默认窗口会从配置文件中获取theme信息和其他底层设置信息,以及xml信息,默认构造一个decorView,所有应用类窗口的最顶层View都是DecorView 子窗口:如上所述,例tabActivity Dialog:如上所述,在show方法执行时,才完成窗口创建,Wms控制显示,避免加载过多资源,超过5秒出现ANR PopupWindow:子窗口,依赖父类,执行showAtLocation和showAsDropDown展示到界面上 Toast:系统窗口,一般不可由应用窗口创建(三者除外,type_toast、type_inputmethod、type_wallpaper), 因为PhoneWindowManger调用checkAddPermission方法里,将三者除去 只有实现PhoneWindow的窗口,默认会监听物理按键,否则需要自己再进行定义和实现 要添加一个手机屏幕上的按钮如腾讯手机管理的小火箭,需要系统窗口权限(一般要在Service中完成) <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"> 第二,需要指定一个合适的token。如果是acitivty的父类,则页面关闭就直接关闭浮层,mWindowToken也一样, 理论上添加一个手机页面token或空token(无父类),则直接展示而不受外界影响。 二、Framework的启动过程-系统启动过程 图片已经在上一篇放过,过程展示的也比较详细,本篇再讲一些细节。此时有必要再贴几张图,以Nexus5为例 可以结合上篇文章,查看手机系统展示出来的内存卡和SD卡上的文件,使用工具:Resource Explorer 在上图可以看到init.rc文件,linux系统启动时,主要的配置指令就是在它里面,包括把zygote程序加入系统 每个应用都是一个虚拟机 每个虚拟机都会加载系统早已加载的共享类和资源 虚拟机中有一些可执行文件,举例三种: 1、dalvikvm:dalvik目录下,创建一个虚拟机,并执行参数指定的Java类 2、dvz:dalvik目录下,从zygote进程孵化出一个新的进程,已然共享资源(PS:应用入口是ActivityThread) 3、app_process:frameworks目录下,使用dalvikvm来启动ZygoteInit.java和SystemServer.java两个类 而虚拟机执行的方法,基本都以c语言的方式实现,例获得进程id socket触发数据读操作有两种办法,一是监听某端口,二是监听某文件,android默认是后者 虚拟机栈大小默认512M,超过报栈溢出异常 preload-classes(预装app)封装在framework.jar中,使用classloader加载 preload-resources(预装资源)同样在上面jar里,使用preloadResources完成,子方法是drawable和color 进程启动包括3个过程 1、内核创建进程数据结构-代表要启动的123……顺序执行以及前后置条件 2、调用程序装载器,读取代码到预定地址 3、装载完毕,内核运行指定进程-如执行launcher加载桌面 启动SystemServer 执行ZygoteInit.java里的init方法初始化数据之后,即执行startSystemServer方法,关键代码有三 1、定义启动进程相关信息,用数据承载,并装载SystemServer.java 2、调用forkSystemServer从zygote进程孵化新进程 3、执行SystemServer.java类 SystemServer类中包括大量系统服务,可谓神经中枢,举例几个 PowerManagerService、ActivityManagerService、TelephonyRegistry(注册响应电话)、 PackageManagerService、AccountManagerService(联系人账户)、ContentService(contentProvider)、 BatteryService、LightsService(光感应器)、VibratorService(震动感应器)、AlarmManagerService、 WindowManagerService、BluetoothService、DevicePolicyManagerService(系统设置)、ClipboardService、 StatusBarManagerService(状态栏)、InputMethodManagerService、NetStatService(网络状态4G、wifi)、 NetworkManagerSerivce、ConnectivityService(网络连接)、LocationManagerService、SerachManagerService、 WallpaperManagerService、AudioSerive、BackupManagerService、AppWidgetService等;还可以将厂商开发的 软件服务写入底层,这也就是厂商固件 当然这些都常见而且重要,接下来我们先讲WMS和AMS,其他慢慢再讲;启动服务的方式有3种: 1、使用构造函数 2、单例模式 3、从服务类main方法开启 最后一句,启动Activity,因为所有的资源已经初始化,apk已经装载,其信息也已经注册,如果自启动一个应用的话,系统可以写入,通过Ams发一个intent请求包含CATEGORY_HOME这样的action,差异化来的请求系统默认页面。 举例: $ adb push E:\Door\3.4\Che\build\outputs\apk\che_manager@anzhi.apk /data/local/tmp/com.che.manager 代表将apk安装包从电脑push到手机的过程$ adb shell pm install -r "/data/local/tmp/com.che.manager" 代表将apk安装包安装(解压)到目录的过程 $ adb shell am start -n "com.che.manager/com.che.manager.activity.SplashActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHERI 代表启动apk包中主类的过程/InstantRun: Instant Run Runtime started. Android package is com.che.manager, real application class is com.che.manager.application.Application. 代表启动应用Application的过程 I/MultiDex: VM with version 2.1.0 has multidex supportI/MultiDex: installI/MultiDex: VM has multidex support, MultiDex support library is disabled.代表build已经支持multiDex,则不需要再导入com.android.support:multidex包 注:一个新进程的启动,是由Ams通过IPC调用,向SystemSever请求,调用Zygote进程fork出一个新进程。
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 关于Framework,就是应用层底下的控制层,离应用层最近,总想找个机会,写写WindowMangerService和ActivityManagerService(注意非控件,而是指一类服务)以及其他一些东西,对底层做一个更为全面的认识。而很早以前,写过一篇文章,来简述Android系统-" Android高级之系统介绍",同样今天我们在讲Framework层时也会再对系统做一个回顾;下图是我对本节内容的一个基本介绍。 PS:W类是ViewRoot的一个内部类,ViewRoot最大作用就是把IPC调用转为本地调用。 HistoryRecord-每个Acitivty都会有一个,用来管理和记录Activity,是一个Binder对象 ViewRoot-实现View和WindowManger之间的协议,是View Hierarchy的最顶层 PhoneWindow-其中有autoManger和keyguardManager的实现对象 一、窗口 就着上图,我们会对每一条做进一步的解说(注意上图大多是包含关系,少数是关联关系,请区别对待),上图对Framework简单做了描述;同时科普一下什么叫窗口,窗口非指window类,而是指所有使用windowmanger将 其展示在用户面前的控件,如toast、activity、menu等,而这些界面通过设置window的callback来监听到wms传给view对象的信息,如手势操作。而窗口类型基本可以分为3种: 1、系统窗口,不需要父窗口-可以指定2000-2999层 2、子窗口,依赖父窗口-1000-1999层 如Toast 3、应用窗口,对应activity-小于99层 如Activity 窗口可以说是View,而wms不直接跟view沟通,而是通过实现IWindow的ViewRoot.W类,然后再传给view。 关于Context,上下文引用,项目中使用的还比较多,是一个场景,用来配合上下文操作;一个应用中context的数量=service个数+activity个数+application个数,原因它们都继承自ContextWraper,而它继承自context。 二、linux文件系统 由于android系统基于linux,就先讲一下linux基础,文件系统通常有3个特点: 1、由文件和目录组成,占据一定存储空间 2、具有读、写、执行权限,可以复制和移动 3、多个文件系统可以并列,且类型可以不同,如FAT16和NTFS 主要的文件目录有以下几种-与android系统类比: 1、bin,存放用户级二进制工具-相当于android系统的acct目录 2、boot,内核镜像文件,由bootloader装载-firmware 3、dev,各种文件系统如打印机等-相当于android系统的dev目录+storage+mnt/sdcard 4、etc,配置文件区-相当于android系统的config目录 5、home,用户工作目录-data/user 6、lib,系统运行时库的存放地-data/app-lib 7、opt,存放系统程序-data/app-private 8、proc,系统级如内核和进程所在文件-proc目录 9、root,管理员工作目录-root目录 10、sbin,管理员的二进制工具-sbin目录 11、sys,驱动对应的系统文件如固件、内核、电量等-sys目录+system目录 12、usr,应用程序安装区-data/app 13、var,调试信息等-data/anr等 因此从上面来以看出,其实操作系统都是由文件组成,外加一些硬件感应设备。但上面介绍的依然不全面,因为android是一层套一层,资源是总体一致,大体分散的结构。同时上面会涉及到进程pid,值为100以内是系统进程,1000以内是root进程,1000以上是用户进程。讲完目录,咱们再讲讲命令: 1、man,查询某命令的意思 2、ls,列出当前目录下所有文件及文件夹信息 3、find,用名字查找文件信息 4、grep,查询文件中的字符串信息 5、cat,打开文件 6、chmod,指定权限,ugo指user(自身)、group(组)、other(其他),权限有r(读1)w(写2)x(执行4),指定权限有两种方式如chmod ug+x(给予当前用户和某群组执行的权限),chmod 777(给予三者所有权限,原因请看上一行) 7、ps和kill,ps列出当前所有进程,kill杀死某进程 8、export,用于设置变量作用于全局 9、mount和unmount,加载和卸载文件系统 好在用过linux操作系统工作过一段时间,对后来做Android开发,起到很大的帮助,上面介绍的是一些常用命令,有兴趣的可以安装一个linux系统来用,之前有一个同事使用ubuntu来编译so,而我当年用的是小红帽rethat。 三、linux启动过程 下面简单讲一个linux启动过程,其实Dalvik虚拟机也是类似 上图少说一点,在CPU运行之前,要把磁盘和其他内存启动,这样才能保证系统正式开始,因为所有系统均为文件,加载文件的硬盘不启,系统如何能被启动? 四、异步信息系统 在Android系统中,用的最多的设计模式就是handler+looper+messageQueue,无论在系统层还是在应用层, 之后我们会再讲到都被用烂了,异步线程表现为:开启后无限循环,遍历数据,如果为空则等待,直到下次数据不 为空时,使用场景有二 1、任务要常驻 2、任务需要根据消息来做不同操作 使用方法如上,解释几点,上面的数据指messegeQueue属于队列LILO,读/写数据时会加锁;如何应用呢, Handler handler =new Handler(); 首先创建handler时,一般需要在UI线程中(一般你也没必要在子线程重写 handler),获得一个UI线程的looper对象;looper对象通过prepare方法(仅一次)准备一个MessageQueue (系统唯一),调用loop方法一直分发消息;MessageQueue可以定义消息处理时刻,否则先进先出, 通过next和enquenceMessage方法来取和加入消息,处于wait状态则唤醒,若无消息则挂起。 五、Binder 再讲一个知识点Binder,然后结束本篇。 Binder工作在linux层面,是一小段内存,属于驱动,主要用来解决IPC调用,应用框架包括3部分 1、服务端接口-Binder对象,收到消息即启动隐藏线程,执行onTransact函数初始化;向客户端发送消息时, 挂起当前线程。 2、Binder驱动-创造mRemote的Binder对象,重载以下方法 1)以线程间消息通信模式,向服务端发送参数 2)挂起客户端线程,等待服务端返回处理结果,并通知客户端 3)接收服务端通知,继续执行客户端线程 3、客户端接口-向客户端发送消息,挂起当前线程。 Binder服务的设计原则是,开放ServiceManger接口给外界,关于具体的操作由底层去统一执行,不暴露出来, 这也是保证框架稳定、逻辑正确的重要方式,使不同管理类与底层实现可以分离解耦。 另一篇关于Binder介绍,最下面:http://blog.csdn.net/reboot123/article/details/52370416 下面这篇文章底层就是使用binder进行通信 Android高级第十讲之AIDL与JNI 六、token token原意指口令,在这里指“身份证”,代表唯一性和代理性;一般token为IBind的实现类,即使View.Attach Info中也是用IBind实现类来进行IPC调用。应用窗口一般都有token,而window可能没有,为什么?window可以不 对应此窗口存在。 应用窗口的token:一般最初跟window一样最终指向DecorView的W类,初始值是window的mAppToken,指应用 HistoryRecord。Activity的token起始是HistoryRecord,最终指向此W类即mAppToken,与window相同。 子窗口的token:是父窗口view的W类,即mPanelParentWindowToken 系统窗口无token 因此view中会有window的token,也会有父窗口的token,而每个应用窗口随activity诞生,均会有一个Activity Thread陪伴。 PopupWindow上套PopupWindow,报错:unable to add window ,is your activity running ? 经查在windowManager执行addView操作时出错 @Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); } WindowManagerGlobal public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { if (view == null) { throw new IllegalArgumentException("view must not be null"); } if (display == null) { throw new IllegalArgumentException("display must not be null"); } if (!(params instanceof WindowManager.LayoutParams)) { throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); } // do this last because it fires off messages to start doing things try { root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up. synchronized (mLock) { final int index = findViewLocked(view, false); if (index >= 0) { removeViewLocked(index, true); } } throw e; } }ViewRootImpl出错出现ADD_BAD_APP_TOKEN或ADD_BAD_SUBWINDOW_TOKEN,要查WindowSession.addToDisplay /** * We have one child */ public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { mView = view; int res; /* = WindowManagerImpl.ADD_OKAY; */ // Schedule the first layout -before- adding to the window // manager, to make sure we do the relayout before receiving // any other events from the system. requestLayout(); if ((mWindowAttributes.inputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) { mInputChannel = new InputChannel(); } mForceDecorViewVisibility = (mWindowAttributes.privateFlags & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0; try { mOrigWindowType = mWindowAttributes.type; mAttachInfo.mRecomputeGlobalAttributes = true; collectViewAttributes(); res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel); } catch (RemoteException e) { mAdded = false; mView = null; mAttachInfo.mRootView = null; mInputChannel = null; mFallbackEventHandler.setView(null); unscheduleTraversals(); setAccessibilityFocus(null, null); throw new RuntimeException("Adding window failed", e); } finally { if (restore) { attrs.restore(); } } if (mTranslator != null) { mTranslator.translateRectInScreenToAppWindow(mAttachInfo.mContentInsets); } mPendingOverscanInsets.set(0, 0, 0, 0); mPendingContentInsets.set(mAttachInfo.mContentInsets); mPendingStableInsets.set(mAttachInfo.mStableInsets); mPendingVisibleInsets.set(0, 0, 0, 0); mAttachInfo.mAlwaysConsumeNavBar = (res & WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR) != 0; mPendingAlwaysConsumeNavBar = mAttachInfo.mAlwaysConsumeNavBar; if (DEBUG_LAYOUT) Log.v(mTag, "Added window " + mWindow); if (res < WindowManagerGlobal.ADD_OKAY) { mAttachInfo.mRootView = null; mAdded = false; mFallbackEventHandler.setView(null); unscheduleTraversals(); setAccessibilityFocus(null, null); switch (res) { case WindowManagerGlobal.ADD_BAD_APP_TOKEN: case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN: throw new WindowManager.BadTokenException( "Unable to add window -- token " + attrs.token + " is not valid; is your activity running?"); case WindowManagerGlobal.ADD_NOT_APP_TOKEN: throw new WindowManager.BadTokenException( "Unable to add window -- token " + attrs.token + " is not for an application"); case WindowManagerGlobal.ADD_APP_EXITING: throw new WindowManager.BadTokenException( "Unable to add window -- app for token " + attrs.token + " is exiting"); case WindowManagerGlobal.ADD_DUPLICATE_ADD: throw new WindowManager.BadTokenException( "Unable to add window -- window " + mWindow + " has already been added"); case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED: // Silently ignore -- we would have just removed it // right away, anyway. return; case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON: throw new WindowManager.BadTokenException("Unable to add window " + mWindow + " -- another window of type " + mWindowAttributes.type + " already exists"); case WindowManagerGlobal.ADD_PERMISSION_DENIED: throw new WindowManager.BadTokenException("Unable to add window " + mWindow + " -- permission denied for window type " + mWindowAttributes.type); case WindowManagerGlobal.ADD_INVALID_DISPLAY: throw new WindowManager.InvalidDisplayException("Unable to add window " + mWindow + " -- the specified display can not be found"); case WindowManagerGlobal.ADD_INVALID_TYPE: throw new WindowManager.InvalidDisplayException("Unable to add window " + mWindow + " -- the specified window type " + mWindowAttributes.type + " is not valid"); } throw new RuntimeException( "Unable to add window -- unknown error code " + res); } } } WindowSession @Override public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, InputChannel outInputChannel) { return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outContentInsets, outStableInsets, outInputChannel); } WindowManagerService public int addWindow(Session session, IWindow client, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, InputChannel outInputChannel) { int[] appOp = new int[1]; int res = mPolicy.checkAddPermission(attrs, appOp); if (res != WindowManagerGlobal.ADD_OKAY) { return res; } boolean reportNewConfig = false; WindowState attachedWindow = null; long origId; final int type = attrs.type; synchronized(mWindowMap) { if (!mDisplayReady) { throw new IllegalStateException("Display has not been initialialized"); } final DisplayContent displayContent = getDisplayContentLocked(displayId); if (displayContent == null) { Slog.w(TAG_WM, "Attempted to add window to a display that does not exist: " + displayId + ". Aborting."); return WindowManagerGlobal.ADD_INVALID_DISPLAY; } if (!displayContent.hasAccess(session.mUid)) { Slog.w(TAG_WM, "Attempted to add window to a display for which the application " + "does not have access: " + displayId + ". Aborting."); return WindowManagerGlobal.ADD_INVALID_DISPLAY; } if (mWindowMap.containsKey(client.asBinder())) { Slog.w(TAG_WM, "Window " + client + " is already added"); return WindowManagerGlobal.ADD_DUPLICATE_ADD; } if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) { attachedWindow = windowForClientLocked(null, attrs.token, false); if (attachedWindow == null) { Slog.w(TAG_WM, "Attempted to add window with token that is not a window: " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN; } if (attachedWindow.mAttrs.type >= FIRST_SUB_WINDOW && attachedWindow.mAttrs.type <= LAST_SUB_WINDOW) { Slog.w(TAG_WM, "Attempted to add window with token that is a sub-window: " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN; } } if (type == TYPE_PRIVATE_PRESENTATION && !displayContent.isPrivate()) { Slog.w(TAG_WM, "Attempted to add private presentation window to a non-private display. Aborting."); return WindowManagerGlobal.ADD_PERMISSION_DENIED; } boolean addToken = false; WindowToken token = mTokenMap.get(attrs.token); AppWindowToken atoken = null; if (token == null) { if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) { Slog.w(TAG_WM, "Attempted to add application window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } if (type == TYPE_INPUT_METHOD) { Slog.w(TAG_WM, "Attempted to add input method window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } if (type == TYPE_VOICE_INTERACTION) { Slog.w(TAG_WM, "Attempted to add voice interaction window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } if (type == TYPE_WALLPAPER) { Slog.w(TAG_WM, "Attempted to add wallpaper window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } if (type == TYPE_DREAM) { Slog.w(TAG_WM, "Attempted to add Dream window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } if (type == TYPE_QS_DIALOG) { Slog.w(TAG_WM, "Attempted to add QS dialog window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } if (type == TYPE_ACCESSIBILITY_OVERLAY) { Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with unknown token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } token = new WindowToken(this, attrs.token, -1, false); addToken = true; } else if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) { atoken = token.appWindowToken; if (atoken == null) { Slog.w(TAG_WM, "Attempted to add window with non-application token " + token + ". Aborting."); return WindowManagerGlobal.ADD_NOT_APP_TOKEN; } else if (atoken.removed) { Slog.w(TAG_WM, "Attempted to add window with exiting application token " + token + ". Aborting."); return WindowManagerGlobal.ADD_APP_EXITING; } if (type == TYPE_APPLICATION_STARTING && atoken.firstWindowDrawn) { // No need for this guy! if (DEBUG_STARTING_WINDOW || localLOGV) Slog.v( TAG_WM, "**** NO NEED TO START: " + attrs.getTitle()); return WindowManagerGlobal.ADD_STARTING_NOT_NEEDED; } } else if (type == TYPE_INPUT_METHOD) { if (token.windowType != TYPE_INPUT_METHOD) { Slog.w(TAG_WM, "Attempted to add input method window with bad token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } } else if (type == TYPE_VOICE_INTERACTION) { if (token.windowType != TYPE_VOICE_INTERACTION) { Slog.w(TAG_WM, "Attempted to add voice interaction window with bad token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } } else if (type == TYPE_WALLPAPER) { if (token.windowType != TYPE_WALLPAPER) { Slog.w(TAG_WM, "Attempted to add wallpaper window with bad token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } } else if (type == TYPE_DREAM) { if (token.windowType != TYPE_DREAM) { Slog.w(TAG_WM, "Attempted to add Dream window with bad token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } } else if (type == TYPE_ACCESSIBILITY_OVERLAY) { if (token.windowType != TYPE_ACCESSIBILITY_OVERLAY) { Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with bad token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } } else if (type == TYPE_QS_DIALOG) { if (token.windowType != TYPE_QS_DIALOG) { Slog.w(TAG_WM, "Attempted to add QS dialog window with bad token " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_APP_TOKEN; } } else if (token.appWindowToken != null) { Slog.w(TAG_WM, "Non-null appWindowToken for system window of type=" + type); // It is not valid to use an app token with other system types; we will // instead make a new token for it (as if null had been passed in for the token). attrs.token = null; token = new WindowToken(this, null, -1, false); addToken = true; } if (addToken) { mTokenMap.put(attrs.token, token); } win.attach(); mWindowMap.put(client.asBinder(), win); if (win.mAppOp != AppOpsManager.OP_NONE) { int startOpResult = mAppOps.startOpNoThrow(win.mAppOp, win.getOwningUid(), win.getOwningPackage()); if ((startOpResult != AppOpsManager.MODE_ALLOWED) && (startOpResult != AppOpsManager.MODE_DEFAULT)) { win.setAppOpVisibilityLw(false); } } Binder.restoreCallingIdentity(origId); return res; }如果加入成功(mWindowMap.put(attr.token,token)),则子View与父View建立连接关系。 查到mTokenMap.get(attr.token)为null,同时又是子窗口,因此报错。 至于为什么是null,继续 回到PopupWindow public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) { if (isShowing() || mContentView == null) { return; } TransitionManager.endTransitions(mDecorView); attachToAnchor(anchor, xoff, yoff, gravity); mIsShowing = true; mIsDropdown = true; final WindowManager.LayoutParams p = createPopupLayoutParams(anchor.getWindowToken()); preparePopup(p); final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff, p.width, p.height, gravity); updateAboveAnchor(aboveAnchor); p.accessibilityIdOfAnchor = (anchor != null) ? anchor.getAccessibilityViewId() : -1; invokePopup(p); } 可以看到,二次添加的PopupWidow,使用一次添加的PopupWindow对象的token,而此token为 private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; private WindowManager.LayoutParams createPopupLayoutParams(IBinder token) { final WindowManager.LayoutParams p = new WindowManager.LayoutParams(); // These gravity settings put the view at the top left corner of the // screen. The view is then positioned to the appropriate location by // setting the x and y offsets to match the anchor's bottom-left // corner. p.gravity = computeGravity(); p.flags = computeFlags(p.flags); p.type = mWindowLayoutType; p.token = token; p.softInputMode = mSoftInputMode; p.windowAnimations = computeAnimationResource(); return p; } 但在WindowManagerService里,PopupWindow依赖show的控件是传过来的,而一次PopupWindow的type刚好等于 /** * Window type: a panel on top of an application window. These windows * appear on top of their attached window. */ public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;所在WindowManagerService不允许在子窗口上创建子窗口。 if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) { attachedWindow = windowForClientLocked(null, attrs.token, false); if (attachedWindow == null) { Slog.w(TAG_WM, "Attempted to add window with token that is not a window: " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN; } if (attachedWindow.mAttrs.type >= FIRST_SUB_WINDOW && attachedWindow.mAttrs.type <= LAST_SUB_WINDOW) { Slog.w(TAG_WM, "Attempted to add window with token that is a sub-window: " + attrs.token + ". Aborting."); return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN; } } 完。 补充:Dialog则无此限制 public void show() { if (mShowing) { if (mDecor != null) { if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) { mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR); } mDecor.setVisibility(View.VISIBLE); } return; } WindowManager.LayoutParams l = mWindow.getAttributes(); if ((l.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) { WindowManager.LayoutParams nl = new WindowManager.LayoutParams(); nl.copyFrom(l); nl.softInputMode |= WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; l = nl; } mWindowManager.addView(mDecor, l); mShowing = true; sendShowMessage(); } Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) { final Window w = new PhoneWindow(mContext); mWindow = w; } PhoneWindow public final WindowManager.LayoutParams getAttributes() { return mWindowAttributes; } private final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams(); public LayoutParams() { super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); type = TYPE_APPLICATION; format = PixelFormat.OPAQUE; } public static final int TYPE_APPLICATION = 2; WindowManagerService要求大于等1000,小于等于1999才报ADD_BAD_SUB_WINDOW。 因此Dialog无此限制。 Android的安装过程:http://www.2cto.com/kf/201402/280725.html Binder能支持跨进程通信的原因,是它实现IBinder接口,系统定义实现此接口即赋予进程通信的功能。 优势:做数据拷贝只用一次,则Pipe、Socket都需要两次;其次安全性高,不会像Socket会暴露地址,被人替换; 通信机制:Service向ServiceManager注册,得到虚拟的Uid和Pid,使用Binder通信;Client向ServiceManager请求,得到虚拟的Uid和Pid,以及目标对象的Proxy,底层通过硬件协议传输,使用Binder通讯。 通信时Client手持Proxy,ServiceManger进行转换,调用到Service。三者运行在三个独立进程中。Client/Sever全双工,互为Sever/Client 实际案例: Vivo7.0以上手机,无法弹出自定义Toast(WindowManager通过addView方式,再配合动画),由于Android手机系统权限逐步收紧,导致无法成功申请的“SYSTEM_ALERT_WINDOW”权限。 解决思路:先看权限是否被收回,其次看使用系统Toast是否可行,最后尝试降级使用WindowManager。 解决办法,降低WindowManager.LayoutParam的flag等级,使用2000以下的等级,比如LAST_SUB_WINDOW(次级window1999)
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 从Android爆发以后,自定义的控件如EditTextWithDelete、ActionBar、PullToRresh逐步进入开发者的视野,想起11年的时候,基本项目中使用的UI还全都是Android提供的基础控件,一点的动画+布局,下载和网络请求都是用HttpClient,图片加载当然也是下载后再使用,当时的程序资源可没有现在这么丰富,看看Github上开源的项目,现在的程序员应该感到幸福。 项目开发从程序上来讲,万古不变两大事情,一是网络通信框架,二是图片加载框架,有关网络框架上一篇已经介绍了async-http和okhttp,而其他如volly同时拥有网络请求和图片加载两个框架,很多人图省事就一次性使用了,当然facebook自己的开源框架也是写的非常不错,接下来再一一介绍;先贴一张11年我们自己写的imageloader 补充一下,上面框架还支持给图片设置像素点占位大小;看到这么多功能,对于现在的项目基本满足要求,缺点是:网络请求直接使用未封装的HttpUrlConnection,以及图片选项设置麻烦一些(如可以设置磁盘和内存最大宽高),不支持webp和gif。再看看其他几种图片加载框架的异同 Fresco,facebook出品,最大的优势在于可展示加载过程,即加载进度、加载前图片、加载中图片、加载后图片、加载失败图片等,还可以设置图片的形状;并且提供加载Gif、Webp图片的方法。 Picasso,默认Config为8888,如果加载图片不成功,需要改565,原因内存不足。 Glide是升级版本的piccaso:1、支持跟fragment和activity生命周期绑定;2、同时可以直接剪切图片;3、网络请求直接使用okhttp;4、相对imageloader,API相对简单。默认加载RGB565格式图片,缺点是没有直接从fileCaceh取文件的接口、不支持webp、防止图片错乱只能设置setTag(int,String)。 volly,基于老的imageloader又做了次封装,差别不是太大,功能弱化一些,同glide支持图片动画,但它同时支持gif和webp。不适用于直播和较大图片下载,因为它解析后的缓存一般直接放在内存中。 https://developer.android.com/training/volley/index.html 而后面这几种框架,都是在imageloader兴起之后出现的,都有加载过程,在我看来也仅有fresco和glide是真正写出了跟原框架不同的东西。 public class ImageDownloader { private static ImageDownloader instance = null; private static File cacheDir; public static Map<String, SoftReference<Bitmap>> bitMapCache = new HashMap<String, SoftReference<Bitmap>>(); public static ImageDownloader getInstance() { if (instance == null) { instance = new ImageDownloader(); } return instance; } private ImageDownloader() { // Find the dir to save cached images if (android.os.Environment.getExternalStorageState().equals( android.os.Environment.MEDIA_MOUNTED)) cacheDir = new File( android.os.Environment.getExternalStorageDirectory(), "WholeMag"); else cacheDir = WholeMagApplication.getInstance().getCacheDir(); if (!cacheDir.exists()) cacheDir.mkdirs(); } public void download(String actName, String url, ImageView imageView) { BitmapDownloaderTask task = new BitmapDownloaderTask(imageView, actName); task.execute(url); // return task.doInBackground(url); } class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> { // private String url; // private boolean flag; private final WeakReference<ImageView> imageViewReference; // 使用WeakReference解决内存问题 private String actName; public BitmapDownloaderTask(ImageView imageView, String actName) { imageViewReference = new WeakReference<ImageView>(imageView); this.actName = actName; } @Override protected Bitmap doInBackground(String... params) { // 实际的下载线程,内部其实是concurrent线程,所以不会阻塞 Bitmap rebmp = getLocalBitmap(params[0], actName); if (rebmp == null) rebmp = downloadBitmap(params[0], actName); if (rebmp == null) { doInBackground(params[0]); } return rebmp; } @Override protected void onPostExecute(Bitmap bitmap) { // 下载完后执行的 if (isCancelled()) { bitmap = null; } if (imageViewReference != null) { ImageView imageView = imageViewReference.get(); if (imageView != null) { imageView.setDrawingCacheEnabled(true); Bitmap temp = imageView.getDrawingCache(); imageView.setDrawingCacheEnabled(false); if (temp != null) { temp.recycle(); } double widthX = (float) WholeMagDatas.getDeviceWidth() / bitmap.getWidth(); // 图片宽度拉伸比例 int bitmapHight = bitmap.getHeight();// 图片高度 imageView.setImageBitmap(bitmap); // 下载完设置imageview为刚才下载的bitmap对象 if(actName.equals(AppData.NEWS_DETAIL_ACT)){ FrameLayout.LayoutParams ll = new FrameLayout.LayoutParams( android.view.ViewGroup.LayoutParams.FILL_PARENT, (int) (bitmapHight * widthX), Gravity.CENTER); imageView.setLayoutParams(ll); } AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);// 创建一个AlphaAnimation对象 alphaAnimation.setDuration(500);// 设置动画执行的时间(单位:毫秒) imageView.startAnimation(alphaAnimation); } } } } static Bitmap getLocalBitmap(String url, String actName) { if (bitMapCache.containsKey(url)) { return bitMapCache.get(url).get(); } // String tmp = url; // String first = url.substring(url.lastIndexOf("/") + 1); // tmp = tmp.substring(0, tmp.lastIndexOf("/")); // String second = tmp.substring(tmp.lastIndexOf("/") + 1); // tmp = tmp.substring(0, tmp.lastIndexOf("/")); // String third = tmp.substring(tmp.lastIndexOf("/") + 1); // String filename = third + second + first; // File f = new File(cacheDir, filename); File f = Tools.getFile(actName, url); InputStream inputStream = null; try { // decode image size BitmapFactory.Options o = new BitmapFactory.Options(); o.inPreferredConfig = Bitmap.Config.RGB_565; o.inDither = false; o.inPurgeable = true; // o.inTempStorage = new byte[12 * 1024]; inputStream = new FileInputStream(f); // Bitmap bitmap = BitmapFactory.decodeFile(f.getAbsolutePath()); Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, o); bitMapCache.put(url, new SoftReference<Bitmap>(bitmap)); return bitmap; } catch (Exception e) { } finally { if (null != inputStream) { try { inputStream.close(); } catch (Exception ex) { } } } return null; } static Bitmap downloadBitmap(String url, String actName) { final AndroidHttpClient client = AndroidHttpClient.newInstance("linux"); final HttpGet getRequest = new HttpGet(url); try { HttpResponse response = client.execute(getRequest); final int statusCode = response.getStatusLine().getStatusCode(); if (statusCode != HttpStatus.SC_OK) { // Log.e("cwjDebug", "Error " + statusCode // + " while retrieving bitmap from " + url); return null; } final HttpEntity entity = response.getEntity(); if (entity != null) { // String tmp = url; // String first = url.substring(url.lastIndexOf("/") + 1); // tmp = tmp.substring(0, tmp.lastIndexOf("/")); // String second = tmp.substring(tmp.lastIndexOf("/") + 1); // tmp = tmp.substring(0, tmp.lastIndexOf("/")); // String third = tmp.substring(tmp.lastIndexOf("/") + 1); // String filename = third + second + first; // File f = new File(cacheDir, filename); File f = Tools.getFile(actName, url); OutputStream os = new FileOutputStream(f); InputStream inputStream = null; try { inputStream = entity.getContent(); BitmapFactory.Options o = new BitmapFactory.Options(); o.inPreferredConfig = Bitmap.Config.RGB_565; o.inDither = false; o.inPurgeable = true; final Bitmap bitmap = BitmapFactory.decodeStream( inputStream, null, o); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os); bitMapCache.put(url, new SoftReference<Bitmap>(bitmap)); return bitmap; } finally { if (inputStream != null) { inputStream.close(); } if (null != os) { os.close(); } entity.consumeContent(); } } } catch (Exception e) { getRequest.abort(); } finally { if (client != null) { client.close(); } } return null; }} 当年为了防止图片爆掉出现OOM,使用了软引用,觉得还不错;图片加载的基本原理就是,把url+imageview抛过来,然后开启异步线程加载,根据获取的byte流decode成bitmap,最后在UI线程将图片加载到imageview上;也没有说做本地缓存,仅做了应用缓存;没有对图片进行压缩或者设置格式,使占用内存更小,展示也更合理;LruCache基本原理跟上面使用软引用的过程差不多,只不过多限制了图片占用内存的大小,计算图片使用的频率,对应用层、SD卡层均做了封装。上面介绍完,相信你也对图片加载有个大概的轮廓,我们拿开源的imageloader为例,来讲讲图片加载框架的一些细节 public final class ImageLoaderConfiguration { final Resources resources;//主要给图片设计宽高时,获得屏幕宽高使用 final int maxImageWidthForMemoryCache;//内存中最大的图片宽度 final int maxImageHeightForMemoryCache;//内存中最大的图片高度 final int maxImageWidthForDiskCache;//SD卡中最大的图片宽度 final int maxImageHeightForDiskCache;//SD卡中最大的图片高度 final BitmapProcessor processorForDiskCache;//从SD卡获得Bitmap的加载器 final Executor taskExecutor;//加载图片时的执行器 final Executor taskExecutorForCachedImages;//加载缓存时的执行器 final boolean customExecutor;//是否使用默认执行器 final boolean customExecutorForCachedImages;//是否使用默认缓存执行器 final int threadPoolSize;//线程数,可以用来控制展示当前界面的item图片 final int threadPriority;//线程的执行优先级 final QueueProcessingType tasksProcessingType;//是LILO还是LIFO,默认是前者,但一般喜欢后者 final MemoryCache memoryCache;//内存缓存对象,如不写可用默认 final DiskCache diskCache;//SD卡缓存对象,如不写可用默认 final ImageDownloader downloader;//图片加载器,根据网络(http/s)、file、content、drawable、asset来加载 final ImageDecoder decoder;//图片解析器,根据获取的图片参数拿到Bitmap final DisplayImageOptions defaultDisplayImageOptions;//设置图片加载状态和结果,见下面源码 //不用网络下载图片的下载器,可理解为加载SD卡图片的加载器 final ImageDownloader networkDeniedDownloader; final ImageDownloader slowNetworkDownloader;//仅网络下载图片的下载器,支持断点续传拿这些变量来讲,基本就可以说明事情 public final class DisplayImageOptions { private final int imageResOnLoading;//图片是否加载中 private final int imageResForEmptyUri;//图片是否来自于空url private final int imageResOnFail;//图片是否加载失败 private final Drawable imageOnLoading;//加载中的图片 private final Drawable imageForEmptyUri;//空数据的图片 private final Drawable imageOnFail;//加载失败的图片 private final boolean resetViewBeforeLoading;//加载完是否重置(意味着放弃之前的加载) private final boolean cacheInMemory;//是否缓存在内存中 private final boolean cacheOnDisk;//是否缓存在SD卡中 private final ImageScaleType imageScaleType;//要多大的图片,统一设置 private final Options decodingOptions;//Bitmap的options对象 private final int delayBeforeLoading;//是否延迟加载,可用于非当前页面图片 private final boolean considerExifParams;//是否支持jpeg图片的rotate和flip等方法 private final Object extraForDownloader;//额外数据 private final BitmapProcessor preProcessor;//加载不在内存中的图片 private final BitmapProcessor postProcessor;//加载在图片中的图片 private final BitmapDisplayer displayer;//展示图片 private final Handler handler;//这个就不用讲了吧,跟主线程交互必不可少的工具 private final boolean isSyncLoading;//是否同步加载 判断是否是主线程: public static boolean isOnMainThread() { return Looper.myLooper() == Looper.getMainLooper(); }
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 1、Common依赖项目找不到。因为主项目没有引进setting.gradle 2、从Eclipse移植到AS,最重要的两个文件是setting.gradle和build.gradle,然后把依赖项目.svn文件夹删除,重新放入主项目目录下,加入版本控制 3、使用Lint工具,规范化代码,File——Settings——Editor——Inspections打开检查项设置窗口 4、清理多余图片:http://www.cnblogs.com/yilongm/p/4741370.html 5、Didn't find class "com.android.tools.fd.runtime.BootstrapApplication" 今天写程序执行安装后,程序启动就崩溃了,很奇怪,在自己的机器上没有问题。百思不得其解,google+stackoverflow。 问题: Didn’t find class “com.Android.tools.fd.runtime.BootstrapApplication” 解决: 方法1: 禁止Instant Run 关闭Instant Run的方法, File –> Settings–>Build,Execution,Deployment –>Instant Run —> 不勾选 “Enable instant run” 6、 Could not resolve all dependencies for configuration ':classpath'. > Could not find com.android.tools.build:gradle:2.2.2. Searched in the following locations: https://repo1.maven.org/maven2/com/android/tools/build/gradle/2.2.2/gradle-2.2.2.pom https://repo1.maven.org/maven2/com/android/tools/build/gradle/2.2.2/gradle-2.2.2.jar 查一下https://repo1.maven.org/maven2/com/android/tools/build/gradle/目录下最新的是2.1.3,所以啦, classpath 'com.android.tools.build:gradle:2.2.2'改为2.1.3即可 方法2: 1/3: Changing: classpath 'com.android.tools.build:gradle:2.0.0-alpha1' 1 1 By: classpath 'com.android.tools.build:gradle:1.2.3' 1 1 2/3: Changing: buildToolsVersion '23.0.2' 1 1 By: buildToolsVersion "21.1.2" 1 1 3/3: (in /.idea/gradle.xml) And: <option name="gradleHome" value="$APPLICATION_HOME_DIR$/gradle/gradle-2.8" /> 1 1 By: <option name="gradleHome" value="$APPLICATION_HOME_DIR$/gradle/gradle-2.4" /> 1 1 方法3: clean工程,重新编译一遍 6、java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[ 原因在于编译需要在arm-v7a包放同样的so文件 7、安装Gpu Tools Android Monitor->Monitors-GPU-点击安装,重启AndroidStudio 8、在User/user/.gradle/gradle.properties里配置的常量无效 解决办法:android studio打开setttings,注意Service directory path的配置 9、Debug不成功,总是出现x,关闭混淆即可。 minifyEnabled false 10、加速Gradle构建 http://droidyue.com/blog/2017/04/16/speedup-gradle-building/index.html 11、使用squareAndroid的代码风格 第一、下载并执行https://github.com/square/java-code-styles 第二、Setting -----------> Editor ---------------> Inspections -----------> Profile 选择“Square" 第三、Setting -----------> Editor ---------------> Code Style -----------> Java -----------> Profile 选择“SquareAndroid" 12、固定Project、Terminal等框 开发工具上面View选项-选中Tool Buttons 13、Error:Execution failed for task ':app:compileDebugAidl'. 原因:首先查找compileSdkVersion和buildSdkVersion是否一致,再看aidl文件写的有没有错,比如包名、类名、类的引入等。 14、gradle4.4版本:All flavors must now belong to a named flavor dimension. Learn more at https://d.android.com/r/tools/flavorDimensions-missing-error-message.html 要求重新定义flavorDimensions,用于区别不同的打包需求。修改为如下代码,即解决: flavorDimensions "default","api" productFlavors { official { dimension "default" } mini23{ dimension "api" minSdkVersion '23' versionCode 20000 versionNameSuffix "-minApi23" } } 15、gradle4.4版本:Unable to resolve dependency for ':@debug/compileClasspath': Could not resolve project :library. 原来这么写: // releaseCompile project(path: ':library', configuration: 'release') // debugCompile project(path: ':library', configuration: 'debug') 现在对compile有了更高要求,需要修改如下: implementation project(':library') 16、gradle4.4版本:The SourceSet 'instrumentTest' is not recognized by the Android Gradle Plugin. Perhaps you misspelled something? 项目所有的build.gradle文件中,原来的instrumentTest已经不支持,需要修改如下: androidTest.setRoot('tests')
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 加密比较复杂,但今天公司有需求,就稍微再研究一下,方式只有两种,对称加密和非对称加密。对称加密是指加密后可以用原方法解密,如情报传输常用;非对称加密指加密后不可解密,有什么用途呢?登录的密码使用非对称加密,拿到加密后数据跟数据库存的数据对比,相同则让登录成功(也就说数据库不存明文密码,只存加密后的字符串),还有接口参数的校验等,例如md5参数校验;有了这种加密方式再不也担心用户名密码被盗了(除非密码本被盗等其他情况)。下面我们介绍几种常见的加密方法 通用的Base64:“对称加密”,一般用在将图片Base64后可以存入数据库。 public class Base64 { static private final int BASELENGTH = 128; static private final int LOOKUPLENGTH = 64; static private final int TWENTYFOURBITGROUP = 24; static private final int EIGHTBIT = 8; static private final int SIXTEENBIT = 16; static private final int FOURBYTE = 4; static private final int SIGN = -128; static private final char PAD = '='; static private final boolean fDebug = false; static final private byte[] base64Alphabet = new byte[BASELENGTH]; static final private char[] lookUpBase64Alphabet = new char[LOOKUPLENGTH]; static { for (int i = 0; i < BASELENGTH; ++i) { base64Alphabet[i] = -1; } for (int i = 'Z'; i >= 'A'; i--) { base64Alphabet[i] = (byte) (i - 'A'); } for (int i = 'z'; i >= 'a'; i--) { base64Alphabet[i] = (byte) (i - 'a' + 26); } for (int i = '9'; i >= '0'; i--) { base64Alphabet[i] = (byte) (i - '0' + 52); } base64Alphabet['+'] = 62; base64Alphabet['/'] = 63; for (int i = 0; i <= 25; i++) { lookUpBase64Alphabet[i] = (char) ('A' + i); } for (int i = 26, j = 0; i <= 51; i++, j++) { lookUpBase64Alphabet[i] = (char) ('a' + j); } for (int i = 52, j = 0; i <= 61; i++, j++) { lookUpBase64Alphabet[i] = (char) ('0' + j); } lookUpBase64Alphabet[62] = (char) '+'; lookUpBase64Alphabet[63] = (char) '/'; } private static boolean isWhiteSpace(char octect) { return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9); } private static boolean isPad(char octect) { return (octect == PAD); } private static boolean isData(char octect) { return (octect < BASELENGTH && base64Alphabet[octect] != -1); } /** * Encodes hex octects into Base64 * * @param binaryData Array containing binaryData * @return Encoded Base64 array */ public static String encode(byte[] binaryData) { if (binaryData == null) { return null; } int lengthDataBits = binaryData.length * EIGHTBIT; if (lengthDataBits == 0) { return ""; } int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP; int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP; int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1 : numberTriplets; char encodedData[] = null; encodedData = new char[numberQuartet * 4]; byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0; int encodedIndex = 0; int dataIndex = 0; if (fDebug) { System.out.println("number of triplets = " + numberTriplets); } for (int i = 0; i < numberTriplets; i++) { b1 = binaryData[dataIndex++]; b2 = binaryData[dataIndex++]; b3 = binaryData[dataIndex++]; if (fDebug) { System.out.println("b1= " + b1 + ", b2= " + b2 + ", b3= " + b3); } l = (byte) (b2 & 0x0f); k = (byte) (b1 & 0x03); byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc); if (fDebug) { System.out.println("val2 = " + val2); System.out.println("k4 = " + (k << 4)); System.out.println("vak = " + (val2 | (k << 4))); } encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)]; encodedData[encodedIndex++] = lookUpBase64Alphabet[(l << 2) | val3]; encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f]; } // form integral number of 6-bit groups if (fewerThan24bits == EIGHTBIT) { b1 = binaryData[dataIndex]; k = (byte) (b1 & 0x03); if (fDebug) { System.out.println("b1=" + b1); System.out.println("b1<<2 = " + (b1 >> 2)); } byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; encodedData[encodedIndex++] = lookUpBase64Alphabet[k << 4]; encodedData[encodedIndex++] = PAD; encodedData[encodedIndex++] = PAD; } else if (fewerThan24bits == SIXTEENBIT) { b1 = binaryData[dataIndex]; b2 = binaryData[dataIndex + 1]; l = (byte) (b2 & 0x0f); k = (byte) (b1 & 0x03); byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)]; encodedData[encodedIndex++] = lookUpBase64Alphabet[l << 2]; encodedData[encodedIndex++] = PAD; } return new String(encodedData); } /** * Decodes Base64 data into octects * * @param encoded string containing Base64 data * @return Array containind decoded data. */ public static byte[] decode(String encoded) { if (encoded == null) { return null; } char[] base64Data = encoded.toCharArray(); // remove white spaces int len = removeWhiteSpace(base64Data); if (len % FOURBYTE != 0) { return null;// should be divisible by four } int numberQuadruple = (len / FOURBYTE); if (numberQuadruple == 0) { return new byte[0]; } byte decodedData[] = null; byte b1 = 0, b2 = 0, b3 = 0, b4 = 0; char d1 = 0, d2 = 0, d3 = 0, d4 = 0; int i = 0; int encodedIndex = 0; int dataIndex = 0; decodedData = new byte[(numberQuadruple) * 3]; for (; i < numberQuadruple - 1; i++) { if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++])) || !isData((d3 = base64Data[dataIndex++])) || !isData((d4 = base64Data[dataIndex++]))) { return null; }// if found "no data" just return null b1 = base64Alphabet[d1]; b2 = base64Alphabet[d2]; b3 = base64Alphabet[d3]; b4 = base64Alphabet[d4]; decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); decodedData[encodedIndex++] = (byte) (b3 << 6 | b4); } if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))) { return null;// if found "no data" just return null } b1 = base64Alphabet[d1]; b2 = base64Alphabet[d2]; d3 = base64Data[dataIndex++]; d4 = base64Data[dataIndex++]; if (!isData((d3)) || !isData((d4))) {// Check if they are PAD characters if (isPad(d3) && isPad(d4)) { if ((b2 & 0xf) != 0)// last 4 bits should be zero { return null; } byte[] tmp = new byte[i * 3 + 1]; System.arraycopy(decodedData, 0, tmp, 0, i * 3); tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); return tmp; } else if (!isPad(d3) && isPad(d4)) { b3 = base64Alphabet[d3]; if ((b3 & 0x3) != 0)// last 2 bits should be zero { return null; } byte[] tmp = new byte[i * 3 + 2]; System.arraycopy(decodedData, 0, tmp, 0, i * 3); tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); return tmp; } else { return null; } } else { // No PAD e.g 3cQl b3 = base64Alphabet[d3]; b4 = base64Alphabet[d4]; decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); decodedData[encodedIndex++] = (byte) (b3 << 6 | b4); } return decodedData; } /** * remove WhiteSpace from MIME containing encoded Base64 data. * * @param data the byte array of base64 data (with WS) * @return the new length */ private static int removeWhiteSpace(char[] data) { if (data == null) { return 0; } // count characters that's not whitespace int newSize = 0; int len = data.length; for (int i = 0; i < len; i++) { if (!isWhiteSpace(data[i])) { data[newSize++] = data[i]; } } return newSize; } } 更多介绍:http://www.ruanyifeng.com/blog/2008/06/base64.html Md5加密,主要用于C端对参数加密后做一个签名,然后S端采取同样操作,签名一致则认为安全,返回数据;目的,为了防止恶意添加参数,盗取数据;原理:非对称加密,加密过程单向不可逆,比较加密后的结果;应用方式,下面是通用的密码本,所以很容易破解,当然也可以自定义密码本(C/S一致即可)。 // 全局数组 private final static String[] strDigits = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}; protected static char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; // 返回形式为数字跟字符串 private static String byteToArrayString(byte bByte) { int iRet = bByte; // System.out.println("iRet="+iRet); if (iRet < 0) { iRet += 256; } int iD1 = iRet / 16; int iD2 = iRet % 16; return strDigits[iD1] + strDigits[iD2]; } // 转换字节数组为16进制字串 private static String byteToString(byte[] bByte) { StringBuffer sBuffer = new StringBuffer(); for (int i = 0; i < bByte.length; i++) { sBuffer.append(byteToArrayString(bByte[i])); } return sBuffer.toString(); } public static String MD5Encrypt(String strObj) { String resultString = null; try { resultString = new String(strObj); MessageDigest md = MessageDigest.getInstance("MD5"); // md.digest() 该函数返回值为存放哈希值结果的byte数组 resultString = byteToString(md.digest(strObj.getBytes())); } catch (NoSuchAlgorithmException ex) { ex.printStackTrace(); } return resultString; } Des加密-(Data Encryption Standard),由56位密钥和8位奇偶检验符组成,通过异或、移位、置换和代换四种操作循环完成,如果一台PC计算能力是一秒一百万次,那么需要2000年才能破解,破解的方案只有穷举法猜出它的密码本或者暴力方式。特别说明一点,使用DES加密的KEY只有前8位有效,写多了也是多余。对称加密,加密方式可逆,用于双方各持有的一个密码本,一个加密一个解密,二战期间的情报加密方式类似这种。 private static String encoding = "UTF-8"; /** * sKey 奇偶校验位 加密字符串 */ public static String encrypTo(String sKey, String str) { String result = str; if (str != null && str.length() > 0) { try { byte[] encodeByte = str.getBytes(encoding); byte[] encoder = getSymmetricResult(Cipher.ENCRYPT_MODE, sKey, encodeByte); result = Base64.encode(encoder).toString(); } catch (Exception e) { e.printStackTrace(); return ""; } } return result; } /** * sKey 奇偶校验位 * 解密字符串 */ public static String decrypTo(String sKey, String str) { String result = str; if (str != null && str.length() > 0) { try { byte[] decodeByte = Base64.decode(str); byte[] decoder = getSymmetricResult(Cipher.DECRYPT_MODE, sKey, decodeByte); result = new String(decoder, encoding); } catch (Exception e) { e.printStackTrace(); return ""; } } return result; } /** * 对称加密字节数组并返回 * * @param byteSource * 需要加密的数据 * @return 经过加密的数据 * @throws Exception */ public static byte[] getSymmetricResult(int mode, String sKey, byte[] byteSource) throws Exception { try { SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); byte[] keyData = sKey.getBytes(); DESKeySpec keySpec = new DESKeySpec(keyData); Key key = keyFactory.generateSecret(keySpec); Cipher cipher = Cipher.getInstance("DES"); cipher.init(mode, key); byte[] result = cipher.doFinal(byteSource); return result; } catch (Exception e) { throw e; } finally { } } AES加密(Advanced Encryption Start)-DES的升级版本,密钥长度可能是128位、192位和256位中的一种,使用更为复杂的算法,对称加密,同样也是可逆的,但由于复杂度加大解密时间更长。 /** * 加密 * * @param content * 需要加密的内容 * @param password * 加密密码 * @return */ public static byte[] encrypt(String password, String content) { try { KeyGenerator kgen = KeyGenerator.getInstance("AES"); kgen.init(128, new SecureRandom(password.getBytes())); SecretKey secretKey = kgen.generateKey(); byte[] enCodeFormat = secretKey.getEncoded(); SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES"); Cipher cipher = Cipher.getInstance("AES");// 创建密码器 byte[] byteContent = content.getBytes("utf-8"); cipher.init(Cipher.ENCRYPT_MODE, key);// 初始化 byte[] result = cipher.doFinal(byteContent); return result; // 加密 } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } catch (BadPaddingException e) { e.printStackTrace(); } return null; } /** * 解密 * * @param content * 待解密内容 * @param password * 解密密钥 * @return */ public static byte[] decrypt(String password, byte[] content) { try { KeyGenerator kgen = KeyGenerator.getInstance("AES"); kgen.init(128, new SecureRandom(password.getBytes())); SecretKey secretKey = kgen.generateKey(); byte[] enCodeFormat = secretKey.getEncoded(); SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES"); Cipher cipher = Cipher.getInstance("AES");// 创建密码器 cipher.init(Cipher.DECRYPT_MODE, key);// 初始化 byte[] result = cipher.doFinal(content); return result; // 加密 } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } catch (BadPaddingException e) { e.printStackTrace(); } return null; } public static void main(String[] args) { String sKey = "adsasafq"; String str = "Hello World"; byte[] aesEncryptStr = AESEncrypt.encrypt(sKey, str); byte[] aesDecryptStr = AESEncrypt.decrypt(sKey, aesEncryptStr); System.out.println(new String(aesDecryptStr)); } Sha加密-(Secure Hash Algorithm),与MD5算法类似,但复杂度要高出32个量级,不对称加密,加密过程不可逆,其中SHA-1-224-256-384-512加密复杂度依次增加, /** * 散列算法 * * @param byteSource * 需要散列计算的数据 * @return 经过散列计算的数据 * @throws Exception */ public static String getShaStr(byte[] byteSource) { try { MessageDigest currentAlgorithm = MessageDigest.getInstance("SHA-256"); currentAlgorithm.reset(); currentAlgorithm.update(byteSource); return bytes2String(currentAlgorithm.digest()); } catch (Exception e) { e.printStackTrace(); } return null; } private static String bytes2String(byte[] aa) {// 将字节数组转换为字符串 String hash = ""; for (int i = 0; i < aa.length; i++) {// 循环数组 int temp; if (aa[i] < 0) // 判断是否是负数 temp = 256 + aa[i]; else temp = aa[i]; if (temp < 16) hash += "0"; hash += Integer.toString(temp, 16);// 转换为16进 } hash = hash.toUpperCase(); // 转换为大写 return hash; } public static void main(String[] args) { String str = "Hello World"; String shaEncryptStr = ShaEncrypt.getShaStr(str.getBytes()); System.out.println(shaEncryptStr);// sha1-0A4D55A8D778E5022FAB701977C5D840BBC486D0 System.out.println(shaEncryptStr.length()); } RSA加密(三位发明者名字首字母),由一个公钥和一个私钥组成,支付宝目前就采用这种加密方式,C端持有公钥,S端持有私钥,C端发送的加密数据只能由S端来处理,安全系统业界称为最高;原理是对两大素数的乘积做拆分,有无数种可能,所以只要公钥和私钥不泄密,一般就没有问题。非对称加密,但加密过程可逆。 根证书主要从VeriSign和GlobalSign两种,就是说默认得信任,否则就互联网没有信任基础了。 详见:欢迎大家提问Android技术及职业生涯等问题第9条 先简单介绍这几种,DES、AES对称加密,MD5、SHA(前两种又属于Hash算法)和RSA非对称加密。 有兴趣下载源码的请点击,源码! 分块隐藏ECB和CBC ECB:直接分块加密 for(int i=0;i<grey->width;i++) for(int j=0;j<grey->height;j++) grey->imageData[j*grey->width+i]=bitrev(grey->imageData[j*grey->width+i]); cvNamedWindow("ecb"); cvShowImage("ecb", grey); CBC:与上块密文异或后加密,需要向量for(int i=0;i<grey->width;i++) for(int j=0;j<grey->height;j++) if(i!=0&&j!=0) grey->imageData[j*grey->width+i]=bitrev(grey->imageData[j*grey->width+i]^grey->imageData[j*grey->width+i-1]); else grey->imageData[0]=grey->imageData[0]^IV; cvNamedWindow("cbc"); cvShowImage("cbc", grey); 算法固然重要,也需要放在安全的环境下,否则被盗取的可能性比较大,也是不安全的。 1、将加密过程放在so库里,这样对方即使得知你的加密字符串,也无法知道你的密钥和加密过程 2、将加密算法通过远程管理,一旦发现出问题,通过服务器派发客户端反射的方式,下发新的算法 3、增加代码混淆的难度 4、为防止撞库,可以选择更慢的算法,来延长被破解的时间。 美国联邦调查局认为:密钥长度需要设置56位,几乎不可破解,而30位以下可以通过暴力破解-入侵的艺术。 了解更多:http://snowolf.iteye.com/blog/379860 推荐一下PHP、IOS和Android都能使用的加密方法:http://www.funboxpower.com/php_android_ios_aes 密码学三大作用: 加密:防止坏人获得你的数据 认证:防止获得数据而你不知道 鉴权:防止假冒你 http://www.cnblogs.com/alisecurity/p/5312083.html 使用 Android 的 AES/DES/DESede 加密算法时,不要使用默认的加密模式ECB,应显示指定使用 CBC 或 CFB 加密模式。说明:加密模式 ECB、 CBC、 CFB、 OFB 等,其中 ECB 的安全性较弱,会使相同的铭文在不同的时候产生相同的密文,容易遇到字典攻击,建议使用 CBC 或 CFB 模式。1) ECB:Electronic codebook,电子密码本模式2) CBC:Cipher-block chaining,密码分组链接模式3) CFB:Cipher feedback,密文反馈模式4) OFB:Output feedback,输出反馈模式
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 随着移动端应用平台的兴起,需求和交互方式的多样化,H5开发逐渐在移动端流行起来;常见的移动产品有Web App(纯H5)、混合型App(Native+h5)、原生App(Native),然而自移动端兴起之日起,混合型产品即被产品经理大力推崇,方便更新、先上线再补发等特点,相比Native端的线上功能不可随意改动、出问题不能及时发布版本更新要排期等特点,H5还是很实用的。只需要前期制定好规则,增强扩展性,之后相当于增加若干功能模块。目测中国联通的产品大半是纯H5开发的,非常流畅,使用方便;淘宝集成天猫、去啊、外卖、聚划算等,仅在支付时调用淘宝支付功能;而H5使用的js、css等可以放到本地,而且无论Android还是苹果端均可使用,有效降低开发成本。 目前Android上常用底层技术方案有两种,一种通过页面加载完执行js方法传值,或者调用-刷新-执行,方法少功能也比较鸡肋,另一种使用webJsInterface(4.2版本以上,主要为H5端提供数据)的方式+shouldOverrideUrlLoading(接收H5端的事件驱动)监听页面动作,由WebViewProvider接口去执行,提供一个注册对象给H5端,调用我方提供的方法即可,交互非常方便;更复杂的功能,比如交互使用js,基本上把内部WebView变成跟浏览器一样强大的功能,为节省时间,还需要使用一些框架加以修改来完成自己的工作,以下介绍几种 Cordova,几乎所有框架都基于它开发,主要提供使用js方法调用本地摄像头、麦克风等,对调用方法以及WebView相关功能进行再次封装,并且可以设置某些产品才能使用此框架的白名单,通过消息来记录页面生命周期和执行JS方法,跟移动端App的管理方式非常类似 。主要完成本地网页的加载,拓展也可以加载站网页,并调用本地手机功能,但Web端开发成本升高,可能会导致某些功能不可用,所以需要平衡使用。源码如下:点击图片下载源码 PhoneGap,集成Cordova最好的一款。历史如此:最先是PhoneGap先出现,后来被Adobe收购,然后此产品转到Apache公司改名为Cordova,继续发展,最后Adobe把PhoneGap给Apache,最终产品名是Cordova,但也有人称为PhoneGap,其实到目前为止已经成为一个东西。而它的主要作用是提供给Web高级开发一个运行环境,用于产品需求不确定性模块的开发(其实可以称为烂尾工程的维护),如此来保证产品的完整性;但它的体验仍然不如Native开发,仍然要转为Native;而Web开发发挥能力的主要方面在于Wap商城,如公司微信公众号或其他产品集成自己产品,如果从战争方面来说,Native用于守城和形象维护,而Web用来四处攻城略地。 Icenium,Telerik公司开发,增强PhoneGap的功能,减少Cordova的复杂性,不用安装sdk,开发工具包分为四个部分: Graphite:轻量级、适用于Windows的开发环境,只有10M左右。 Mist:网页端开发环境,可以与Graphite同步,同步机制包含版本控制功能,同步以Git网址为基础,支持Github和其他类似网站。不过Mist功能局限性较大,对于非Windows用户没有多少吸引力。 LiveSync:实时编译,在所有已连接的设备上运行代码,并获得预览图,比xCode的错误提示更加直观。LiveSync支持iPhone、Nexus 7等多种设备。 Kendo:Kendo是整个开发包的核心部分,它能优化并转换代码,生成原生安装包,以适应每种设备。 Icenium,增强PhoneGap的功能,减少Cordova的复杂性,由Telerik开发,基本不用安装sdk,开发工具包分为四个部分: MobileAngular,一个完全模仿App控件做的一个H5框架,框架地址。 Sencha Touch,支持Web标准以及各种手势动作,异常强大;WX5,一个开源H5平台软件,可直接把开发资源生成Android apk和Apple ipa文件,并发布到应用市场。 Ionic,使用MVVP模式,支持AngularJs和Sass ReactNative,支持得到App端全套API支持,如取图、Sensor等;同样也支持IOS。 week,阿里开源,支持Android和IOS。 有人说一款应用,80%的人只访问20%的页面,其他功能相当浪费,因此将多余部分做成H5。个人深感这句话的深意,那么以后前端应该做IOS+Android+H5的全栈工程师。 拓展学习:Android如何从外部跳进App
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 研究安卓已多年,一直在应用层做开发,Framework层只是看过,也就是大家常说的"底层",而高级一点的功能如热加载、处理器类型配置,必须得了解再深入些才好,Library、Runtime、Kernel层等;当然了解底层的原因,不是在于去做底层开发,而是更好的做应用层,使其功能更强大和完善。这里的底层是除应用层以外其他层。而Dalvik是什么,就是把各层串起来,处理安卓系统和服务的一个计算机。 Dalvik这个名字,是创始人的祖先生存过一个小村庄的名字,大概为纪念先人的原因,这个村庄位于冰岛。前面也有讲这个虚拟机是专门为移动设备,即内存小、计算能力低、运行标准要求高提供的嵌入式设备,正因为克服如此多的问题,以其优良的性能出现在世人面前,才受到众多厂商的追捧,前有HTC、摩托罗拉,后来阿里云、小米;越来越追求代码层的优化,更好的解决方案,更快的解释、加载速度,更好的体验和兼容性,来抵消硬件设备带来的价格冲击-这也是国内厂商,应对众多街机的一种战略手段,以数十万上百万的出厂设备*单利-有限的研发力量价格=最终厂家利润,假设每件研发投入100万,价格降低5块,则收益至少达到平衡或500万以上;个人不太认同这种恶性竞争,逼死开发的策略,商品应该优质高价,让消费者使用安全放心,带给的是心理的安全感、舒适感、品牌感知度,以及强大的功能,像IPhone一样,让生活归生活,功能归功能;总之从厂商竞争情况来说,个人觉得华为做的最好。但万变不离其宗,产品总是不完善的,需要维护和升级,以维持其对于公众的领先地位,因此理解虚拟机,有助于从全局出发,提出更好的优化或解决方案,为消费者提供更好的产品。 闲话这么多,进入正题。Dalvik来自于JVM,大多数的java程序都可以执行(为什么说大多数,因为它退出JSF标准,按说1.6以后是否全面的支持Java,这事能肯定)。前面对JVM有过讲解,它基于堆栈,处理代码指令,需要不断的从主内存复制到临时内存,然后操作完再存回去,有寄存器但仅限于虚拟机自己使用;而Dalvik基于寄存器,代码的执行本身就依赖于寄存器,且避免过多的存取,字节码指令减少47%,访问次数减少37%,因此执行速度更快。但寄存器有一个缺陷,只有65536个,每个32位,即总共256k,因此它还需要另一种文件Odex的支持。说到它,就得先讲dex文件,我们都知道java文件被编译后形成.class文件,这种文件符合JVM的执行规则;而Dalvik则把.class文件使用dx工具压缩形成dex文件,它符合寄存器的执行规则,同时又是对原数据进行过优化,共享共同数据,降低冗余,使用代码结构紧凑,因而包大小大约为同类型jar文件的50%。 Dex文件的格式与前面讲的类文件格式类似,可以对比着看:有基本数据类,如byte占8位,表示1byte的有符号数,ubyte同样,但表示无符号数;也有文件结构,也有字符串索引,字段索引,方法索引,类型索引,类定义,多的是原型数据索引,连接数据区等;同时header信息里也存着魔数,文件总长度,以上各种索引及类型的个数、地址等,索引除公用的是绝对位置,其他均为相对位置。数据的存放。Dalvik字节码总共有226条,使用时会用一个hash指令表来存储,格式如 B|A|op 12x 第一个是指令,第二个是缩写,用来说明它的操作逻辑。 Odex文件是为了提高程序执行速度而做的本地缓存,一般存在data/dalvik-cache/xxx.odex,这个文件已经由寄存器加载时帮忙做过字节码校验、空方法消除、替换优化等工作,执行时优化解释odex,没有再执行dex,把加载过的字节码缓存入odex,这就是它的工作原理。它在Dex文件前添加了头部信息,尾部拼接了依赖库、寄存器映射关系、类hash索引等辅助信息,索引再介绍一下,它包括文件的偏移地址和类的偏移地址,通过hash定位可以尽快查找到资源并加载。讲到这里,dex加载的原理已经明了,由Dalvik加载,处理器解释执行,缓存放入odex。而Odex不断增大,也是安卓手机内存变小的原因,因而对于卸载的App的odex文件可以删除,不常用的选择删除,常用的保留,不然每次加载解释后仍然会增大,而手机速度更慢。降低Android手机内存最好的方法就是找到data/dalvik-cache里不使用的odex删除。 dex是重点,而apk的组成还有主配置文件,资源文件,通过aapt工具打包而成,用jarsigner签名。而dex的加载跟class文件加载非常类似,把Dex(class)与一个DexFile(ClassFile)文件关联,建立起数据结构,再把各个类依次加载生成对象,把指针交给解释器引用执行,而4.0.4解释器又有两种,C语言和汇编,移动和快速型;但虚拟机除了支持java语言以外,还支持c语言编写的程序,按照java接口进行调用,执行效率更高;当然使用JIT补丁的方法,会更便捷,加载更快,移植性较强,后面我们将介绍热加载的原理和常用的几种框架。 Apk内部的文件包含以下几种: resources.arsc:二进制文件,资源id和路径映射组成 res:二进制,项目中的res目录 class.dex:class编译成的可执行文件,反编译后可以看到源码结构 AndroidManifest.xml:二进制文件,反编译后可看到部分内容 讲完dex文件,接下来可以讲讲安卓系统的启动过程,之前有过介绍,但本次希望以更通俗的语法来说明。系统按键触动驱动,触发init.rc的init进程启动,这是系统启动后的首个进程,再创建各种守护进程,然后启动Zygote进程,也就是用户进程,其他应用都是由它创建-fork子进程共享内存和资源,启动系统服务的SystemServer进程(相当于服务端)监听socket指令操作。x86初始化,执行dalvik/dalvikvm/Main.c中的main方法,执行JNI_CreateJavaVM函数创建虚拟机;而Arm平台初始化是从Zygote进程开始的,执行/jni/AndroidRuntime.cpp的startVm方法,通过JNI_CreateJavaVM函数创建虚拟机。然后调用startReg注册JNI函数,用来调用java类;调用ZygoteInit类的main方法:1、创建socket接口(相当于客户端),接收应用请求 2、调用预加载类和资源 3、启动SystemServer并分裂出sytem_server,启动各项系统服务,最终将线程加入Bind通信系统,此进程退出则Zygote退出重启4、调用runSelectLoopMode监听程序请求 注:子进程同样可以再fork出一个Zygote进程,所以在实际操作时,要区分一下。在子进程中fork函数返回0,在父进程返回负值 或此进程的pid,可以用来区别运行的是否是子进程或者创建成功与否。 虚拟机的功能可以分为以下几部分: 1、进程管理:每个虚拟机都有一个进程,依赖于Zygote机制实现 2、Zygote进程管理:会fork出来一个子Zygote进程,一个SystemServer进程,一个非Zygote进程的Speciallize进程 3、类加载:先加载所有类库,然后加载字节码,放入类数据结构,供类解释器执行,如果有关联的超类、接口等也一并加载 4、内存管理:使用新老生代结合的方式,使用复制+标记-清除的算法 4、本地接口:JNI 5、反射机制:通过类结构的加载,查看、调用、修改类中的方法和属性 6、解释器:负责执行Dex字节码 7、即时编译:JIT Dalvik编译的原理在于:开发者编译项目成dex,在安装时转成odex,减少了JIT时再进行字节码校验等工作; 而ART在Android4.4版本出现,相比较Dalvik的优势在于,直接将dex编译成可执行的机器码(而不再JIT将字节码转换为机器码),这样加速App的运行,减少电量的消耗,用户体验更加流畅;当然劣势也是有的,App安装时比较慢,缓存文件变大;较之IOS一次开发者编译,即在用户手机运行有先天的弊端,优势在于Android热修复和动态加载方便,而IOS相对则比较古板。 Dex较之class文件,添加新操作码支持,进行文件优化和压缩,加快解析速度签名好处:1、顺利升级2、热修复 3、进程级数据共享Meta-Inf里的文件Manifest.MF是对Apk包中非Meta-Inf的所有文件进行数据校验,而Cert.Sf对Manifest.MF和其他文件进行校验,Cert.Rsa中存储签名信息 Dalvik运行在Arm的CPU上,那么有必要了解一下Arm Arm:一家1990年成立的英国公司,主要进行Cpu授权,是一种知识产权公司,占据移动95%的市场,主要靠设备抽成盈利。 产权领域:指令集架构、微处理器、GPU、互连架构等 特点:低功耗、低成本,使用RISC(Reduced Instruction Set Computer,通常仅执行1或2个指令即可完成意图), 和big.LITTLE架构(适配高性能-64位的内核和低性能-32位的内核切换),方便其他品牌贴标生产 授权方式:架构、内核、使用三种形式,分别针对大、中、小型公司 合作伙伴:高通、联发科等蚂蚁联盟 合作形式:实行OEM(Original Equipment Manufacture) 对手劣势:1968年成立的Intel使用CISC(Complex Instruction Set Computer,通常仅执行3或4个指令即可完成意图)架构的x86功耗高、掉电快 适配执行:从上面介绍可以看出,Arm占据绝对多数市场,因此mips和x86基础不需要考虑适配,而且它们会主动解析Arm指令成自己能使用的指令(使得自身效率更低,不得不赞叹规模效应的强大),big.LITTLE的高低性能内核切换,可以进行有效省电(计算量小用低性能、大用高性能,性能越高越耗电)。而64位寄存器的使用,减少内存存取的次数,提高CPU处理效率,用于RISC架构高性能的处理器。 Mips:1998年成立,同样依据RISC架构,中科院设计的“龙芯”与它95%类似,涉及侵权,而准备收购其估值1亿的20%股份。 在Android Studio下,则可以通过以下的构建方式指定需要类型的SO库。 productFlavors { flavor1 { ndk { abiFilters "armeabi-v7a" abiFilters "x86" abiFilters "armeabi" } } flavor2 { ndk { abiFilters "armeabi-v7a" abiFilters "x86" abiFilters "armeabi" abiFilters "arm64-v8a" abiFilters "x86_64" } } } 更多:http://blog.csdn.net/reboot123/article/details/17918447 案例: 如果你使用的是 1.7.4 版 (或更高版本) Agora Native SDK: Agora Native SDK 目前支持 64 位的 ARM 架构,只需将 arm64-v8a 里面的文件(位于SDK包内)拷贝至项目对应的 arm64-v8a 路径下。目前不支持 x86_64 架构直接运行,不过可以以 x86 方式兼容运行。具体做法就是在64位的 x86 设备上保证 APK 里面没有 x86_64 目录。 如果你使用的是 1.7.4 版之前的 Agora Native SDK: Agora Native SDK 目前只提供 32 位 native 库(armeabi-v7a),在 64 位设备上,Android 支持以 32 位进程模式启动 APK,因此 Agora Native SDK 也可以在 64 位 Android 设备上使用,但必须保证 APK 的 arm64 目录为空。否则 Android 系统将以 64 位进程模式加载 APK,由于 Agora Native SDK 没有提供 64 位库,APK 启动会失败。 由于目前 Android 的主流还是 32 位设备,一般各厂商都会提供 32 位库,因此一般来说,在 64 位 Android 设备以 32 位进程模式启动一般不会有问题。但是如果在某些 64 位平台上出现了这样的错误: java.lang.UnsatisfiedLinkError: dlopen failed: ”libHDACEngine.so” 但是如果 64 位平台上出现以下报错: java.lang.UnsatisfiedLinkError: dlopen failed: "libHDACEngine.so" 可能的原因: 在安装 APK 的时候,系统会按照 Build.SUPPORTED_ABIS 去查找 APK 的 lib 目录下的 native 库的目录(现有的 ABI: armeabi, armeabi-v7a,arm64-v8a, x86, x86_64, mips64, mips)。 如果在 app 中有兼容 64-bit 的目录但是又缺少库文件的话,并不会使用其他 ABI 目录下的库文件替换所缺少的库文件进行安装,这些库不混合使用,也就是说需要为每个架构提供对应的库文件。 Android 在加载 native 库的时候有回退(fallback)机制,在64位系统上如果 app 并不存在 arm64-v8a 的目录,则会尝试寻找armeabi-v7a下面的库进行加载,一般来说是向下兼容的。 解决方案: 方法 1: 在构建应用程序的时候,在工程 (project) 里删除所有 arm64-v8a 下面的库以及该目录;在生成 app 后,确认 apk 的包内 lib 下没有 arm64-v8a 的目录。 方法 2: 在 gradle 构建文件中设置 abiFilters,只打包 32 位架构的库: android { ... defaultConfig { ... ndk { abiFilters "armeabi-v7a","x86" } } }
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 1.0版本:2016-05-21 1.1版本:2016-10-22 前两节我们探讨了Java类内存模块,文件结构,以及Jvm的回收机制,今天我们再来探讨一下它的文件加载机制,都知道Jvm要加载的是二进制流,可以是.class文件形式,也可以是其他形式,总之按照它加载的标准即协议来设计就不会有问题,以下主要就机制和标准两个问题分析一番 首先来说Java类文件的加载机制,跟变量的加载机制类似,它先把Class文件加载入内存,再对数据进行验证、解析和初始化,最终形成虚拟机可以直接使用的Java类型。由于Java是采用JIT机制,所以加载时会比较慢,但优点也明显,具有高度灵活性,支持动态加载和动态连接。接下来就讲讲类的加载过程: 一个类加载的基本过程是按照下面的顺序来,但也有不严格按照这个顺序来的,也有打乱顺序来的,如动态加载就得先初始化再解析。 1、加载- 由虚拟机自行决定,但也有由于下面的阶段要执行而执行上面阶段的情况。这时虚拟机会做三件事,第一、通过全限定名找到文件,读取它的二进制流,第二、把文件里的静态方法和变量放到方法区中,第三、生成一个对象放入堆中,作为访问入口。注意第一条,仅是读取二进制流,没说具体从什么文件中读,也没说从哪里读,所以造就Java很强的扩展性,可以从Jar、Zip中,也可以从网络层、数据库层等 。主要是对象和方法区的声明。 2、验证 确保二进制流符合虚拟机的要求,不符合会报VerifyError。 第一、文件格式验证,是否符合Java文件的要求:如是否有魔数,主次版本是否符合当前虚拟机要求(详见:Java高级之类结构的认识);第二、元数据验证,是否符合Java代码规范,如abstract类是否直接被实例化或子类有未加载的方法,普通类有无间接或直接继承基础父类:Object等;第三、字节码验证,对数据流和控制流进行分析,保证不会做出危害虚拟机的行为,如是否把父类赋值给子类,是否把对象赋值给一个非此类型的对象等;第四、符号引用验证,主要是类、变量、方法描述是否能找的到,如全限定名是否能找到该文件,是否具有可访问性等,主要对内部结构的判定。 3、准备 不会为实例变量赋值,主要为类静态变量赋初值,通常为0值如static int a=123此时a=0,final int b=123此时b=123; 4、解析 将常量池中的符号引用转化为直接引用的过程。这里说的符号引用指变量类型,直接引用指可以直接定位到对象的句柄。类、方法、字段、接口解析,根据全限定名获得相关对象,拿到它的类型,若无对所在类访问权会抛出IllegalAccessError,无字段NoSuchFieldError,无方法NoSuchMethodError,是类不是接口会抛出IncompatibleClassChangeError 5、初始化 根据程序要求加载类和必要的资源。有且仅有四种情况,需要主动初始化后才能执行接下来的操作 ,所以要先执行上面的四步。第一、new对象或static标记的类、方法、变量;第二、继承的父类,子类如要执行就需要初始化父类;第三、反射类里的方法,那肯定要初始化了;第四、执行的主类,用main方法的类。其他被动初始化的情况不需要考虑。 其实这一条还能衍生出一个问题:如何快速实现应用加载,就需要尽量避免上述行为。 小例子: public class SuperClass { static { System.out.println("SuperClass!!"); } public static int value = 1; } public class SubClass extends SuperClass { static { System.out.println("SubClass!!"); } } public class TestClassLoad { public static void main(String[] args) { System.out.println(SubClass.value); SuperClass superClass = new SubClass(); } } SuperClass!! 1 SubClass!! 执行结果说明一个问题:子类调用父类变量的时候,子类没有初始化,因为此时的代码关系跟子类无关;子类初始化的时候,父类也没有再初始化,因为父类在当前方法体中已经初始化过了。接口与父类的唯一区别在于,接口初始化不会要求父接口,只有用到父接口才会初始化,同样的都会生成<clinit>类构造器。 这个时候加载类构造器<clinit>,会初始化类中所有变量,当然父类先于子类初始化 6、使用 加载完之后,该怎么样调用怎么样调用,TextView设置文字,ImageView设置图片等等 7、卸载 类不再被调用,如类被GC回收,虚拟机退出。 两个类是否相等,主要在于第一使用同一个加载器加载,第二全限定名地址一致 为什么要提出上面的问题呢?接下来要讲讲虚拟机的一个加载机制。 在虚拟机的角度来看,有两种类加载器,一种叫系统加载器(Bootstrap ClassLoader),一种叫自定义加载器(extends ClassLoader),这种呢又分为两个,一种叫应用加载器,一种叫扩展类加载器,一般默认为前者;而我们的应用程序加载主要由上面三个加载器相互配合完成的。三者的关系如Application-->Extension-->Bootsrap,双亲委派机制是指两两以组合的方式,子加载器先去调用父加载器的方法,没找到目标对象再去用子加载器 伪代码如下: loadClass(String name,boolean resolve){ Class c=findLoadedClass() if(c==null){ try{ if(parent !=null) c=parent.loadClass(name,false); else c=findBootstrapClassOrNull(name); }catch(ClassNotFoundException e){ } } if(c==null) c=findClass(name); } Java提倡我们去把自己调用类的逻辑写在findClass里,这样有助于双亲委派机制的正常使用。 破坏1、重写loadClass 破坏2、使用线程上下文加载器去让父加载器去调用子加载器的方法 破坏3、热加载 现在常用的做法是自定义类加载器并将原bug模块覆盖-OSGI 但由于自定义加载器之间的规则如果混乱,出现同时互相引用的问题,那么会最终找不到类,而出现线程死锁和内存泄露的问题。 关于热修复更多:http://blog.csdn.net/reboot123/article/details/53641127 下一节我们会讨论一下线程并发的问题。
博客出自:http://blog.csdn.net/liuxian13183,转载注明出处! All Rights Reserved ! 区别于C语言手动回收,Java自动执行垃圾回收,但为了执行高效,需要了解其策略,更好的去应用。 以下用HotSpot虚拟机为例,选取几个有意思的参数讲一下 1、默认GC时间为总时间的1%。也就是说GC线程设置有超时时间,防止卡死或过多妨碍主线程。 2、最高最低内存空闲比例分别为70%和40%。也就是说在小于70之后自动压缩,在大于40之后自动扩展。 3、最大内存为64M,达到即开始回收。 4、默认关闭来自System.gc()要求的垃圾回收。也就是说一般调用这个方法都没用。 5、默认Eden与Survivor的比例是8:1,Eden是主要存储区域,Survivor是次要的,配合老年代使用。 6、默认某对象经过15次GC后都没回收则进入老年代,默认老年代空间使用68%即被回收 Dalvik与JVM的最大差别在于,前者基于寄存器架构(句柄引用),后者基于栈架构(指针引用),也就是说前者处理速度更快。另外后者采用JIT的编译方式即时编译-也叫热加载,适用于J2EE;而前者采用AOT的加载方式,提前编译,虽然加载时间变长,但运行过程流畅,适用于J2ME,而IOS也是同样的原理。 新生代:一般是指大批对象产生的快,消亡的也快 老生代:一般是指大批对象产生后,不容易消亡 虚拟机底层回收算法有3种: 1、标记-清理(mark-sweep) 标记出所有需要回收的内存,然后统一清理; 2、复制算法(copying)上述5就是这种算法的逻辑,Eden和Survivor回收时,当剩余存活对象放到另一个Survivor里 3、标记-整理(mark-compact)标记出不需要回收的内存,复制到内存的一端,然后把另一端内存都回收掉 注:标记-清理、标记-整理均适用于老年代,复制算法适用于新生代。 特殊说明: 1、标记-清理劣势在于效率不高,因为要不断计算标记不活跃的对象;其次会产生很多碎片,不利于大数据对象的生成; 2、标记-整理适用于老年代,存活率高的话就特殊麻烦,而且如果内存是100%的存活就很麻烦,可能要几个虚拟机一起存储,这或许就是服务器集群的基础; 3、复制算法,也是对象存活率高的话,可能原来分配的Survivor区根本不够,也需要依赖其他内存块-或者直接将对象以持久化数据的形式存储在硬盘上,比起标记整理来说,内存利用率要高一些,因为标记整理差不多一半一半来分配空闲内存和活跃对象内存。 最后一种流行的垃圾回收机制,是分代收集算法,就是把前3种,按照新生代和老年代以及内存活跃度,采用相关比例及合适的回收机制来处理。以上就是垃圾回收机制的主要内容。 回收策略:由上述虚拟机最大内存和回收机制决定。其中引用计数也作为一种判断对象存活的算法,给对象添加一个引用计数器,有新地方引用就+1,不再引用就-1,为0时即可标记,稍后清除;但是,但是,但是,虚拟机并不是简单的使用这个算法来判断对象是否存活,对象引用、组合、聚合,最后循环引用,必然是行不通的,接下来了解更多。 GC Roots( 回收起点) 有以下几种 1、栈中引用的对象 2、方法区静态引用对象 3、方法区常用引用对象 4、JNI引用的对象 常见的由常量、方法区开始,搜索出使用过的路径,就叫引用链,当一个对象跟引用链没有连接,则标为可回收对象 同时该对象的finalize方法如果被调用过,或者未被覆盖(如果覆盖并被引用链引用,则会逃脱),则二次标记回收,正式进入回收队列F-Queue。而GC方法,仅是催促虚拟机加快回收,具体则由收集器的策略决定 以上两种第一种是人工代码层的回收策略,第二种是虚拟机的回收策略 那么基于上述各种机制的垃圾回收器有几种呢,接下来继续看 做技术至今,发现一个问题:没有万能的方法,只有最合适的方法。垃圾回收器也是如此,没有万能的通用垃圾回收器,只有最合适的垃圾回收器,偶尔两个回收器也会搭手共同完成任务。 下面的回收器,依次优化升级,越来越高级! Serial 最早的垃圾回收器,单线程操作,意思就是虚拟机启动1个小时得休息5分钟,用来做垃圾回收;现在也是处理器要分出一个来处理垃圾回收,不过变成多线程的;最初的产品设计,可以理解嘛,完成任务是第一要求,能用才能把事继续做下去。优点是,没有多线程交互的开销,只要不频繁,如果只有200M以内的新生代(注意老年代不行)内存,100毫秒以内可以回收。使用标记-清理算法(配第一种回收器),新生代收集器 ParNew,Serial的多线程版本,CPU越多,执行越高效,少的时候由于线程交互,效率未必比Serial高,但首次实现不等待操作(由于用户线程等级>垃圾回收线程)使用复制算法,新生代收集器 Parallel Scavenge 同ParNew一样,不同处在于它以吞吐量为先,运行代码时间/CPU总消耗时间,也就要尽可能要求更长的运行时间,减少GC的次数,这样用户的体验就会好一些。使用复制算法,新生代收集器。无法与CMS配合 Serial Old 单线程,使用标记-整理算法的老年代收集器-应用:1、与前一种回收器配合,2、作为CMS的后备方案 Parallel Old 多线程,使用标记-整理算法的老年代收集器 CMS,Concurrent Mark Sweep 使用标记-清理算法,以回收停顿时间最短为标准 ,因此体验较好,有并发标记、初始标记、重新标记、并发清除四个过程,初始和重新标记均会停下所有线程。 缺点有3 1、标记清除,容易使内存不连续,最后大对象很难生成; 2、跟CPU关系很大,开启的线程数=(CPU数量+3)/4; 3、无法处理浮动内存,因为并发收集,所以收集期间仍然会产生新的垃圾。标记整理和标记清除的算法都适用于老年代收集器 G1 Garbage First 将整个堆(新生代和老年代)划分成多块,跟踪垃圾堆集的程度,根据允许收集的时候,首先收集垃圾最多的区域。这样既能保证吞吐量,又能减少卡顿时间(指定只能收集多长时间),使用标记整理算法,适用老年代垃圾收集器。 内存由新生代的Eden(对象优先存储在这里)+Survivor+老年代组成,内存不足先执行Minor GC,如果内存仍然不够,让老年代担保,Survivor对象进入老年代,否则进行Full GC 一般大对象(byte[]、年龄大于15(Minor一次岁数+1)以上的对象或者Survivor空间相同年龄的总和大于空间一半,则直接进入老年代 最后再介绍一下引用,强引用、软引用、弱引用、虚引用 一般情况下常量都是强引用,软引用的对象在内存不足时会被回收,弱引用执行一次Minor GC即被回收,虚引用不会导致对象被回收,只是为了在该对象被回收时得到一个通知。 垃圾回收器是虚拟机的重要组成部分,另一块就是内存分配,还有线程并发、类等结构定义、文件加载等模块 常见虚拟机有三种: HotSpot(就是上面举例那种,Sun公司97年收购并作为默认虚拟机) JRockit(Bea公司02年收购,对服务器硬件和使用场景高度优化,即可编译,号称最快虚拟机) J9(IBM独立开发,作为支持其硬件销售的虚拟机,可以说并不开放) 其他Sun Classic(虚拟机鼻祖)Sun Exact(与HotSpot共存过的虚拟机,后被并入HotSpot) Apache Harmony(兼容1.5和1.6,催生安卓,由于TCK-技术兼容包问题,与Oracle决裂,退出JCP-Java委员会)意味着安卓是并非严格执行J2EE标准 嵌入虚拟机: Dalvik,名字来源于冰岛一个渔村,执行ART的dex而非JIT的class文件,使得它的运行速度更快 KVM,简单、轻量、高移植性,Android和IOS出现以前,广泛应用于手机平台,但运行较慢 CDC/CLDC Sun设计用来代替上面的虚拟机 下次咱们讲讲Dalvik的原理
博客出自:http://blog.csdn.net/liuxian13183,转载注明出处! All Rights Reserved ! 下文是博主感悟,请带着怀疑性的态度阅读! 需要了解基本变量所占内存大小,请移步:读书笔记-类结构的认识 Java存储空间有这么几块-来源于Java编程思想 寄存器:位于处理器内部,不受外层代码控制,由处理器自行分配-C/C++可以建议分配方式,使用句柄(包含引用类型和引用地址)来操作数据。 堆栈:位于RAM中 引用和基本数据类型存放的区块。 指针向下生成新对象,向上释放对象(new关键字),相当于链表结构。 堆:位于RAM中 对象存放的区块 常量存储:位于ROM中 存放于方法体中 非RAM存储:流对象和持久化数据-存储到硬盘 说到存储就难免讲到JVM的垃圾回收机制,需要了解的同学可以点进去看看 如果要实现处理器的高效率,那么就要压榨它的每一寸(byte)的运行能力,I3的处理器达到3.4GHz,即每秒运算3.4亿次,因此给它划分任务块,每块分配足够多的任务,实现高并发;所以对内存的模型需要详细了解。 由于硬件的读写速度与处理器的运算速度差距过大,一般都会写一层高速缓存来作为缓冲,一边从硬盘读数据到缓存,一边把处理器的处理结果写入缓存,一边把缓存中要写入的数据写到硬盘;因此很多程序会使用到中间件。 如果多个处理器同时处理缓存,就需要拟定协议谁先谁后,对于同一个处理器中的任务也是同样如此,有sychronzied关键字来处理;同时处理器还会对一段程序丧心病狂的进行(OOOE)乱序处理,也就是顺序在前面的代码并不一定先执行,对于依赖前段程序结果的代码来说,就需要通过其他途径来保证顺序性。 sychronized关键字的原子性在于,顺序执行到这一句时,当前线程被挂起,执行完毕再唤醒。 有时见到一种单例的写法 if(instance==null){ sychronized(Instance.class){ if(instance==null){ instance=new Instance(); } return instance; } } return instance; 这种的好处在于外层实现高性能并发,内层加if判断防止多个初始化同时进行(即第一个初始化完成,处理状态的其他线程再进去初始化一次)。 内存模型定义的关键在于第一使各处理器的操作不具有歧义 第二不影响拓展各自的特性;它主要定义虚拟机存取数据的细节,定义所有变量都存储在主内存,每条线程都有自己的工作内存(主内存的副本,或者叫引用),不同线程的工作内存互不直接访问,通过主内存来影响各自对值的引用;拿虚拟机来做例子,寄存器、栈、堆缓存就像工作内存,硬件设备就是主内存。 定义了八种操作来完成上述存取过程 lock和unlock 作用于主内存,标识为某线程独占或释放,成对存在 read和load 读取和加载,从主内存将数据读给工作内存,再加载到工作内存,成对存在 use和assign 使用和赋值 作用于工作内存,将变量给工作引擎,将接收到的值进行处理 成对存在 store和write 存储和写入 从工作内存将数据存回主内存,再写入主内存 成对存在 顺序过程unlock放到write后面即可。不允许读不入工作内存,也不允许写不入主内存;新变量只能在主内存中产生,不能跳级执行,lock与unlock一样重复执行多次,只是每次lock工作内存则被清空。lock可类比为Java的Lock对象。 讲完上面的存取过程,变量的原子性就很好讲了,原子性指对变量的存取过程顺序执行,要么执行完,要么不执行,不允许其他线程对其进行污染。而带有特殊含义的sychronzied和final关键字,就可以用原子性来解释:前者由于保障了unlock之前变量已同步到主内存,这里的变量指方法体或类中所有的;后者是避免构造器把this引用传递出去,因而像惰性气体一样稳定。 另外java的先行发生原则,很有意思,有以下几种表现形式 1、程序控制流顺序执行,即代码顺序执行 2、volitale和锁顺序执行,即前一个锁执行结束,后一个得到锁 3、Thread的start方法先于run方法内的方法执行 4、通过isAlive、interrupt和join方法判断线程是否存活 5、对象结束先于finilize方法执行 6、A先于B,B先于C,可得出A先于C执行的传递性。 最后再讲下volatile关键字,它有两个作用 1、保证改变后马上通知其他线程(执行write操作后,变量马上刷新),即对其他线程的可见性 2、保障上面所指丧心病狂的处理器处理此变量不被乱序操作,即禁止指令重排优化 但是volatile没有原子性(PS:原子性指read-assign-store这3组,只要一个执行,就会全部执行),不能保证作为计数器而正确存在;所以一般如果很少对它标识的变量进行改变的场景比较适用,比如多条线程共同执行多个有父类的任务,一个条件通知结束,则所有线程一起结束;就像劳动节来临,不论工程师还是设计师,都可以休息一天。 补充一点,64位的long和double无原子性,会被当成两个32位变量来处理,但一般默认为具有原子性,占用两个局部变量的位置 虚拟机运行时的数据区域有以下几种 虚拟机栈 主要存放引用和基本数据类型 堆 主要存放对象 方法区 常见的类信息除对象以外的所有,包括类信息(数据类型),常量池,方法、接口、静态变量等 本地方法栈 用来执行native方法 程序计数器 存储下一条需要执行的字节码指令,每条线程都有一个 虚拟机的多线程是通过线程切换并分配执行时间,同时一个内核在任一时刻只处理一条线程的指令 虚拟机栈和堆是线程共享的数据区,方法区、本地方法栈和程序计数器是线程所不能访问到的数据区 其中数据访问的方式有两种:一种是句柄形式,引用指向句柄,句柄包含对象地址和对象类型;一种是指针,直接存储对象地址,以句柄少一步,所以访问也会快一些,而HotSpot就是用这种;前者也有一定优化,值发生改变时,引用不用变,后者要改变指针才行。 内存异常有两种表现,一种叫OutOfMemoryError(内存溢出),请求的虚拟机扩展栈已无足够空间,分配给新对象,典型的标记-清理算法容易产品这种情况,另一种叫StackOverFlow,指请求深度超过限制。还有一种常见的Memory Leak(内存泄露),指已经申请的内存无法被回收。 接下来对中英文分别占多少字节进行解释 public static void main(String[] args) { String[] charsetNames = { "utf-8", "utf-16", "UTF-16BE", "UTF-16LE", "UTF-32", "UTF-32BE", "UTF-32LE", "unicode", "GBK", "GB2312", "GB18030", "ISO8859-1", "BIG5", "ASCII" }; for (int i = 0; i < charsetNames.length; i++) { printByteLength(charsetNames[i]); } } /** public static void printByteLength(String charsetName) { * String类的不带参数的getBytes()方法会以程序所运行平台的默认编码方式为准来进行转换, * 在不同环境下可能会有不同的结果,因此建议使用指定编码方式的getBytes(String charsetName)方法。 */ String a = "a"; // 一个英文字符 String b = "啊"; // 一个中文字符 try { System.out.println(); System.out.println(charsetName + "编码英文字符所占字节数:" + a.getBytes(charsetName).length); System.out.println(charsetName + "编码中文字符所占字节数:" + b.getBytes(charsetName).length); } catch (UnsupportedEncodingException e) { System.out.println("非法编码格式!"); } } utf-8编码英文字符所占字节数:1 utf-8编码中文字符所占字节数:3 utf-16编码英文字符所占字节数:4 utf-16编码中文字符所占字节数:4 UTF-16BE编码英文字符所占字节数:2 UTF-16BE编码中文字符所占字节数:2 UTF-16LE编码英文字符所占字节数:2 UTF-16LE编码中文字符所占字节数:2 UTF-32编码英文字符所占字节数:4 UTF-32编码中文字符所占字节数:4 UTF-32BE编码英文字符所占字节数:4 UTF-32BE编码中文字符所占字节数:4 UTF-32LE编码英文字符所占字节数:4 UTF-32LE编码中文字符所占字节数:4 unicode编码英文字符所占字节数:4 unicode编码中文字符所占字节数:4 GBK编码英文字符所占字节数:1 GBK编码中文字符所占字节数:2 GB2312编码英文字符所占字节数:1 GB2312编码中文字符所占字节数:2 GB18030编码英文字符所占字节数:1 GB18030编码中文字符所占字节数:2 ISO8859-1编码英文字符所占字节数:1 ISO8859-1编码中文字符所占字节数:1 BIG5编码英文字符所占字节数:1 BIG5编码中文字符所占字节数:2 ASCII编码英文字符所占字节数:1 ASCII编码中文字符所占字节数:1 Linux默认可以存放100个进程,放1个跟99个是一样的,其他均为sleep状态,意思是已经开辟这么大内存,用还是不用,反正都在那里放着,不会浪费CPU时间,因此后台进程只要不是说一直处于活动状态,跟IOS一样,无需杀死后台进程。 String str1 = "hello"; String str2 = "hello"; System.out.println(str1 == str2); // 对象是否是同一个 System.out.println(str1.equals(str2)); // 字符串的话是可以直接相等,跟int等其他数据类型一样 String strObj1 = new String("hello"); String strObj2 = new String("hello"); System.out.println(strObj1 == strObj2);// 是否是同一个对象 System.out.println(strObj1.equals(strObj2));// 对象相等,因为char全相等 执行结果: truetruefalsetrue 分析:拿String作为例子,其他容器类同样类似 public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; } 首先判断是否是同一个对象,即两个引用指向同一个对象,那自然相等; ==判断两者是否是同一个对象,equals判断值是否相等。 其次比较是否是同一种类型,如不等则返回false,如相等则继续; 最后比较内部的值,String的char,List的value,Map的key和value,如果完全相等则相等。
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! Java体系包括,各种版本的虚拟机,可执行文件,各种第三方类库,Java API类库,Java语言 发展史:91年James Gosling博士带领开发出Oak,95年互联网兴起来变成Java,并正式改名,并提出“一次编译到处执行”的口号。96年1.0正式发布,代表技术有Java虚拟机,AWT和Applet-以前很喜欢的Swing开发套件; 97年1.1,代表技术有Jar文件格式、JDBC、JavaBeans、RMI、内部类、反射等 98年1.2,拆分三个方向,就是J2ME,J2EE,J2SE 99年正式使用HotSpot,代表技术JIT、Collections集合类等 2000年1.3,代表技术有数学运算、TimeApi、JNDI(正式应用)等,此后2年发布一次 2002年1.4,走向成熟,正则表达式、XML解析器等出现 2004年1.5,自动装箱、泛型、注解、枚举、可变长参数、循环等语法 2006年1.6,提供动态语言支持、编译API和HTTP服务器API,对锁、同步、垃圾回收、类加载做了大量优化 2009年1.7,由于涉及sun收购,之后的发布节奏也被打乱,提供新的G1收集器、加强对非JAVA语言的调用、升级类加载架构 2012年1.8,支持接口有默认实现方法、支持Lamda表达式(android studio已支持)、TimerAPI的全新优化。 Java的类结构是对变量,各种访问标记,接口和继承,同步、枚举,注解,内部类,异常、方法等的组合 类文件是以8个字节为基础单位的二进制流 类文件和图片都有魔数,用来标识它可以被虚拟机接受,即0xCAFEBABE-全大写,只是为了方便大家记忆;在很多地方都有应用,因为文件后缀名可以改变,而它不能改变。 Java类文件的前4位代表它是否能被虚拟机接受,之后是版本号,5和6代表次版本号,7和8代表主版本号 紧接下来的是常量,大概有11种表结构,除了常见的String,Integer,Float,Double,Long以外,还有Class,Utf-8,Field,Method,InterfaceMethod,NameAndType类型,数量不确定 接着是访问标志有8种,public,final,abstract,interface,super,enum,annotation,synthetic(非用户代码产生的类) 接着是“继承”索引,this索引和super索引,除Object以外,其他索引值均不为0,代表有父类;索引集合描述这个类实现了哪些接口,从左到右按顺序排列,有几个计数器值为几 再接下来是字段表集合,描述所有的变量(如作用范围-public,并变性-final,并发可见-volatile等)和返回值(基本数据类型和类),基本数据类型的全限定名,开头都是其英文大写,如'[()V; '和'[[Ljava/lang/String';前者代表一个空返回值,后者代表一个String二维数组,类都以L开头作表示;全限定名可以理解为对象的地址。 接下来到方法表集合了,跟字段表一样,但某些访问控制答不能修饰方法,如volatile和transient关键字,方法名加尖括号表示,如<init>-指实例构造器,<cinit>-指类构造器,编译后的代码存在一个"Code"的属性里 属性表,类、方法体、常量表中都有属性表 1、Code属性指编译后的字节码,还有它的长度,栈的最大深度,最大存储空间,一个方法编译后不允许超过65535个字节码指令,一个指令=1Slot=32位,这是最小的存储单位,而float和int都是32位;一般还可以分为源码和元数据(即对源码的描述,如类,字段,方法等) public static int getFinally() { int a = 1; try { a = 2; return a; } catch (Exception e) { // TODO: handle exception a = 3; return a; } finally { a = 4; } } 上面是用一段源码,来对字节码指令进行分析,很多人看见后,都很想对照自己的结果,准确答案是2 跟你的结果是否一致,为什么是2而不是4呢? 这段代码可以分为三层来分析: 第一层如果try没有任何问题,则执行a=2 return a 第二层,如果try出错,则执行finally,再执行catch语句,a=3 return a 第三层,如果try无错,catch无错,而出现了其他error,则直接执行finally语句的a=4 这个地方可以这样理想,按照正常的Java指令没有打乱的情况下,先执行try return时已经执行a的write方法,则finally中仅执行了a的assign方法,此时a已经为2。不懂变量加载过程的,可以查看:Java高级之内存模型分析 2、Exceptions属性 列举出可检查出的异常如catch语句后和throws的异常 3、LineNumberTable属性 描写源码行号与字节码行号的关系,即这段代码位于哪一行,用于异常抛出时定位 4、LocalVariableTable属性 定义后别人引用,不会产生参数是arg0,arg1的情况 5、SourceFile属性,用于记录Class源码文件名称,主要异常抛出时可以定位是哪个文件 6、ConstantValue属性,通知虚拟机自动定义为静态变量,只能为基本数据类型和String类型 7、InnerClasses属性 显然定义内部类的,数量位置等 8、Deprecated及Synthetic 前者是定义废弃类,方法,变量等,后者指非源码产生由编译器自动加的东西。 Class文件是虚拟机解释的入口,了解其内部结构,有助于对代码进行分析定位错误,以及热加载等其他功能的理解。 以下是拓展知识 1位=1bit 最小的计算机单位,指0和1 byte=8bit 字节 取值范围 -128到127 记忆技巧 2^字节次方 char =2byte Unicode short=2byte 取值-2^15到2^15-1 int =4byte 取值-2^31到2^31-1 float=4byte long=8byte 取值-2^63到2^63-1 意义2个int或float double=8byte boolean 无意义 一个汉字是2byte,一个英文字母1byte(GBK和GB2312编码的环境中) 以上有个规律,就是次方数都是奇数,偶数达不到,同时咱们来测一下int的大小是不是2的31次方-1 public class TestInteger { public static void main(String[] args) { int size = 0x7fffffff;// Integer.MAX_VALUE Integer oral = 2; for (int i = 0; i < 4; i++) { oral *= oral; } for (int i = 0; i < 15; i++) { oral *= 2; } System.out.println(size == oral - 1); System.out.println(size); System.out.println(oral * -1); }} 输出结果: true2147483647-2147483648 得出证明:在电脑上,测算出int的结果跟虚拟机的设置一致(注意,不能把4写成5,要考虑到int存值大小)。 至于实验过程中,有人讲跟操作系统的位数有关。结果研究64位和32位的区别,发现:只是前者执行8个字节指令,后者执行4个;其次前者最大支持内存128,后者支持到4G(可使用3.25G)。但是java是运行在虚拟机,即其中的编译器上,一般现在都是4个字节,除非有特殊的处理器除外,可能是2个字节(必须大于等于short的长度)。因此,使用不同大小的处理器,可以设计出适应不同软件的更优硬件出来。 虚拟机的存储区域,一般常说的有栈和堆,栈放指针和基本数据类型,而堆放对象,还有一种需要熟悉的是常量存储,就是存代码内部(方法里的);还有两种拓展了解:寄存器位于处理器内部,极少,处理极快;非RAM存储,如持久化对象和流对象,特指数据流传输和硬盘对象。除了常量存储存在只读存储器(ROM-Only)和寄存器,其他都存在随机访问存储器(RAM-Acess)里。 位操作介绍四种,位与(&),位或(|),位否(~)和异或(^) 位与,同为1则为1,否则为0 位或,有一个1则为1,否同为0 位否,1变0,0变1 计算方式~x=-(x+1) 异或,不同则为1,相同则为0 int a = 0x010101; int b = 0x110100; System.out.println(a & b); System.out.println("位与操作:"+0x010100); System.out.println(a | b); System.out.println("位或操作:"+0x110101); System.out.println(a ^ b); System.out.println("异或操作:"+0x100001); 结果 65792位于操作:65792 1114369位或操作:1114369 1048577异或操作:1048577 Tips:负数操作 要先获得补码再操作,然后变源码+1操作 另外一个例子: System.out.println((1 & 0) + "," + (1 & 1) + "," + (0 & 0));//两个为1才为1,否则为0 System.out.println((1 | 0) + "," + (1 | 1) + "," + (0 | 0));//有一个为1则为1,否则为0 System.out.println((1 ^ 0) + "," + (1 ^ 1) + "," + (0 ^ 0));//只有一个为1才为1,否则为0 结果: 0,1,01,1,01,0,0 图片的大小=图片width*height*颜色深度 格式有四种(A-Alpha不透明度 R-Red G-Green B-Blue) ALPHA_8格式 8位=1byte ARGB_4444格式 16位=4+4+4+4=2byte RGB_565格式 16位=5+6+5=2byte ARGB_8888格式 32位=8+8+8+8=4byte 借用格式来压缩图片是一种不错的办法。 以下是图片魔数 图像文件头 1)1-2:(这里的数字代表的是"字",即两个字节,下同)图像文件头。0x4d42='BM',表示是Windows支持的BMP格式。(注意:查ascii表B 0x42,M0x4d,bfType 为两个字节,B为low字节,M为high字节所以bfType=0x4D42,而不是0x424D,但注意) 2)3-6:整个文件大小。4690 0000,为00009046h=36934。 3)7-8:保留,必须设置为0。 4)9-10:保留,必须设置为0。 5)11-14:从文件开始到位图数据之间的偏移量(14+40+4*(2^biBitCount))。4600 0000,为00000046h=70,上面的文件头就是35字=70字节。 位图信息头 6)15-18:位图图信息头长度。 7) 19-22:位图宽度,以像素为单位。8000 0000,为00000080h=128。 8)23-26:位图高度,以像素为单位。9000 0000,为00000090h=144。 9)27-28:位图的位面数,该值总是1。0100,为0001h=1。 10)29-30:每个像素的位数。有1(单色),4(16色),8(256色),16(64K色,高彩色),24(16M色,真彩色),32(4096M色,增强型真彩色)。1000为0010h=16。 11)31-34:压缩说明:有0(不压缩),1(RLE 8,8位RLE压缩),2(RLE 4,4位RLE压缩,3(Bitfields,位域存放)。RLE简单地说是采用像素数+像素值的方式进行压缩。T408采用的是位域存放方式,用两个字节表示一个像素,位域分配为r5b6g5。图中0300 0000为00000003h=3。 12)35-38:用字节数表示的位图数据的大小,该数必须是4的倍数,数值上等于(≥位图宽度的最小的4的倍数)×位图高度×每个像素位数。0090 0000为00009000h=80×90×2h=36864。 13)39-42:用象素/米表示的水平分辨率。A00F 0000为0000 0FA0h=4000。 14)43-46:用象素/米表示的垂直分辨率。A00F 0000为0000 0FA0h=4000。 15)47-50:位图使用的颜色索引数。设为0的话,则说明使用所有调色板项。 16)51-54:对图象显示有重要影响的颜色索引的数目。如果是0,表示都重要。 Java中的switch语句支持整型和枚举,整型包括byte,short,int,char 对象的hashcode存在的价值在于第一可以判断两个对象是否相等,第二用于容器类快速查找 闭包(封闭的包,只被当前主类使用):在一定的逻辑或者方法内存在的变量,安全性极高,应用在局部范围。 比如匿名内部类,且内部类引用对象必须是final的,因为两者引用地址相同,如果改变则两者数据不同步。 逻辑与(&&)与逻辑或(||) 如果多维布尔值比较,则先执行逻辑与,再执行逻辑或。 public static void main(String[] args) { System.out.print("false或:"); System.out.print(false || true && false); printBlank(); System.out.print(false || false && false);// 提示去掉最后一个 printBlank(); System.out.print(false || true && true); printBlank(); System.out.println(false || false && false);// 提示去掉最后一个 System.out.print("true或:"); System.out.print(true || false && false);// 提示去掉最后两个 printBlank(); System.out.print(true || true && false);// 提示去掉最后两个 printBlank(); System.out.print(true || false && true);// 提示去掉最后两个 printBlank(); System.out.print(true || true && true);// 提示去掉最后两个 System.out.println("\t"); System.out.print("false与:"); System.out.print(false && true || false);// 提示去掉中间一个 printBlank(); System.out.print(false && false || false);// 提示去掉中间一个 printBlank(); System.out.print(false && true || true);// 提示去掉中间一个 printBlank(); System.out.println(false && false || false);// 提示去掉中间一个 System.out.print("true与:"); System.out.print(true && false || false); printBlank(); System.out.print(true && true || false);// 提示去掉最后一个 printBlank(); System.out.print(true && false || true); printBlank(); System.out.print(true && true || true);// 提示去掉最后一个 } static void printBlank() { System.out.print("\t"); } 结果: false或: false false true falsetrue或: true true true true false与: false false true falsetrue与: false true true true
博客出自:http://blog.csdn.net/liuxian13183,转载注明出处! All Rights Reserved ! 签名过程: 1、创建签名keytool -genkey -v -keystore stone.keystore -alias stone -keyalg RSA -keysize 2048-validity 10000 生成签名文件2、为apk签名jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore stone.keystore unsigned.apk stone 不生成新文件3、检测apk是否签名jarsigner -verbose -certs -verify signed.apk4、优化apk-优化资源请求效率zipalign -f -v 4 signed_unaligned.apk signed_aligned.apk 以上是Apk的编译过程,可以根据这个过程来编译脚本,在服务器端自动编译apk 以下是7.0以前打包方式的总结。 今天谈一下Androdi三种打包方式,Ant、Gradle、Python。 当然最开始打包用Ant 很方便,后来转Studio开发,自带很多Gradle插件就用了它,然后随着打包数量越多,打包时间成了需要考虑的事,前两者平均打一个包要花费2-3分钟,打30个就要差不多2个小时;而前两者打包的思路主要是,替换AndroidManifest.xml的meta-data下的value值,然后重新编译 注:不管Ant还是Gradle,下面这句都要加入AndroidManifest.xml <meta-data android:name="UMENG_CHANNEL" android:value="${UMENG_CHANNEL_VALUE}"/> 而Python打包非常快,几百个包5分钟以内搞定,而它的思路仅是打完一个可发布包后,往apk的META-INF下写入一个含渠道名的文件,由应用去解析这个渠道名即可,不再使用传统的meta-data去标识value值。 编译一般有以下几个步骤: 1.用aapt命令生成R.java文件 2.用aidl命令生成相应java文件 3.用javac命令编译java源文件生成class文件 4.用dx.bat将class文件转换成classes.dex文件 5.用aapt命令生成资源包文件resources.ap_ 6.用apkbuilder.bat打包资源和classes.dex文件,生成unsigned.apk 7.用jarsinger命令对apk认证,生成signed.apk -------------------------------------------------------------我是分割线---------------------------------------------------------------------------- 一、简单讲一下Ant打包的流程1、安装ant,并配置好环境变量,在ant->lib目录下放入一个ant-contrib-1.0b3.jar 2、在主项目和依赖项目目录下放置build.xml和local.properties(依赖文件只用放sdk_dir就行) 3、在主项目目录下放置custom_rules.xml即可 4、在命令行下,进入要打包的主项目目录下,输入ant deploy即可(如果二次打包要先输入ant clean) build.xml文件如下 <?xml version="1.0" encoding="UTF-8"?> <project name="Project" default="help" > <property name="project_name" value="Project" /> <property name="base_jar_name" value="Common" /> <property name="aapt.ignore.assets" value="!.svn:!.git:\x3Cdir\x3E_*:.*:!CVS:!thumbs.db:!picasa.ini:!*.scc:*~:crunch" /> <!-- The local.properties file is created and updated by the 'android' tool. It contains the path to the SDK. It should *NOT* be checked into Version Control Systems. --> <property file="local.properties" /> <!-- The ant.properties file can be created by you. It is only edited by the 'android' tool to add properties to it. This is the place to change some Ant specific build properties. Here are some properties you may want to change/update: source.dir The name of the source directory. Default is 'src'. out.dir The name of the output directory. Default is 'bin'. For other overridable properties, look at the beginning of the rules files in the SDK, at tools/ant/build.xml Properties related to the SDK location or the project target should be updated using the 'android' tool with the 'update' action. This file is an integral part of the build system for your application and should be checked into Version Control Systems. --> <property file="ant.properties" /> <!-- if sdk.dir was not set from one of the property file, then get it from the ANDROID_HOME env var. This must be done before we load project.properties since the proguard config can use sdk.dir --> <property environment="env" /> <condition property="sdk.dir" value="${env.ANDROID_HOME}" > <isset property="env.ANDROID_HOME" /> </condition> <!-- The project.properties file is created and updated by the 'android' tool, as well as ADT. This contains project specific properties such as project target, and library dependencies. Lower level build properties are stored in ant.properties (or in .classpath for Eclipse projects). This file is an integral part of the build system for your application and should be checked into Version Control Systems.--> <loadproperties srcFile="project.properties" /> <!-- quick check on sdk.dir --> <fail message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable." unless="sdk.dir" /> <!-- Import per project custom build rules if present at the root of the project. This is the place to put custom intermediary targets such as: -pre-build -pre-compile -post-compile (This is typically used for code obfuscation. Compiled code location: ${out.classes.absolute.dir} If this is not done in place, override ${out.dex.input.absolute.dir}) -post-package -post-build -pre-clean --> <import file="custom_rules.xml" optional="true" /> <!-- Import the actual build file. To customize existing targets, there are two options: - Customize only one target: - copy/paste the target into this file, *before* the <import> task. - customize it to your needs. - Customize the whole content of build.xml - copy/paste the content of the rules files (minus the top node) into this file, replacing the <import> task. - customize to your needs. *********************** ****** IMPORTANT ****** *********************** In all cases you must update the value of version-tag below to read 'custom' instead of an integer, in order to avoid having your file be overridden by tools such as "android update project" --> <!-- version-tag: 1 --> <import file="${sdk.dir}/tools/ant/build.xml" /> </project> 主要作用:声明主项目和依赖项目,sdk的位置、用到的文件如local.properties等 local.properties ## This file is automatically generated by Android Studio. # Do not modify this file -- YOUR CHANGES WILL BE ERASED! # # This file must *NOT* be checked into Version Control Systems, # as it contains information specific to your local configuration. # # Location of the SDK. This is only used by Gradle. # For customization when using a Version Control System, please read the # header note. #Tue Feb 16 16:07:45 CST 2016 sdk.dir=AndroidSdk的位置,例如:D:\\Android_Software\\adt-bundle-windows-x86_64-20140702\\sdk sdk.platformtools=YourSdkPm sdk.tools=YourSdkTools apk.dir=打出包放的位置-打包前要确定此路径存在,且无中文 app_version=版本号 app_name=版本名称 market_channels=渠道号-以逗号隔开 key.store=密钥存储路径-注意双斜杠\\ key.store.password=密码 key.alias=别名 key.alias.password=别名密码 最重要的custom_rules.xml来了 <?xml version="1.0" encoding="UTF-8"?> <project name="custom_rules"> <taskdef resource="net/sf/antcontrib/antcontrib.properties"> <classpath> <pathelement location="ant-libs/ant-contrib-1.0b3.jar" /> </classpath> </taskdef> <target name="deploy"> <foreach delimiter="," list="${market_channels}" param="channel" target="modify_manifest"> </foreach> </target> <target name="modify_manifest"> <replaceregexp byline="false" encoding="UTF-8" flags="g"> <regexp pattern="android:name="UMENG_CHANNEL"([\w\W]*)android:value="(.*)"" /> <substitution expression="android:name="UMENG_CHANNEL" android:value="${channel}"" /> <fileset dir="" includes="AndroidManifest.xml" /> </replaceregexp> <property name="out.final.file" location="${apk.dir}/${app_name}${app_version}@${channel}.apk" /> <antcall target="clean" /> <antcall target="release" /> <antcall target="clean" /> </target> </project> 此文件配置获得打包命令,打包渠道,以及修改文件名,最后打包的过程《完》 -------------------------------------------------------------我是分割线---------------------------------------------------------------------------- 二、再讲一下Gradle打包的流程 1、配置好build.gradle,如下 2、studio命令行:gradlew assembleDebug --打非正式包gradlew assembleRelease --打正式包gradlew assembleWandoujiaRelease -打特定渠道 结束! android { signingConfigs { debug { keyAlias 'your_alias_key' keyPassword 'your_key_pwd storePassword 'your_store_pwd' storeFile file('your_store_key') } release { keyAlias 'your_alias_key' storeFile file('your_store_key') if (System.console() != null) { keyPassword System.console().readLine("\nKey password: ") storePassword System.console().readLine("\nKeystore password: ") } } } buildTypes { debug { //多余的参数 minifyEnabled false zipAlignEnabled false shrinkResources false signingConfig signingConfigs.debug // 显示Log buildConfigField "boolean", "LOG_DEBUG", "true" } release { minifyEnabled true//缩小 zipAlignEnabled true shrinkResources true//删除无用资源 signingConfig signingConfigs.release // 显示Log buildConfigField "boolean", "LOG_DEBUG", "false" applicationVariants.all { variant -> variant.outputs.each { output -> def outputFile = output.outputFile if (outputFile != null && outputFile.name.endsWith('.apk')) { // 输出apk名称为apkName_v1.0_wandoujia.apk def fileName = "apkName${defaultConfig.versionName}_${variant.productFlavors[0].name}.apk" output.outputFile = new File(outputFile.parent, fileName) } } } proguardFile 'your_cfg'--例:E:/SorkSpace/branches/studio/proguard.cfg } } productFlavors { baidu {} tencent {} } productFlavors.all { flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name] } compileSdkVersion 19 buildToolsVersion '22.0.1' sourceSets { main { manifest.srcFile 'AndroidManifest.xml' java.srcDirs = ['src'] resources.srcDirs = ['src'] aidl.srcDirs = ['src'] renderscript.srcDirs = ['src'] res.srcDirs = ['res'] assets.srcDirs = ['assets'] } // Move the tests to tests/java, tests/res, etc... instrumentTest.setRoot('tests') // Move the build types to build-types/<type> // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ... // This moves them out of them default location under src/<type>/... which would // conflict with src/ being used by the main source set. // Adding new build types or product flavors should be accompanied // by a similar customization. debug.setRoot('build-types/debug') release.setRoot('build-types/release') } defaultConfig { applicationId 'com.mayi.manager' versionCode 20 versionName '3.0' minSdkVersion 10 targetSdkVersion 19 // dex突破65535的限制 multiDexEnabled true // AndroidManifest.xml 里面UMENG_CHANNEL的value为 ${UMENG_CHANNEL_VALUE} // manifestPlaceholders = [UMENG_CHANNEL_VALUE: "channel_name"] } dexOptions { incremental true javaMaxHeapSize "4g" } packagingOptions { exclude 'META-INF/NOTICE.txt' exclude 'META-INF/LICENSE.txt' } lintOptions { abortOnError false } } 配置比Ant简单多了,当然在命令行也可以打包,只不过将gradle换成gradlew即可 其次将Gradle下载后,配置环境将Gradle/bin放入即可。 问题1、https://repo1.maven.org/maven2/com/android/tools/build/gradle/只支持最高2.1.3版本,日期2017.01.16 问题2、2.2.0以下出现, > org.gradle.api.internal.tasks.DefaultTaskInputs$TaskInputUnionFileCollection cannot be cast to org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection 因此暂时不使用命令行操作。 -------------------------------------------------------------我是分割线---------------------------------------------------------------------------- 三、Python打包 1、安装python软件 2、在项目中放入ChannelUtil.java类,用来获得渠道号 3、打好一个包放在与MultiChannelBuildTool.py同级目录 4、在.py同级目录info下的channel.txt添加渠道号 5、点击MultiChannelBuildTool.py即可 文件目录: 新包: ChannelUtil.java package com.blog.util; import java.io.IOException; import java.util.Enumeration; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.preference.PreferenceManager; import android.text.TextUtils; public class ChannelUtil { private static final String CHANNEL_KEY = "yourchannel"; private static final String CHANNEL_VERSION_KEY = "yourchannel_version"; private static String mChannel; /** * 返回市场。 如果获取失败返回"" * @param context * @return */ public static String getChannel(Context context){ return getChannel(context, ""); } /** * 返回市场。 如果获取失败返回defaultChannel * @param context * @param defaultChannel * @return */ public static String getChannel(Context context, String defaultChannel) { //内存中获取 if(!TextUtils.isEmpty(mChannel)){ return mChannel; } //sp中获取 mChannel = getChannelBySharedPreferences(context); if(!TextUtils.isEmpty(mChannel)){ return mChannel; } //从apk中获取 mChannel = getChannelFromApk(context, CHANNEL_KEY); if(!TextUtils.isEmpty(mChannel)){ //保存sp中备用 saveChannelBySharedPreferences(context, mChannel); return mChannel; } //全部获取失败 return defaultChannel; } /** * 从apk中获取版本信息 * @param context * @param channelKey * @return */ private static String getChannelFromApk(Context context, String channelKey) { //从apk包中获取 ApplicationInfo appinfo = context.getApplicationInfo(); String sourceDir = appinfo.sourceDir; //默认放在meta-inf/里, 所以需要再拼接一下 String key = "META-INF/" + channelKey; String ret = ""; ZipFile zipfile = null; try { zipfile = new ZipFile(sourceDir); Enumeration<?> entries = zipfile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = ((ZipEntry) entries.nextElement()); String entryName = entry.getName(); if (entryName.startsWith(key)) { ret = entryName; break; } } } catch (IOException e) { e.printStackTrace(); } finally { if (zipfile != null) { try { zipfile.close(); } catch (IOException e) { e.printStackTrace(); } } } String[] split = ret.split("_"); String channel = ""; if (split != null && split.length >= 2) { channel = ret.substring(split[0].length() + 1); } return channel; } /** * 本地保存channel & 对应版本号 * @param context * @param channel */ private static void saveChannelBySharedPreferences(Context context, String channel){ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); Editor editor = sp.edit(); editor.putString(CHANNEL_KEY, channel); editor.putInt(CHANNEL_VERSION_KEY, getVersionCode(context)); editor.commit(); } /** * 从sp中获取channel * @param context * @return 为空表示获取异常、sp中的值已经失效、sp中没有此值 */ private static String getChannelBySharedPreferences(Context context){ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); int currentVersionCode = getVersionCode(context); if(currentVersionCode == -1){ //获取错误 return ""; } int versionCodeSaved = sp.getInt(CHANNEL_VERSION_KEY, -1); if(versionCodeSaved == -1){ //本地没有存储的channel对应的版本号 //第一次使用 或者 原先存储版本号异常 return ""; } if(currentVersionCode != versionCodeSaved){ return ""; } return sp.getString(CHANNEL_KEY, ""); } /** * 从包信息中获取版本号 * @param context * @return */ private static int getVersionCode(Context context){ try{ return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionCode; }catch(NameNotFoundException e) { e.printStackTrace(); } return -1; } } MultiChannelBuildTool.py #!/usr/bin/python # coding=utf-8 import zipfile import shutil import os # 空文件 便于写入此空文件到apk包中作为channel文件 src_empty_file = 'info/yourchannel_.txt' # 创建一个空文件(不存在则创建) f = open(src_empty_file, 'w') f.close() # 获取当前目录中所有的apk源包 src_apks = [] # python3 : os.listdir()即可,这里使用兼容Python2的os.listdir('.') for file in os.listdir('.'): if os.path.isfile(file): extension = os.path.splitext(file)[1][1:] if extension in 'apk': src_apks.append(file) # 获取渠道列表 channel_file = 'info/channel.txt' f = open(channel_file) lines = f.readlines() f.close() for src_apk in src_apks: # file name (with extension) src_apk_file_name = os.path.basename(src_apk) # 分割文件名与后缀 temp_list = os.path.splitext(src_apk_file_name) # name without extension src_apk_name = temp_list[0] # 后缀名,包含. 例如: ".apk " src_apk_extension = temp_list[1] # 创建生成目录,与文件名相关 output_dir = 'output_' + src_apk_name + '/' # 目录不存在则创建 if not os.path.exists(output_dir): os.mkdir(output_dir) # 遍历渠道号并创建对应渠道号的apk文件 for line in lines: # 获取当前渠道号,因为从渠道文件中获得带有\n,所有strip一下 target_channel = line.strip() # 拼接对应渠道号的apk target_apk = output_dir + src_apk_name + "_" + target_channel + src_apk_extension # 拷贝建立新apk shutil.copy(src_apk, target_apk) # zip获取新建立的apk文件 zipped = zipfile.ZipFile(target_apk, 'a', zipfile.ZIP_DEFLATED) # 初始化渠道信息 empty_channel_file = "META-INF/yourchannel_{channel}".format(channel = target_channel) # 写入渠道信息 zipped.write(src_empty_file, empty_channel_file) # 关闭zip流 zipped.close() channel.txt baidu tencent