
应用中许多网页由于优化的不够理想,出现加载慢,加载时间长等,而且因为碎片化导致兼容性问题,有一些网页有视频内容,产品还提出各种小窗需求,搞得心力憔悴。找到公开的有crosswalk和x5webview,经过分析和研究决定上x5weview,他的好处有哪些呢? 1. TBS(腾讯浏览服务)的优势 1) 速度快:相比系统webview的网页打开速度有30+%的提升; 2) 省流量:使用云端优化技术使流量节省20+%; 3) 更安全:安全问题可以在24小时内修复; 4) 更稳定:经过亿级用户的使用考验,CRASH率低于0.15%; 5) 兼容好:无系统内核的碎片化问题,更少的兼容性问题; 6) 体验优:支持夜间模式、适屏排版、字体设置等浏览增强功能; 7) 功能全:在Html5、ES6上有更完整支持; 8) 更强大:集成强大的视频播放器,支持视频格式远多于系统webview; 9) 视频和文件格式的支持x5内核多于系统内核 10) 防劫持是x5内核的一大亮点 2. 运行环境 1)手机ROM版本高于或等于2.2版本 2)手机RAM大于500M,该RAM值通过手机 /proc/meminfo 文件的MemTotal动态获取 注:如果不满足上述条件,SDK会自动切换到系统WebView,SDK使用者不用关心该切换过程。 3. SDK尺寸指标 1)SDK提供的JAR包约250K 经过实际测试对一些页面的支持确实比原生的好,尤其是对视频的支持上面,解码和加载效果明显。而且对一些非常重的页面支持比较好,经常发现有一些在PC端的页面直接就甩过来丢到app上面,一看几十M,加载半天加载不出来,而且白屏,特别烦。关键webview还是有点问题的,有时候加载错误也不回调,进度直接卡死。 集成步骤: 1.导入jar包. 2.导入so库。这里需要注意的是,只提供了ameabli的,如果要求其他的直接考一个到读应的目录就行。 3.权限声明增加下面配置: <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> 4.替换到app中原先所有的webview,包括布局和动态创建的webview--x5webview。 5.初始化,上报错误可以不加。 QbSdk.PreInitCallback cb = new QbSdk.PreInitCallback() { @Override public void onViewInitFinished(boolean finished) { //x5內核初始化完成的回调,为true表示x5内核加载成功,否则表示x5内核加载失败,会自动切换到系统内核。 LogUtil.e("my", " onViewInitFinished is " + finished); } @Override public void onCoreInitFinished() { LogUtil.e("my", " onCoreInitFinished " ); } }; //x5内核初始化接口 try { QbSdk.initX5Environment(getApplicationContext(), cb); } catch (Exception e) { } CrashReport.UserStrategy strategy = new CrashReport.UserStrategy(getApplicationContext()); strategy.setCrashHandleCallback(new CrashReport.CrashHandleCallback() { public Map<String, String> onCrashHandleStart(int crashType, String errorType, String errorMessage, String errorStack) { LinkedHashMap<String, String> map = new LinkedHashMap<String, String>(); String x5CrashInfo = com.tencent.smtt.sdk.WebView.getCrashExtraMessage(getApplicationContext()); map.put("x5crashInfo", x5CrashInfo); return map; } @Override public byte[] onCrashHandleStart2GetExtraDatas(int crashType, String errorType, String errorMessage, String errorStack) { try { return "Extra data.".getBytes("UTF-8"); } catch (Exception e) { return null; } } }); 7.注意事项 如果需要使用播放器功能,需要播放的页面在清单文件中加入,不加的话,呵呵呵,比如小窗模式 在某些手机上就用不了 页面的Activity需要声明android:configChanges="orientation|screenSize|keyboardHidden" 为了避免闪烁的问题,在activity中要加入: getWindow().setFormat(PixelFormat.TRANSLUCENT);(这个对宿主没什么影响,建议声明) 不要去尝试调用 webview.setLayerType() webview.setDrawingCacheEnabled(true); 重要的点,关于websettings的设置,一定按照demo中的来,我的配置如下 WebSettings webSetting = this.getSettings(); webSetting.setJavaScriptEnabled(true); webSetting.setJavaScriptCanOpenWindowsAutomatically(true); webSetting.setAllowFileAccess(true); webSetting.setLayoutAlgorithm(LayoutAlgorithm.NARROW_COLUMNS); webSetting.setSupportZoom(true); webSetting.setBuiltInZoomControls(true); webSetting.setUseWideViewPort(true); webSetting.setSupportMultipleWindows(true); webSetting.setLoadWithOverviewMode(true); webSetting.setAppCacheEnabled(true); // webSetting.setDatabaseEnabled(true); webSetting.setDomStorageEnabled(true); webSetting.setGeolocationEnabled(true); webSetting.setAppCacheMaxSize(Long.MAX_VALUE); // webSetting.setPageCacheCapacity(IX5WebSettings.DEFAULT_CACHE_CAPACITY); webSetting.setAppCachePath(getContext().getDir("appcache", 0).getPath()); webSetting.setDatabasePath(getContext().getDir("databases", 0).getPath()); webSetting.setGeolocationDatabasePath(getContext().getDir("geolocation", 0).getPath()); webSetting.setPluginState(WebSettings.PluginState.ON_DEMAND); // webSetting.setRenderPriority(WebSettings.RenderPriority.HIGH); webSetting.setCacheMode(WebSettings.LOAD_NO_CACHE); //增加 // webSetting.setTextSize(WebSettings.TextSize.NORMAL); //支持混合模式 // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // webSetting.setMixedContentMode(android.webkit.WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); // } //接口禁止(直接或反射)调用,避免视频画面无法显示: // setLayerType(View.LAYER_TYPE_SOFTWARE,null); // setDrawingCacheEnabled(true); // this.getSettingsExtension().setPageCacheCapacity(IX5WebSettings.DEFAULT_CACHE_CAPACITY);//extension // settings 的设计 CookieSyncManager.createInstance(getContext()); CookieSyncManager.getInstance().sync(); 接入以后遇到一些诡异的问题: 1.小窗有些手机上无法使用,发现是清单文件声明少了导致的 2.发现拦截无网络的errordes和webview的不同 3.发现极简的app,在首次加载网页会出现加载不出来的问题,经过调试发现原来是x5webview还没有初始化完成,就在activity中调用了x5webview导致的,所以一定要注意预加载的回调。 4.关于线上问题的查漏补缺,记得带上x5webview的版本号 腾讯x5webview官网 腾讯x5webview论坛交流
动态权限对于谷歌来说从android6.0引入,对于国内的rom来说,这个题目不是好的选择题。因为大多数时候由于使用群众的层次不同,有些人在乎隐私的泄露,而更多的人却并不关心,使用了动态权限,增加了用户的交互对于中国市场来说,这并不是和本地化的设计。虽然有关部门也非常关注这些,新闻媒体也在报道隐私的泄露,但实际上要区别对待。这就造成了目前市场上有一些应用已经使用android6.0以上的编译版本完成了动态权限监测,而大多数应用仍在使用android6.0一下的编译版本。 对于手机厂商来,他们如何选择,也不同。比如早期的vivo、oppo,干脆不上6.0以上的rom,或者上了把动态权限这块阉割掉。这样用户用起来和以前没区别,加上他们的用户群体都是小白用户,完全不关心这些。而小米的就坑爹了,不仅有系统的权限设置,还有自己的设置页面的权限设置,这就导致了有时候权限是否授予状态不对,就是坑。华为的权限吧,有的干脆弄了默认值,简直就是马丹啊。更坑的就是搞了个智能权限设置,有的应用通过华为市场自动安装,自动授予了权限,而有的就是特喵的没有,如果有一天你的老板问你,为什么自己的应用就没有权限,需要授予,是不是技术不行?我建议你回答,不是技术不行,是老板不行,能硬的起来的都可以找他们合作授予,硬不起来的老板,都是这个吊样! 下面出个华为智能权限设置的图吧。
在一些场景下我们需要知道应用是否在前台显示,当不在前台显示的时候,一些后台进程可以暂时停止,比如一些查询任务、不必要的线程、不需要的渲染等,以减少对设备资源的占用。判断应用是否在前台通常可以使用一下方式: ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE); List<RunningTaskInfo> runnings = am.getRunningTasks(Integer.MAX_VALUE); for(RunningTaskInfo info : runnings){ if(info.topActivity.equals(activityName)){ Log.i("my","前台显示"); } } 因为系统api的变迁,也可以使用下面的方式: ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE); List<RunningAppProcessInfo> runnings = am.getRunningAppProcesses(); for(RunningAppProcessInfo running : runnings){ if(running.processName.equals(getPackageName())){ if(running.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND || running.importance == RunningAppProcessInfo.IMPORTANCE_VISIBLE){ //前台显示... }else{ //后台显示... } break; } } 这里后者判断加上了可见。比如,当用户点击了home键,这时候方法1和方法2都可以判断出来处于后台显示,然后再点击应用 再快速的打开其他的应用,这时候我们的应用就被其他应用盖在上面了,而方法1和方法2判断的结果都是在前台显示,这种情况下,就无法做出正确的判断, 通过研究和测试,发现使用下面的方式可以正确判断出来,方法如下: public boolean isAppOnForeground(Context context) { ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); boolean isOnForground = false; List<ActivityManager.RunningAppProcessInfo> runnings = am.getRunningAppProcesses(); for (ActivityManager.RunningAppProcessInfo running : runnings) { if (running.processName.equals(getPackageName())) { if (running.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND || running.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE) { //前台显示... Log.e("my", "前台显示"); isOnForground = true; } else { //后台显示... Log.e("my", "后台显示"); isOnForground = false; } break; } } String currentPackageName = ""; if (am.getRunningTasks(1).size() > 0) { ComponentName cn = am.getRunningTasks(1).get(0).topActivity; currentPackageName = cn.getPackageName(); } // Log.e("my", "isAppOnForeground :" + currentPackageName + " getPackageName:" + getPackageName()); // return !TextUtils.isEmpty(currentPackageName) && currentPackageName.equals(getPackageName()); return isOnForground; } 下面的部分代码虽然没用到,但是不可省略,删去则不能正确判断。产生这个问题的原因,可能是因为线程管理进入list中时,必须需要同步安全的操作,找到对应的源代码如下: public int addAppTask(@NonNull Activity activity, @NonNull Intent intent, @Nullable TaskDescription description, @NonNull Bitmap thumbnail) { Point size; synchronized (this) { ensureAppTaskThumbnailSizeLocked(); size = mAppTaskThumbnailSize; } final int tw = thumbnail.getWidth(); final int th = thumbnail.getHeight(); if (tw != size.x || th != size.y) { Bitmap bm = Bitmap.createBitmap(size.x, size.y, thumbnail.getConfig()); // Use ScaleType.CENTER_CROP, except we leave the top edge at the top. float scale; float dx = 0, dy = 0; if (tw * size.x > size.y * th) { scale = (float) size.x / (float) th; dx = (size.y - tw * scale) * 0.5f; } else { scale = (float) size.y / (float) tw; dy = (size.x - th * scale) * 0.5f; } Matrix matrix = new Matrix(); matrix.setScale(scale, scale); matrix.postTranslate((int) (dx + 0.5f), 0); Canvas canvas = new Canvas(bm); canvas.drawBitmap(thumbnail, matrix, null); canvas.setBitmap(null); thumbnail = bm; } if (description == null) { description = new TaskDescription(); } try { return ActivityManagerNative.getDefault().addAppTask(activity.getActivityToken(), intent, description, thumbnail); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }
移动智能设备的快速普及,给生活带来巨大的精彩,但是智能设备上用户的信息数据很多,隐私数据也非常多,各种各样的app可能通过各种方式在悄悄的收集用户数据,而用户的隐私就变得耐人寻味了。比如之前的可以无限制的读取用户的联系人数据、短信记录、通话记录等,这些信息一旦泄露,可能就会造成重大财产损失。腾讯社会研究中心和DCCI互联网数据中心日前发布《2017年度网络隐私安全及欺诈行为研究分析报告》,《报告》显示,98.5%安卓手机APP存在获取用户隐私权限问题,iOS应用获取用户隐私权限也达到81.9%。用户从来无法感知这些数据收集行为,也发确定行为会触及到隐私数据,而为了保护用户隐私,谷歌android6.0加入了隐私信息获取的权限申请。其主要的目的就是在涉及到需要使用隐私数据的时候,进行权限申请。制定的权限策略分为两类:第一类是不涉及用户隐私的,只需要在manifest中声明即可,比如网络,蓝牙,NFC等,第二类是涉及用户隐私信息的,需要用户授权后才可使用的,比如SD卡读写,联系人,短信读写等等。 接下来我从当前市场上一线大厂的应用进行分析对比,主要是淘宝、京东、网易新闻、今日头条、哔哩哔哩、腾讯新闻,分析和对比的维度从启动权限申请数量、运行权限申请、交互提示等方面进行。 1.启动权限申请 app 启动权限申请 说明 淘宝 电话权限 不授予电话权限无法进入应用。 当选择不授予权限,则程序在获得操作结果以后继续申请,当选择不再提示,引导进入设置页面进行开启权限。 京东 电话权限 不授予电话权限无法进入应用。 当选择不授予权限,则程序在获得操作结果以后继续申请,当选择不再提示,引导进入设置页面进行开启权限。 网易新闻 无 进入首页以后申请, 电话权限 存储权限 位置权限 禁止也不影响应用正常使用,下次启动的时候还会继续申请未授权的权限。当选择不再提示,则在下一次启动应用时引导用户进入设置页面进行开启权限。 今日头条 电话权限 不授予电话权限也可以进入应用,选择禁止下次启动应用会继续申请,当选择不再提示,下次启动应用不提示。 哔哩哔哩 无 直接进入应用 腾讯新闻 无 进入应用后进行申请位置权限和电话权限 从启动权限申请来看,淘宝、京东作为电商派app,作风比较强硬,不授权则无法进入应用,相对来说,新闻类app这方面就做的比较好,其中网易新闻和腾讯新闻比较突出,腾讯新闻申请的数量比较少。而哔哩哔哩的实现是最理想的,启动到进入应用全程无申请,而是到了具体使用的时候在申请这一轮比较哔哩哔哩胜出。 2.所有敏感权限比较 app 所有权限 说明 淘宝 存储 位置 日历 电话 相机 通讯录 麦克风 京东 存储 位置 电话 相机 通讯录 麦克风 网易新闻 存储 位置 电话 相机 通讯录 麦克风 今日头条 存储 位置 电话 相机 通讯录 麦克风 哔哩哔哩 存储 位置 电话 相机 麦克风 腾讯新闻 存储 位置 电话 相机 通讯录 麦克风 从涉及到的主要权限来看,淘宝的权限数量是最多的,多了一个日历的权限,说明淘宝有一些业务需要在对应日期进行的必要操作,涉及到对日历的修改。其他的app权限申请大同小异,部分需要通讯录的权限,可能涉及到用户账号的问题,应该是有和手机厂商合作,可以使用android系统的账号登录,这种业务合作能力也不是一般的app所能具备的,与此同时肯定还有类似白名单的业务合作也是同样情况。其中又可以对比的出来哔哩哔哩真的是良心作品,没有那么多的权限,精简实在。 3.运行时权限检查比较 这里选取一个场景就是拍照的相机权限作为比较。 应用 交互 说明 淘宝 点击二维码扫描,弹出系统权限申请,选择禁止相机权限,再次点二维码扫描,仍然会提示申请,但是如果勾选不再提示,则会闪一下一个白色页面,交互不理想。见图1 京东 点击头像,首先弹出自定义的框进行一些说明,然后点击下一步,才进行系统权限申请。党选择禁止,再次点击头像,仍然会提示申请。如果勾选不再提示。则会引导用户进入设置页面。 网易新闻 点击头像,首先弹出自定义的框进行一些说明,然后点击下一步,才进行系统权限申请。党选择禁止,再次点击头像,仍然会提示申请。如果勾选不再提示。则会引导用户进入设置页面。 今日头条 点击头像,首先弹出自定义的框进行一些说明,然后点击下一步,才进行系统权限申请。党选择禁止,再次点击头像,仍然会提示申请。如果勾选不再提示。则会引导用户进入设置页面。 哔哩哔哩 点击头像,首先弹出自定义的框进行一些说明,然后点击下一步,才进行系统权限申请。党选择禁止,再次点击头像,仍然会提示申请。如果勾选不再提示。点击无交互或者提示。 腾讯新闻 点击头像无反应,这个牛逼了 我猜是个bug。。。。 从交互设计上看,京东、网易、今日头条的处理是最好的,在进行权限申请的时候最好加一个自定义的提示弹窗,给用户知情权,虽然还是有少部分用户不同意授权,但是从设计交互上做到的用户知情。看的出来有一些app存在一些缺陷。 4.通用组件的权限说明。 从一个app产品角度来看,肯定会存在下面的组件:分享组件、统计组件、推送组件,可能存在支付、地图,那么这些组件又需要那些敏感权限呢,下面列举一下。 组件 权限 说明 sharesdk 存储 电话 友盟 存储 定位 电话 地图 存储 定位 个推 存储 电话 综上所述:权限的申请和业务是有必然的关联的,如果不能放弃某个启动时就必须进行的业务,那么权限申请必然需要进行。上面分析可以看到出来淘宝和京东在启动时,就有电话的权限申请,说明应用重视某项业务,这个业务必须要进行初始。而新闻类app相对来说拥有较大的空间,对比如推送、统计类组件的初始和数据可以不那么重视,或者是比如推送有其他的途径可以不用那么早的初始化。就交互体验上来说,我觉得最舒服的还是哔哩哔哩,如果他在勾选不再提示后的处理优化就更好额。
续上一篇,开发图片二维码识别功能后,我们对功能进行性能分析内存占用显著提高了,不使用该功能内存占用大约是147M,使用这个功能多次以后,高达203M。 因此对功能进行研究,发现每次生成的图片没有即时的释放,导致内存中的图片不断累积,内存占用不断攀升。因此,需要对图片进行释放,释放的时候需要特别关注的地方有: 1.释放注意图片的状态。 2.注意异常的捕获。 下面就是图片释放的有关代码。 /** * 回收bitmap */ public static void recycleBitmap(Bitmap bitmap ) { if(bitmap != null && !bitmap.isRecycled()){ bitmap.recycle(); bitmap = null; } } 对于异常的捕获主要是需要关注图片在进行encode和decode过程中的处理,原来的方法应该改为如下: public static Result handleQRCodeFormBitmap(Bitmap bitmap) { Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class); hints.put(DecodeHintType.CHARACTER_SET, "utf-8"); hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE); hints.put(DecodeHintType.POSSIBLE_FORMATS, BarcodeFormat.QR_CODE); RGBLuminanceSource source = null; QRCodeReader reader2 = null; Result result = null; try { source = new RGBLuminanceSource(bitmap); BinaryBitmap bitmap1 = new BinaryBitmap(new HybridBinarizer(source)); reader2 = new QRCodeReader(); result = reader2.decode(bitmap1, hints); } catch (Exception e) { e.printStackTrace(); if (source != null && reader2 != null) { BinaryBitmap bitmap2 = new BinaryBitmap(new GlobalHistogramBinarizer(source)); try { result = reader2.decode(bitmap2, hints); } catch (Exception e1) { e1.printStackTrace(); } } } return result; } 当然对于整个流程来说还有其他的优化方法,比如将保存的图片格式压缩比例都进行调整,在不影响识别的前提下,将图片进行处理,这样既能节省cpu时间又能节省内存开销。 如果大家有其他更好的方案,欢迎提出。
最新业务开发二维码识别的功能,这个功能,在很多应用上都有,比如微信长按图片识别二维码,如果图片中存在可以识别的二维码时,可以增加一个选项 识别二维码。那么如何去实现这个功能呢。这里其实也非常简单,首先对图片进行二维码识别,识别结果返回的时候判断是否有二维码,有则增加识别二维码选项。 对于识别二维码,目前主流的就是zxing和zbar,对于这两者的选型,一般来说移动智能手机更多选择zxing,因为zxing更适合,zbar适合嵌入式硬件,两者对于QR图形码能力相差无几,但是zxing的资料明显更多一些。各位根据自己的情况进行调整。 二维条码/二维码(2-dimensional bar code)是用某种特定的几何图形按一定规律在平面(二维方向上)分布的黑白相间的图形记录数据符号信息的。 在代码编制上巧妙地利用构成计算机内部逻辑基础的“0”、“1”比特流的概念,使用若干个与二进制相对应的几何形体来表示文字数值信息,通过图象输入设备或光电扫描设备自动识读以实现信息自动处理:它具有条码技术的一些共性:每种码制有其特定的字符集。 每个字符占有一定的宽度;具有一定的校验功能等。同时还具有对不同行的信息自动识别功能、及处理图形旋转变化点。 对于信息量越大的二维码图形点越多,多到一定程度识别就困难了,二维码有一定的纠错能力,但是不是万能的。 首先给出识别二维码的实现,二维码识别一组数据。 /** * 对二维码图片解码结果进行编码 * * @param str * @return */ public static String recode(String str) { String formart = ""; try { boolean ISO = Charset.forName("ISO-8859-1").newEncoder() .canEncode(str); if (ISO) { formart = new String(str.getBytes("ISO-8859-1"), "UTF-8"); } else { formart = str; } } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (Exception e){ } return formart; } 其次,二维码生成的代码如下: public static Result handleQRCodeFormBitmap(Bitmap bitmap) { Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class); hints.put(DecodeHintType.CHARACTER_SET, "utf-8"); hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE); hints.put(DecodeHintType.POSSIBLE_FORMATS, BarcodeFormat.QR_CODE); RGBLuminanceSource source = new RGBLuminanceSource(bitmap); BinaryBitmap bitmap1 = new BinaryBitmap(new HybridBinarizer(source)); QRCodeReader reader2 = new QRCodeReader(); Result result = null; try { result = reader2.decode(bitmap1, hints); } catch (Exception e) { e.printStackTrace(); if (source != null) { BinaryBitmap bitmap2 = new BinaryBitmap(new GlobalHistogramBinarizer(source)); try { result = reader2.decode(bitmap2, hints); } catch (Exception e1) { e1.printStackTrace(); } } } return result; } 对返回的result进行处理,如果未能识别,弹框中则无识别二维码选项。 而对于图片会有保存的功能,图片手进行进行命名,然后设定保存路径,创建好路径,最后将图片存入文件,根据需要是否需要进行压缩,可以设定相关参数。保存以后通知系统更新图片库。 保存图片的代码如下: public static void saveImageToGallery(Context context, Bitmap bmp) { String fileName = System.currentTimeMillis() + ".jpg"; String filePath = FileUtils.getFilePath(context, "/pictures/qrcode/"); // 首先保存图片 File appDir = new File(filePath); if (!appDir.exists()) { appDir.mkdir(); } File file = new File(appDir, fileName); try { FileOutputStream fos = new FileOutputStream(file); bmp.compress(Bitmap.CompressFormat.JPEG, 100, fos); fos.flush(); fos.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (Exception e){ } // 其次把文件插入到系统图库 ContentValues values = new ContentValues(); values.put(MediaStore.Images.Media.DATA, file.getAbsolutePath()); values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); Uri uri = context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); // 最后通知图库更新 context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file))); } 到这里二维码识别和图片保存的功能就算基本完成了。 但是,经过实际的测试发现,这种方式只管功能,没有兼顾性能,进行多次操作导致内存使用增加30%,下一篇将描述分析过程和解决方法。
从项目的整体风格考虑,对所有类要进行必要的说明,就注释说明来说首先需要说明是作者,文件创建时间,业务功能说明,这几项是基本的内容,而添加这些说明内容以前可能手动的添加文件标题头,这种做法现在都非常过时了。 通过studio可以配置有关的file header达到自动添加的目的。 配置的方式如下图 配置代码如下: /** * Created by ${USER} on ${DATE} ${TIME}. * describe 该类主要完成以下功能 * 1.主要功能 */ 下面讨论下这个类还可能需要那些说明。 首先应该有版本变迁说明,每个版本发生了那些变动,增、改、删了那些业务功能,并对这些修改内容进行必要的描述。 其次应该有修订人说明,以及修订人联系方式,方便日后沟通。 因此,综上所述,file header设置为下面。 /** * Created by ${USER} on ${DATE} ${TIME}. * describe 该类主要完成以下功能 * 1.主要功能 * change info: v1.0 创建 * v1.1 增加直播功能 * modify by tony tony@126.com * */ 一家之言,供大家参考。
android 6.0以上为了保护用户的隐私,和以往被人诟病的权限机制,确立了新的权限机制。从 Android 6.0(API 级别 23)开始,用户开始在应用运行时向其授予权限,而不是在应用安装时授予。此方法可以简化应用安装过程,因为用户在安装或更新应用时不需要授予权限。它还让用户可以对应用的功能进行更多控制;例如,用户可以选择为相机应用提供相机访问权限,而不提供设备位置的访问权限。用户可以随时进入应用的“Settings”屏幕调用权限。 谷歌将权限分两类:1.正常权限。2.危险权限。 正常权限: 正常权限不会直接给用户隐私权带来风险。如果应用在其清单中列出了正常权限,系统将自动授予该权限。 危险权限: 危险权限会授予应用访问用户机密数据的权限。如果您的应用在其清单中列出了正常权限,系统将自动授予该权限。如果列出了危险权限,则用户必须明确允许应用使用这些权限。 那么对于一个应用要关注的就是那些危险权限,也就是我们所说的敏感权限。下表列出来有关的危险权限。 权限名称 1.calendar 2.camera 3.contacts 4.location 5.microphone 6.phone 7.senors 8.sms 9.storage 兼容性来说,有以下细节: 1.低于API23 也就是系统6.0以下的,在manifest中注册申明权限清单,则自动授予权限,但是用户可以去设置中心关闭权限,但是不会引起应用运行异常 2.API23系统6.0以上的,需要在使用的时候去申请,没有申请就去使用则会引起应用运行异常。权限失效会导致 SecurityException 。 3.一开始是6.0以下系统应用,做了动态权限以后,会自动授予原先已有的权限,如果有新增的危险权限,需要做申请。 4.一开始就做了动态权限,再换回到6.0以下编译环境,新的apk无法安装。 下面以一个流程图说明问题。 危险权限的表单如下: 申请权限的工具类如下: package com.nfdaily.nfplus.util; import android.Manifest; import android.app.Activity; import android.content.pm.PackageManager; import android.os.Build; import android.support.v4.app.ActivityCompat; /** * Created by xilinch on 2017/5/3. * 对关键的权限进行申请 calendar /camera/ contacts/location/microphone /phone/senors/sms/storage * 增加对版本号的判断,大于等于23 (6.0)以上才进行权限的申请 */ public class UtilRequestPermissions { /** * 拨打电话的请求码 */ public static final int REQUEST_CODE_CALL_PHONE = 0x9001; /** * 存储 */ public static final int REQUEST_CODE_READ_EXTERNAL_STORAGE = 0x9002; /** * 发送短信 */ public static final int REQUEST_CODE_SEND_SMS = 0x9003; /** * 传感器 */ public static final int REQUEST_CODE_BODY_SENSORS = 0x9004; /** * 录音 */ public static final int REQUEST_CODE_RECORD_AUDIO = 0x9005; /** * 定位 */ public static final int REQUEST_CODE_ACCESS_COARSE_LOCATION = 0x9006; /** * 相机 */ public static final int REQUEST_CODE_CAMERA = 0x9007; /** * 读取日历 */ public static final int REQUEST_CODE_READ_CALENDAR = 0x9008; /** * 录音 */ public static final int REQUEST_CODE_READ_CONTACTS = 0x9009; /** * 请求权限 * @param activity * @param permission * @param requestCode */ public static void requestPermission(Activity activity, String[] permission, int requestCode){ ActivityCompat.requestPermissions(activity, permission, requestCode); } /** * 检查权限 * @param activity * @param permission * @return */ public static boolean checkSelfPermission(Activity activity, String permission){ boolean isGranted = ActivityCompat.checkSelfPermission(activity, permission) == PackageManager.PERMISSION_GRANTED; return isGranted; } /** * 日历 * @param activity * @param requestCode */ public static void requestPermissionCalendar(Activity activity,int requestCode){ if(Build.VERSION.SDK_INT >= 23){ if(requestCode == 0){ requestPermission(activity, new String[]{Manifest.permission.READ_CALENDAR}, REQUEST_CODE_READ_CALENDAR); } else { requestPermission(activity, new String[]{Manifest.permission.READ_CALENDAR}, requestCode); } } } /** * 照相 * @param activity * @param requestCode */ public static void requestPermissionCamera(Activity activity,int requestCode){ if(Build.VERSION.SDK_INT >= 23){ if(requestCode == 0){ requestPermission(activity, new String[]{Manifest.permission.CAMERA}, REQUEST_CODE_CAMERA); } else { requestPermission(activity, new String[]{Manifest.permission.CAMERA}, requestCode); } } } /** * 联系人 * @param activity * @param requestCode */ public static void requestPermissionContacts(Activity activity,int requestCode){ if(Build.VERSION.SDK_INT >= 23){ if(requestCode == 0){ requestPermission(activity, new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_CODE_READ_CONTACTS); } else { requestPermission(activity, new String[]{Manifest.permission.READ_CONTACTS}, requestCode); } } } /** * 定位 * @param activity * @param requestCode */ public static void requestPermissionLocation(Activity activity,int requestCode){ if(Build.VERSION.SDK_INT >= 23){ if(requestCode == 0){ requestPermission(activity, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, REQUEST_CODE_ACCESS_COARSE_LOCATION); } else { requestPermission(activity, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, requestCode); } } } /** * 录音 * @param activity * @param requestCode */ public static void requestPermissionMicrophone(Activity activity,int requestCode){ if(Build.VERSION.SDK_INT >= 23){ if(requestCode == 0){ requestPermission(activity, new String[]{Manifest.permission.RECORD_AUDIO}, REQUEST_CODE_RECORD_AUDIO); } else { requestPermission(activity, new String[]{Manifest.permission.RECORD_AUDIO}, requestCode); } } } /** * 传感器 * @param activity * @param requestCode */ public static void requestPermissionSensors(Activity activity,int requestCode){ if(Build.VERSION.SDK_INT >= 23){ if(requestCode == 0){ requestPermission(activity, new String[]{Manifest.permission.BODY_SENSORS}, REQUEST_CODE_BODY_SENSORS); } else { requestPermission(activity, new String[]{Manifest.permission.BODY_SENSORS}, requestCode); } } } /** * 短信 * @param activity * @param requestCode */ public static void requestPermissionSms(Activity activity,int requestCode){ if(Build.VERSION.SDK_INT >= 23){ if(requestCode == 0){ requestPermission(activity, new String[]{Manifest.permission.SEND_SMS}, REQUEST_CODE_SEND_SMS); } else { requestPermission(activity, new String[]{Manifest.permission.SEND_SMS}, requestCode); } } } /** * 存储 * @param activity * @param requestCode */ public static void requestPermissionStorage(Activity activity,int requestCode){ if(Build.VERSION.SDK_INT >= 23){ if(requestCode == 0){ requestPermission(activity, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_CODE_READ_EXTERNAL_STORAGE); } else { requestPermission(activity, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, requestCode); } } } /** * 电话 有关,包括READ_PHONE_STATE /CALL_PHONE /READ_CALL_LOG/WRTITE_CALL_LOG/ADD_VOICEMAIL/USE_SIP/PROCESS_OUTGOING_CALLS * @param activity * @param requestCode */ public static void requestPermissionPhone(Activity activity,int requestCode){ if(Build.VERSION.SDK_INT >= 23){ if(requestCode == 0){ requestPermission(activity, new String[]{Manifest.permission.READ_PHONE_STATE}, REQUEST_CODE_CALL_PHONE); } else { requestPermission(activity, new String[]{Manifest.permission.READ_PHONE_STATE}, requestCode); } } } } 在下一部分将描述项目中实际处理的情况以及对比京东和淘宝的处理方案。
在项目中遇到一个问题,在webveiw和原生之间进行传值的时候,出现了一些encode的小问题。看起来很简单的问题,实际上却存在不小的坑。 首先说一下目前项目的结构,在一个activity中,webview和原生之间有多种交互。 如图所示 在原生调用webview方法,这种协议已经非常常用了,直接调用loadJS();但是自定义协议这个过程,使用的拦截跳转的方式,按照预定的协议来解析数据,这里面就有一些情况,比如数据中出现了中文,webview会encode这部分内容,这就要求我们对拦截以后的内容进行decode。 见下方代码: 1 private String decodeUrl(String url) { 2 try { 3 return URLDecoder.decode(url, "utf-8"); 4 } catch (UnsupportedEncodingException e) { 5 e.printStackTrace(); 6 } 7 return ""; 8 } 上面这段代码,看上去是很合理的,但是老司机们认真看看,这里面有坑。 首先看一下底层代码的decode。 /** * @throws UnsupportedEncodingException if {@code charsetName} is not supported. */ public static String decode(String s, String charsetName) throws UnsupportedEncodingException { return UriCodec.decode(s, true, Charset.forName(charsetName), true); } 我想老司机们应该已经明白了是为啥了,就是检查异常和运行时异常的问题了。UnsupportedEncodingException 仅仅是检查时异常,而可能还有运行时异常,因此这里代码需要改为: 1 private String decodeUrl(String url) { 2 String decodeUrl = ""; 3 try { 4 decodeUrl = URLDecoder.decode(url, "utf-8"); 5 } catch (Exception e) { 6 e.printStackTrace(); 7 } finally { 8 return decodeUrl; 9 } 10 } 这里对异常进行简单的介绍。 在 Java 中,所有的异常都继承了 Throwable(可抛出)。Throwable 指定代码中可用异常传播机制通过 Java 应用程序传输的任何问题的共性。 Throwable: 有两个重要的子类:Exception(异常)和 Error(错误),二者都是 Java 异常处理的重要子类,各自都包含大量子类。 Error(错误):是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。 。这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。 Exception(异常):是程序本身可以处理的异常。见图: 运行时异常很常见比如 空指针、非法参数、数组越界、类转换异常、算术异常等。这些异常需要老司机们有经验有技巧的对待,写代码时动用金手指,把这些异常都捕获住。 常见的比如: 1 String numberStr= "1"; 2 try{ 3 int number = Integer.valueOf(numberStr); 4 } catch(exception e){ 5 e.printStackTrace(); 6 } 上面代码没有检查时异常,但是需要老司机捕获住。 当然实际上这里面坑还不止这些,中文符号被decode还办好,关键是一些特殊符号不好办。 有些符号在URL中是不能直接传递的,如果要在URL中传递这些特殊符号,那么就要使用他们的编码了。编码的格式为:%加字符的ASCII码,即一个百分号%,后面跟对应字符的ASCII(16进制)码值。例如 空格的编码值是"%20"。下表中列出了一些URL特殊符号及编码。 实际测试中发现,只要替换调%就好了。 替换代码为: 1 private String decodeUrl(String url) { 2 String decodeUrl = ""; 3 try { 4 String transformUrl = url.replaceAll("%(?![0-9a-fA-F]{2})", "%25"); 5 decodeUrl = URLDecoder.decode(transformUrl,"UTF-8"); 6 } catch (Exception e) { 7 e.printStackTrace(); 8 } finally { 9 LogUtil.e("my", "decodeUrl:" + decodeUrl); 10 return decodeUrl; 11 } 12 } 以上代码,通过了表格8种符号 全半角形式以及日文韩文的测试,传值和decode都是正常的。
EventBus是Android下高效的发布/订阅事件总线机制。作用是可以代替传统的Intent,Handler,Broadcast或接口函数在Fragment,Activity,Service,线程之间传递数据,执行方法。特点是代码简洁,是一种发布订阅设计模式(Publish/Subsribe),或称作观察者设计模式。 下面对EventBus框架使用进行介绍以及一些需要注意的地方。 1.EventBus使用配置。 1)在gradle中添加:compile 'org.greenrobot:eventbus:3.0.0' 2)在需要接受event的类中注册和结束注册,如activity中 onCreate方法中使用EventBus.getDefault().register(this); 在onDestory方法中使用 EventBus.getDefault().unregister(this); 3)增加EventModel,如: public class FirstEventModel { private String msg; public FirstEventModel(String msg){ this.msg = msg; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } } 4).在接受Event类中 增加需要处理的方法。处理的方法有四个 四个方法分别为: ---onEvent()://如果使用onEvent作为订阅函数,那么该事件在哪个线程发布出来的,onEvent就会在这个线程中运行, 也就是说发布事件和接收事件线程在同一个线程。使用这个方法时,在onEvent方法中不能执行耗时操作, 如果执行耗时操作容易导致事件分发延迟。 ---onEventMainThread()://如果使用onEventMainThread作为订阅函数,那么不论事件是在哪个线程中发布出来的, onEventMainThread都会在UI线程中执行,接收事件就会在UI线程中运行, 这个在Android中是非常有用的,因为在Android中只能在UI线程中跟新UI, 所以在onEvnetMainThread方法中是不能执行耗时操作的。 ---onEventBackgroudThread()://如果使用onEventBackgrond作为订阅函数,那么如果事件是在UI线程中发布出来的, 那么onEventBackground就会在子线程中运行,如果事件本来就是子线程中发布出来的, 那么onEventBackground函数直接在该子线程中执行。 ---onEventAsync(): //使用这个函数作为订阅函数,那么无论事件在哪个线程发布,都会创建新的子线程在执行onEventAsync. 注意:首先上述方式可以使用多态的方式,在回调的时候,eventBu框架会根据参数的不同,通过反射判断选择使用哪个方法调用,如果参数一致则多个方法都会被调用。 其次这些方法一定要加上注解: @Subscribe 代码如下: @Subscribe public void onEvent(){ //如果使用onEvent作为订阅函数,那么该事件在哪个线程发布出来的,onEvent就会在这个线程中运行, // 也就是说发布事件和接收事件线程在同一个线程。使用这个方法时,在onEvent方法中不能执行耗时操作, // 如果执行耗时操作容易导致事件分发延迟。 } @Subscribe public void onEventAsync(){ //使用这个函数作为订阅函数,那么无论事件在哪个线程发布,都会创建新的子线程在执行onEventAsync. } @Subscribe public void onEventBackgroundThread(){ //如果使用onEventBackgrond作为订阅函数,那么如果事件是在UI线程中发布出来的, // 那么onEventBackground就会在子线程中运行,如果事件本来就是子线程中发布出来的, // 那么onEventBackground函数直接在该子线程中执行。 } @Subscribe public void onEventMainThread(FirstEventModel firsEventModel){ //如果使用onEventMainThread作为订阅函数,那么不论事件是在哪个线程中发布出来的, // onEventMainThread都会在UI线程中执行,接收事件就会在UI线程中运行, // 这个在Android中是非常有用的,因为在Android中只能在UI线程中跟新UI, // 所以在onEvnetMainThread方法中是不能执行耗时操作的。 activity_main_tv.setText(firsEventModel.getClass().getSimpleName() + ":" +firsEventModel.getMsg() ); } @Subscribe public void onEventMainThread(SecondEventModel secondEventModel){ //如果使用onEventMainThread作为订阅函数,那么不论事件是在哪个线程中发布出来的, // onEventMainThread都会在UI线程中执行,接收事件就会在UI线程中运行, // 这个在Android中是非常有用的,因为在Android中只能在UI线程中跟新UI, // 所以在onEvnetMainThread方法中是不能执行耗时操作的。 activity_main_tv.setText(secondEventModel.getClass().getSimpleName() + ":" + secondEventModel.getCode()); } 2.从使用体验上感受EvenBus在处理同一进程下、不同activity、fragment的通信机制上解耦合 是比较好用的,降低了一些开发者对于解耦合处理的难度要求。有一些机制比如:startActivityForResult、广播、handle等这些可以使用eventBus替代,代码的维护性会比较高。 3.图说EventBus 这里发布者是可以有多个的,通过调用post方法,eventbus则通过反射机制根据参数列表判断调用哪个Subcriber,如果是多个subcriber参数列表一致,则都会调用,而且优先级越高则越在前面被调用到。 点击此处下载demo
产品近来蛋疼,时间格式从做完到现在改了四遍了 ,最新的要求如下: * 2分钟内 无显示 * 2分钟-24小时 HH:mm * 昨天 昨天 HH:mm * 前天 前天 HH:mm * 今年 MM:DD HH:mm * 去年 去年 MM:DD HH:mm * 前年 前年 MM:DD HH:mm * 更远 yyyy:MM:DD HH:mm 这不是问题,很快写完代码。 1 /** 2 * 将一个时间戳转换成提示性时间字符串,如 3 * 2分钟内 无显示 4 * 2分钟-24小时 HH:mm 5 * 昨天 昨天 HH:mm 6 * 前天 前天 HH:mm 7 * 一年内 MM:DD HH:mm 8 * 去年 去年 MM:DD HH:mm 9 * 前年 前年 MM:DD HH:mm 10 * 更远 yyyy:MM:DD HH:mm 11 * 毫秒计算 12 * @param charttime 13 * @return 14 */ 15 public static String convertChatDetailTimeFormat(long charttime) { 16 17 long curTime = System.currentTimeMillis() ; 18 long time = curTime - charttime; 19 20 XCApplication.base_log.i(XCConfig.TAG_SYSTEM_OUT, time + "---时间差" + time/ 1000/ 60 + "分钟"); 21 XCApplication.base_log.i(XCConfig.TAG_SYSTEM_OUT, curTime + "---当前时间" + format(new Date(curTime), FORMAT_LONG_CN_1)); 22 XCApplication.base_log.i(XCConfig.TAG_SYSTEM_OUT, charttime + "---chartTime" + format(new Date(charttime), FORMAT_LONG_CN_1)); 23 24 if (time < 120 * 1000 && time >= 0) { 25 return "刚刚"; 26 } else if (time >= 120 *1000 && time < 3600 * 24 * 1000) { 27 28 return format(new Date(charttime), FORMAT_HH_MM); 29 30 } else if (time >= 3600 * 24 * 1 * 1000 && time < 3600 * 24 * 2 * 1000) { 31 32 return "昨天" + format(new Date(charttime), FORMAT_HH_MM); 33 34 } else if (time >= 3600 * 24 * 2 * 1000 && time < 3600 * 24 * 3 * 1000) { 35 36 return "前天" + format(new Date(charttime), FORMAT_HH_MM); 37 } else if (time >= 3600 * 24 * 3 * 1000 && time < 3600 * 24 * 365 * 1 * 1000) { 38 39 return format(new Date(charttime), FORMAT_MM_DD_HH_MM); 40 } else if (time >= 3600 * 24 * 365 * 1 * 1000 && time < 3600 * 24 * 365 * 2 * 1000) { 41 42 return "去年" + format(new Date(charttime), FORMAT_MM_DD_HH_MM); 43 } else if (time >= 3600 * 24 * 365 * 2 * 1000 && time < 3600 * 24 * 365 * 3 * 1000) { 44 45 return "前年" + format(new Date(charttime), FORMAT_MM_DD_HH_MM); 46 } else if (time >= 3600 * 24 * 365 * 3 * 1000) { 47 48 return format(new Date(charttime), FORMAT_LONG_CN_1); 49 } else { 50 return "刚刚"; 51 } 52 } 这里就有一个小问题,就是自然日时间跨越实际日时间,有可能出现昨天的时间不显示昨天,而显示为HH:mm,于是测试找上门来,要求改,将2分钟-24小时的条件改为2分钟-今日内。 那么这里的需求就改为 * 2分钟内 无显示 * 2分钟-今日 HH:mm * 昨天 昨天 HH:mm * 前天 前天 HH:mm * 今年 MM:DD HH:mm * 去年 去年 MM:DD HH:mm * 前年 前年 MM:DD HH:mm * 更远 yyyy:MM:DD HH:mm 这也不是多大的问题,问题是在跨年的情况该如何,2015-01-01 00:01.001 的前三分钟接受的消息,也就是2014-12-31 该显示为昨天还是去年。如果信息的接收时间比时间还要大,该如何显示。经过一番撕逼,终于敲定,这里为了产品再次修改,要求产品立字据啊,作为终极版本存在。 1 /** 2 * 终极方法 3 * 将一个时间戳转换成提示性时间字符串,如 4 * 2分钟内 无显示 5 * 2分钟-今天 2分钟-今天 HH:mm 6 * 昨天 昨天 HH:mm 7 * 前天 前天 HH:mm 8 * 今年 MM:DD HH:mm 9 * 去年 去年 MM:DD HH:mm 10 * 前年 前年 MM:DD HH:mm 11 * 更远 yyyy:MM:DD HH:mm 12 * 毫秒计算 13 * @param time 14 * @return 15 */ 16 public static String convertWEChartTimeFormatFinalMethed(long time) { 17 long curTime = System.currentTimeMillis() ; 18 String showTimeFormat = ""; 19 20 long temp = curTime - time; 21 if (temp < 120 * 1000 && temp >= 0) { 22 showTimeFormat = ""; 23 return showTimeFormat; 24 } 25 Date mayTime = new Date(time); 26 27 // Date today = UtilDate.parse("2015-01-01 02:02:02.001", UtilDate.FORMAT_FULL); 28 Date today = new Date(); 29 //时间值 30 String mayTime_FORMAT_SHORT = format(mayTime, FORMAT_SHORT); 31 String mayTime_FORMAT_SHORT_YEAR = getYear(mayTime); 32 33 if(mayTime.after(today)){ 34 //除此以外 35 showTimeFormat = format(mayTime, FORMAT_LONG_CN_1); 36 37 } else { 38 if(mayTime_FORMAT_SHORT != null && !mayTime_FORMAT_SHORT.trim().toString().equals("")){ 39 //今天的时间yyyy-MM-dd 40 String today_str = format(today, FORMAT_SHORT); 41 String thisYear_str = getYear(today); 42 43 //昨天的时间 yyyy-MM-dd 44 Calendar calLastDay = Calendar.getInstance(); 45 calLastDay.setTime(today); 46 calLastDay.add(Calendar.DAY_OF_YEAR, -1); 47 System.out.println("昨天:" + format(calLastDay.getTime(), FORMAT_SHORT)); 48 String lastDay = format(calLastDay.getTime(), FORMAT_SHORT); 49 50 //前天的时间 yyyy-MM-dd 51 Calendar calPreviousDay = Calendar.getInstance(); 52 calPreviousDay.setTime(today); 53 calPreviousDay.add(Calendar.DAY_OF_YEAR, -2); 54 System.out.println("前天:" + format(calPreviousDay.getTime(), FORMAT_SHORT)); 55 String previousDay = format(calPreviousDay.getTime(), FORMAT_SHORT); 56 57 //去年的时间 yyyy 58 Calendar calLastYear = Calendar.getInstance(); 59 calLastYear.setTime(today); 60 calLastYear.add(Calendar.YEAR, -1); 61 String lastYear = getYear(calLastYear.getTime()); 62 System.out.println("去年:" + format(calLastYear.getTime(), FORMAT_SHORT)); 63 64 //前年的时间 yyyy 65 Calendar calPreviousYear = Calendar.getInstance(); 66 calPreviousYear.setTime(today); 67 calPreviousYear.add(Calendar.YEAR, -2); 68 String previousYear = getYear(calPreviousYear.getTime()); 69 System.out.println("前年:" + format(calPreviousYear.getTime(), FORMAT_SHORT)); 70 71 //首先判断是否是今天 72 if(mayTime_FORMAT_SHORT.equals(today_str)){ 73 //今天,则显示为 13:12 74 showTimeFormat = format(mayTime, FORMAT_HH_MM); 75 } else if(mayTime_FORMAT_SHORT.equals(lastDay)){ 76 //昨天 77 showTimeFormat = "昨天 " + format(mayTime,FORMAT_HH_MM); 78 79 } else if(mayTime_FORMAT_SHORT.equals(previousDay)){ 80 //昨天 81 showTimeFormat = "前天 " + format(mayTime,FORMAT_HH_MM); 82 83 } else if(mayTime_FORMAT_SHORT_YEAR.equals(thisYear_str)){ 84 //今年 85 showTimeFormat = format(mayTime, FORMAT_MM_DD_HH_MM); 86 } else if(mayTime_FORMAT_SHORT_YEAR.equals(lastYear)){ 87 //去年 88 showTimeFormat = "去年 " + format(mayTime, FORMAT_MM_DD_HH_MM); 89 } else if(mayTime_FORMAT_SHORT_YEAR.equals(previousYear)){ 90 //前年 91 showTimeFormat = "前年 " + format(mayTime, FORMAT_MM_DD_HH_MM); 92 } else { 93 //除此以外 94 showTimeFormat = format(mayTime, FORMAT_LONG_CN_1); 95 } 96 97 } 98 } 99 100 101 return showTimeFormat; 102 }
公司项目选择了umeng的更新功能和,统计插件,而由于版本的管理,使得需要针对某些版本进行强制更新。比如上个版本出现了重大问题,必须进行版本升级才能修复,产品架构有了重大调整,数据结构发生了变化导致原先的app无法解析等等情况。但是查过umeng的产品文档,点击跳转地址为: 坑爹的umeng竟然没有强制更新功能,在目前的方法中无法实现强制更新。想想都开始打算做一个下载接口了,自己实现强制更新了。。。 再查了次umeng的替代方案,发现有一个这样方式,通过在后台设置在线参数,然后在app中对在线参数进行处理,将升级对话框中响应事件设置监听器,完成强制更新的功能。 首先看下后台设置在线参数地址:点击跳转 然后在app中嵌入一下代码,参考 1 UmengUpdateAgent.setUpdateOnlyWifi(false); 2 String upgrade_mode = MobclickAgent.getConfigParams(this, "upgrade_mode"); 3 4 if(TextUtils.isEmpty(upgrade_mode)){ 5 return; 6 } 7 String[] upgrade_mode_array = upgrade_mode.split(";"); 8 UmengUpdateAgent.setUpdateOnlyWifi(false); 9 UmengUpdateAgent.update(MainActivity.this); 10 UmengUpdateAgent.forceUpdate(MainActivity.this);//这行如果是强制更新就一定加上 11 for(String mode:upgrade_mode_array){ 12 String versionName = ((MyApplication)getApplication()).getVersionName(); 13 versionName = versionName + "f"; 14 if(mode.equals(versionName)){ 15 //进入强制更新 16 UmengUpdateAgent.setUpdateListener(new UmengUpdateListener() { 17 18 @Override 19 public void onUpdateReturned(int updateStatus, UpdateResponse updateResponse) { 20 21 } 22 }); 23 UmengUpdateAgent.setDialogListener(new UmengDialogButtonListener() { 24 @Override 25 public void onClick(int status) { 26 27 switch (status) { 28 case UpdateStatus.Update: 29 30 break; 31 default: 32 //退出应用 33 MyApplication.base_logs.shortToast(getString(R.string.force_update_toast_string)); 34 ((MyApplication) getApplication()).AppExit(MainActivity.this); 35 } 36 } 37 }); 38 break; 39 } 40 } 这里 UmengUpdateAgent.forceUpdate(MainActivity.this); 这行很重要,不加上这条,看看界面如何:这里可以选择忽略改版,然后即使代码规定点击以后再说,也不会退出应用程序。反过来加上这句代码看看界面如何: 这里没有忽略改版,然后点击以后再说,就会退出应用程序。再次吐槽umeng的东西现在体验性不友好啊。
最近在看了许多关于dp-px,px-dp,sp-px,px-sp之间转化的博文,过去我比较常用的方式是: 1 //转换dip为px 2 public static int convertDipOrPx(Context context, int dip) { 3 float scale = context.getResources().getDisplayMetrics().density; 4 return (int)(dip*scale + 0.5f*(dip>=0?1:-1)); 5 } 6 7 //转换px为dip 8 public static int convertPxOrDip(Context context, int px) { 9 float scale = context.getResources().getDisplayMetrics().density; 10 return (int)(px/scale + 0.5f*(px>=0?1:-1)); 11 } 然后看到了一种新的转化方式,代码如下: 1 public static int dp2sp(float dpVal){ 2 return (int)(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, 3 MyAppliction.getInstance().getApplicationContext().getResources().getDisplayMetrics())); 4 } 5 //????? 6 public static int sp2dp(float spVal){ 7 return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spVal, 8 MyAppliction.getInstance().getApplicationContext().getResources().getDisplayMetrics())); 9 } 码农对TypedValue充满好奇,通过查询官网了解该类 TypedValue ---android.util.TypedValue Container for a dynamically typed data value. Primarily used with Resources for holding resource values. 翻译过来就是:这个类是工具类,作为一个动态容器,它存放一些数据值,这些值主要是resource中的值。 我们来理解一下:resource中到底有哪些值?layout、drawable、string、style、anim、dimens、menu、colors、ids这些值一些和屏幕适配有直接的关系。 有一些方法必然是可以读取这些资源文件信息的,比如: getDimension(DisplayMetrics metrics) 再看具体的方法: applyDimension(int unit, float value,DisplayMetrics metrics) 第一个参数是单位,第二个参数是对应值,第三个你懂的,封装了显示区域的各种属性值。 对于applyDimension(int unit, float value,DisplayMetrics metrics)中的代码我们来看下 1 public static float applyDimension(int unit, float value, 2 DisplayMetrics metrics) 3 { 4 switch (unit) { 5 case COMPLEX_UNIT_PX: 6 return value; 7 case COMPLEX_UNIT_DIP: 8 return value * metrics.density; 9 case COMPLEX_UNIT_SP: 10 return value * metrics.scaledDensity; 11 case COMPLEX_UNIT_PT: 12 return value * metrics.xdpi * (1.0f/72); 13 case COMPLEX_UNIT_IN: 14 return value * metrics.xdpi; 15 case COMPLEX_UNIT_MM: 16 return value * metrics.xdpi * (1.0f/25.4f); 17 } 18 return 0; 19 } 其中单位为dip的,将其转化为密度*值,也就是像素值,而单位sp的也将其转化为px值,因此该方法可以能进行 dip-->px sp-- >px 因此上面 TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, value ,DisplayMetrics );这个方法肯定不能将sp转化为dp,我们判断 dp2sp(50) = 150 sp2dp(50) = 150 convertDipOrPx(50) = 150 convertPxOrDip(50) = 17将代码运行实际结果与判断结果一致。接下来我们继续分析 TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, value ,DisplayMetrics );该方法系统本意是用来做什么的? 查看官方说明: Converts an unpacked complex data value holding a dimension to its final floating point value. 这里就把对应的值转化为实际屏幕上的点值,也就是像素值。如果是TypedValue.COMPLEX_UNIT_DIP,则乘以显示密度density。 而如果是TypedValue.COMPLEX_UNIT_SP,则乘以像素密度scaledDensity。 我们继续刨根追底 density和scaledDensity的区别在于 density:The logical density of the display.显示密度density = dpi/160 scaledDensity:A scaling factor for fonts displayed on the display.显示字体的缩放因子 = density 实际上两者的值一样,为了验证这个结论我们随便找两台机器小米2S和华为p7,取出density和scaledDensity是一致的,P7为3.0,小米2S = 2.0 因此本文结论转化dp-px,px-dp,sp-px,px-sp 使用下面方法: 1 //转换dip为px 2 public static int convertDipOrPx(Context context, int dip) { 3 float scale = context.getResources().getDisplayMetrics().density; 4 return (int)(dip*scale + 0.5f*(dip>=0?1:-1)); 5 } 6 7 //转换px为dip 8 public static int convertPxOrDip(Context context, int px) { 9 float scale = context.getResources().getDisplayMetrics().density; 10 return (int)(px/scale + 0.5f*(px>=0?1:-1)); 11 } 12 13 public static int sp2px(Context context, float spValue) { 14 float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 15 return (int) (spValue * fontScale + 0.5f); 16 } 17 18 public static int px2sp(Context context, float pxValue) { 19 float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 20 return (int) (pxValue / fontScale + 0.5f); 21 } 如有错误,敬请指正。
在最近的项目开发中,使用webview加载html页面,这样可以节省大量页面开发的时间,同时也可加快项目进度。 我们需求是需要显示商品评论,页面设计如下: 调用android代码,对于webview的设置如下: webView.getSettings().setJavaScriptEnabled(true); webView.getSettings().setBuiltInZoomControls(true); webView.getSettings().setLayoutAlgorithm(LayoutAlgorithm.SINGLE_COLUMN); webView.getSettings().setDefaultTextEncodingName("UTF-8"); 在展示数据的时候,出现问题。实际效果如下: 这里物流速度和产品质量无法显示. html的关键代码如下: switch (jsonForCreat.DATA[i].QUALITYLEVEL){ case '1': $("#q"+jsonForCreat.DATA[i].KEY_ID).css("background-position", "0px 515px"); break; case '2': $("#q"+jsonForCreat.DATA[i].KEY_ID).css("background-position", "0px 535px"); break; case '3': $("#q"+jsonForCreat.DATA[i].KEY_ID).css("background-position", "0px 555px"); break; case '4': $("#q"+jsonForCreat.DATA[i].KEY_ID).css("background-position", "0px 575px"); break; case '5': $("#q"+jsonForCreat.DATA[i].KEY_ID).css("background-position", "0px 595px"); break; default : $("#q"+jsonForCreat.DATA[i].KEY_ID).css("background-position", "0px 595px"); break; } switch (jsonForCreat.DATA[i].SPEEDLEVEL){ case '1': $("#s"+jsonForCreat.DATA[i].KEY_ID).css("background-position", "0px 515px"); break; case '2': $("#s"+jsonForCreat.DATA[i].KEY_ID).css("background-position", "0px 535px"); break; case '3': $("#s"+jsonForCreat.DATA[i].KEY_ID).css("background-position", "0px 555px"); break; case '4': $("#s"+jsonForCreat.DATA[i].KEY_ID).css("background-position", "0px 575px"); break; case '5': $("#s"+jsonForCreat.DATA[i].KEY_ID).css("background-position", "0px 595px"); break; default : $("#s"+jsonForCreat.DATA[i].KEY_ID).css("background-position", "0px 595px"); break; } 在仔细对比所有的数据格式无误,对比ios,发现: 同样的html页面,同样的数据,这个页面在ios上显示正常,在android上就是无法显示物流速度和产品质量的评价星数。 最后在比对webview设置的时候,将webview属性 webView.getSettings().setLayoutAlgorithm(LayoutAlgorithm.SINGLE_COLUMN); 注释掉以后,发现显示正常了, 查询资料得知: SINGLE_COLUMN:把所有内容放到WebView组件等宽的一列中。 这个属性至于为何与html显示冲突,还不得而知,也希望有牛人能给予解答。
AndroidTouchGalleryLibrary 是一个非常好用的库, 但是使用的时候,需要小心处理,容易引发OutOfMemoryError,同时使用UrlTouchImageView的时候, 从网络下载是没有缓存的,因此每次都是新加载图片,同时使用在线家在较大图片容易导致程序挂掉 因此将原先的UrlTouchImageView类中的此段代码更换 原代码: //No caching load public class ImageLoadTask extends AsyncTask<String, Integer, Bitmap> { @Override protected Bitmap doInBackground(String... strings) { String url = strings[0]; Bitmap bm = null; try { URL aURL = new URL(url); URLConnection conn = aURL.openConnection(); conn.connect(); InputStream is = conn.getInputStream(); int totalLen = conn.getContentLength(); InputStreamWrapper bis = new InputStreamWrapper(is, 8192, totalLen); bis.setProgressListener(new InputStreamProgressListener() { @Override public void onProgress(float progressValue, long bytesLoaded, long bytesTotal) { publishProgress((int)(progressValue * 100)); } }); bm = BitmapFactory.decodeStream(bis); bis.close(); is.close(); } catch (Exception e) { e.printStackTrace(); } return bm; } @Override protected void onPostExecute(Bitmap bitmap) { if (bitmap == null) { mImageView.setScaleType(ScaleType.CENTER); bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.no_photo); mImageView.setImageBitmap(bitmap); } else { mImageView.setScaleType(ScaleType.MATRIX); mImageView.setImageBitmap(bitmap); } mImageView.setVisibility(VISIBLE); mProgressBar.setVisibility(GONE); } @Override protected void onProgressUpdate(Integer... values) { mProgressBar.setProgress(values[0]); } } 更换为: //No caching load public class ImageLoadTask extends AsyncTask<String, Integer, Bitmap> { @Override protected Bitmap doInBackground(String... strings) { String url = strings[0]; Bitmap bm = null; try { URL aURL = new URL(url); URLConnection conn = aURL.openConnection(); conn.connect(); InputStream is = conn.getInputStream(); int totalLen = conn.getContentLength(); InputStreamWrapper bis = new InputStreamWrapper(is, 8192, totalLen); bis.setProgressListener(new InputStreamProgressListener() { @Override public void onProgress(float progressValue, long bytesLoaded, long bytesTotal) { publishProgress((int)(progressValue * 100)); } }); BitmapFactory.Options options=new BitmapFactory.Options(); options.inTempStorage = new byte[100*1024]; options.inPreferredConfig = Bitmap.Config.RGB_565; options.inPurgeable = true; options.inSampleSize = 2;//压缩 options.inInputShareable = true; bm = BitmapFactory.decodeStream(bis, null, options); bis.close(); is.close(); } catch (Exception e) { e.printStackTrace(); } return bm; } @Override protected void onPostExecute(Bitmap bitmap) { if (bitmap == null) { mImageView.setScaleType(ScaleType.CENTER); bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.no_photo); mImageView.setImageBitmap(bitmap); } else { mImageView.setScaleType(ScaleType.MATRIX); mImageView.setImageBitmap(bitmap); } mImageView.setVisibility(VISIBLE); mProgressBar.setVisibility(GONE); } @Override protected void onProgressUpdate(Integer... values) { mProgressBar.setProgress(values[0]); } }
最近在开发平板项目,完全是fragmentactivity+fragment的结构。看起来似乎简单,但是和以前不同的是,业务逻辑非常复杂,多处的非常规跳转,fragment之间的数据交换,一处更新多处更新等操作,有时玩起来都心塞。项目背景介绍完毕。现在有这样一个场景,项目需求是,后台可配置功能,也就是说app端所有的功能都是后台配置上去的动态生成,对应的功能界面如下图。 左边是功能索引,根据后台配置动态生成,右边每个功能对应的界面。已经实现的是左边使用fragment,右边也是fragment,点击不同的功能号切换到不同的功能界面。现在在1.1fragment中,有一个按钮点击需要实现 1)跳转到功能号为2的功能界面, 2)同时左边的功能号对应的也需要切换过来。需求1)通过回调可以实现,而需求2)简单点实现是通过发从程序内广播实现。 下面主要介绍下程序内广播。 1.LocalBroadcastManager基本介绍 这个类是在v4包中的,谷歌官方的介绍是: Helper to register for and send broadcasts of Intents to local objects within your process. This is has a number of advantages over sending global broadcasts with sendBroadcast(Intent): You know that the data you are broadcasting won't leave your app, so don't need to worry about leaking private data. It is not possible for other applications to send these broadcasts to your app, so you don't need to worry about having security holes they can exploit. It is more efficient than sending a global broadcast through the system. 本人献丑翻译下: 能够完成在应用内的广播发送,而且比全局广播更具优势: 1).广播只会在你的应用内发送,所以无需担心数据泄露,更加安全。 2).其他应用无法发广播给你的应用,所以也不用担心你的应用有别人可以利用的安全漏洞 3).相比较全局广播,它不需要发送给整个系统,所以更加高效。 2. 使用方式广播注册: 1 LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(getActivity()); 2 IntentFilter filter = new IntentFilter(); 3 filter.addAction(ACTION); 4 myBroadcastReciver = new MyBroadcastReciver(); 5 localBroadcastManager.registerReceiver(myBroadcastReciver, filter); 广播发送 1 Intent intent = new Intent(); 2 intent.setAction(SaleLeftFragment.ACTION); 3 intent.putExtra(TAG, data); 4 LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(intent); 3.使用注意 在使用的时候,请关注以下几点: 1).LocalBroadcastManager注册广播只能通过代码注册的方式。 2).LocalBroadcastManager注册广播后,一定要记得取消监听。 3).重点的重点,使用LocalBroadcastManager注册的广播,您在发送广播的时候务必使用LocalBroadcastManager.sendBroadcast(intent);否则您接收不到广播,不要怪政府哈。 谢谢。上面的需求实现也欢迎各位童鞋多给意见和建议。
对于radiaoButton,应该很多人都用过。下面看一个场景 上方时radiogroup,细致观察发现左1,文字开始位置和右1文字开始位置不同,这是为何呢? 查看布局: <RadioButton android:layout_width="0dp" android:layout_weight="1.0" android:layout_height="match_parent" android:button="@null" android:gravity="center" android:text="@string/guide_video_safe" style="@style/text_middle1_size" android:textColor="@drawable/textview_selector" android:singleLine="true" /> <RadioButton android:layout_width="0dp" android:layout_weight="1.0" android:layout_height="match_parent" android:button="@null" android:gravity="center" android:text="@string/guide_video_maintain" style="@style/text_middle1_size" android:textColor="@drawable/textview_selector" android:singleLine="true" /> <RadioButton android:layout_width="0dp" android:layout_weight="1.0" android:layout_height="match_parent" android:button="@null" android:gravity="right|center_vertical" android:text="@string/guide_video_emergency" style="@style/text_middle1_size" android:textColor="@drawable/textview_selector" android:singleLine="true" /> <...></RadioGroup> 发现并无什么特别, style定义如下: <style name="text_middle1_size"> <item name="android:textSize">15sp</item> </style> 那么问题就来了,造成左右区别的到底是哪个属性?原来这里我们通过设置background可以使得文字的开始位置从最左边开始。
listview.setEmpty(View view); 使用listView或者gridView时,当列表为空时,有时需要显示一个特殊的empty view来提示用户,今日对这个方法进行一下小结,书写的方式有三种: 1.一般情况下,继承ListActivity,只要 <ListView android:id="@id/android:list".../> <TextView android:id="@id/android:empty.../> 当列表为空时就会自动显示TextView 2.如果继承Activity的话,想出现上面的效果,就需要手动代码 <ListView android:id="@+id/list" .../> <TextView android:id="@+id/empty" .../> ListView list= (ListView)findViewById(R.id.mylist); TextView tv= (TextView)findViewById(R.id.myempty); list.setEmptyView(tv); 3.我们随性的写法,可用。 private TextView tv ; private ListView listView; listView = (ListView) view.findViewById(R.id.ListView_nav_search_list_poi); listView.setAdapter(adapter); tv = new TextView(context); tv.setVisibility(View.GONE); tv.setText(R.string.map_favorite_no_data); ((ViewGroup)(listView.getParent())).addView(tv); listView.setEmptyView(tv ); 注意:这里还没有完,如果数据集合发生变更,从有数据到无数据,再有无数据到有数据时,视图会还停留在无数据的状态,不会显示listview, 这里需要添加一个方法 public void changePoiItems(List<PoiItem> poiItems){ this.poiItems = poiItems; if(poiItems != null && poiItems.size() > 0){ tv.setVisibility(View.GONE); listView.setVisibility(View.VISIBLE); } //改变数据集合 if(adapter != null){ adapter.changeData(poiItems); } } 发生数据变更的时候,将视图状态改变下即可。
这段时间在给朋友申请苹果账号,从个人开发者账号、团体账号到公司账号,申请了个遍。这里对申请流程做一下介绍,方便其他朋友,少走弯路,账号早日申请通过。 1.首先介绍下个人开发者账号、团体账号、公司账号之间的区别: 用途 限制 是否需要邓白氏码 费用(元) 申请地址 个人开发者账号 用于个人开发者上传和发布应用,在apptore上显示个人开发者信息。 1.只能有一个开发者; 2.100个IOS设备UDID测试 否 688 http://developer.apple.com/programs/ios/ 团体账号 用于团体、公司开发者上传和发布应用,在apptore上显示团体名称。 1.允许多个账号管理; 2.100个IOS设备UDID测试 是 688 http://developer.apple.com/programs/ios/ 公司账号 用于公司发布应用,使用该证书的应用不需要审核,但是也不能发布到appstore 1.不能上传到appstore; 2.无IOS设别UDID数量限制 是 1988 https://developer.apple.com/programs/ios/enterprise/ 教育账号 不能对外正式发布应用 苹果特批 ----- 0 https://developer.apple.com/programs/start/university/ 这么多选项,那么问题就来了.....申请到底选择哪种账号才是合适自己的?(不是学挖掘机哪家强。。。。) 一般性的个人开发者选择个人开发者账号,地球人都知道的,小企业发布应用就单纯省事来说,用个人开发者账号,也可以。如果是注重企业VI品牌的话,选择团体账号,这 样可以在apptore上显示公司名,有图有真相: 接下来问题又来了,企业账号神马情况下使用? 如果应用开发出来仅仅是给企业内部使用,并不打算放到apppstore给所有人使用,那么就需要使用这个账号了。无需通过官方审核,秒发秒更新! 比如企业发布了一个加密通讯工具,只供内部职工使用,在上一个版本发布以后,突然发现上个版本有重大bug,这个时候企业者开发账号的好处就体现出来了。 然后再就是有另外一个情况,企业开发了一个生活服务类应用,提供公司职工使用,但是老板希望这个应用也要上传到appstore, 那么问题就来了,该肿么办? 呵呵,企业者账号+团体账号就ok了撒。 2.邓白氏码 DUNS number介绍 D-U-N-S&reg; Number,是Data Universal Numbering System的缩写,是一个独一无二的9位数字全球编码系统,相当于企业的身份识别码 (就像是个人的身份证),被广泛应用于企业识别、商业信息的组织及整理.这个号码是由邓白氏公司签发的,每个号码会跟一个唯一的企业实体相对应,不会重复使用。也就是说,一个号码代表一个公司实体。通过邓氏编码,浏览者可以迅速获得独创、丰富且高质量的信息产品和服务。 说了这么多,邓白氏码的发布者是一个公司,公司就需要盈利,那么去邓白氏公司申请邓白氏码就肯定要花钱,多少钱呢?走国内代理帮忙4000元每年,便宜的也要800元一年,而且还不保证这个码在苹果公司是否可用,那么问题又来了,为啥不可用呢?哥哥花了钱买了个邓白氏码,结果在苹果那儿不能用,这不是逗比吗。 原来苹果公司升级了数据了,数据库中新增了字段legal entity,而邓白氏码公司没有这个字段,无法保证邓白氏码在苹果公司是可用的,所以呢,苹果公司说了,要码么? 找我来吧,点击这个网址免费申请咯:https://developer.apple.com/ios/enroll/dunsLookupForm.action 现在还要花钱去买邓白氏码的童鞋么,就不要再上当了。 3.申请步骤 团体账号: 首先需要一个邓白氏码,进入https://developer.apple.com/ios/enroll/dunsLookupForm.action 填写信息: 法人实体名称 总部地址 邮寄地址 您的工作联系信息 页面信息如下: 然后在一个星期内 ,就会收到一封来自苹果公司的邮件,大约是你申请通过了,DUNS号多少,你收到以后就可以使用appid进行申请团体账号了, 但是需要注意的是:如果审核不通过一个星期内不要超过两次提交。 2).收到DUNS号以后,在http://developer.apple.com/programs/ios/ 页面提交公司信息 这时候静静等候苹果审核吧,一般1个星期-2个星期,会受到邮件,或者需要确认信息、或者需要修改信息、或者是最好的通知你付费,付费成功,则账号可以使用了。 这个时候问题又来了,我们申请的时候是英文信息,如果需要显示 中文公司名,要肿么办? 在费用成功以后,我们可以向苹果公司发送邮件,申请修改公司名,但是修改的机会只有一次,确认无误后,即可在appstore显示中文公司信息。 最后,还有一个问题,学挖掘机到底哪家强?
xml中可以设置为: <EditText android:layout_width = "fill_parent" android:layout_height = "wrap_content" android:id = "@+id/mEdit" android:maxLength = "10"/> //手动设置maxLength为10 InputFilter[] filters = {new InputFilter.LengthFilter(10)}; et_rechargeFromWallet_count.setFilters(filters);
近来一直在做APK反编译和重编译的工作,针对一些apk需要放入一些相应的文件,(当然这里不涉及非法盈利,都是有合约的),在对一些包打包以后,发现可以通过一个叫做zipalign的工具进行优化,对于这个工具的介绍在谷歌官网有介绍:http://developer.android.com/tools/help/zipalign.html, 对其中我将一部分按照自己的理解翻译出来,如果翻译和理解不到位,还请指正。 zipalign:是一个用于内容对齐的工具,用来优化android apk文件。它的目的是保证所有未被压缩的文件都有特定的对齐。具体的说来,就是他会让所有未压缩的数据,比如raw中的图片都有按照4bite对齐。这样就会让所有的部分都可以直接使用 MMAP 来直接访问,即使MMAP包含了对于二进制数据的对齐限制。它的好处在运行应用的时候,减少了大量的RAM消耗。(因此可以提高apk运行的速度和效率) 你应该在把这个文件发给用户之前,使用这个zipalign来进行对齐。你也可以使用android build tools来处理。当你在使用android adt插件的时候,导出向导会自动的帮你zipalign 你使用私钥签名以后的apk文件。同样你也可以使用ant脚本来完成zipalign apk文件,只要你在项目中配置了ant ant.properties 在其中标明 alias,脚本会首先给应用签名。 警告: zipalign必须要在使用私钥签名以后再使用,否则的话,则会导致签名过程会取消对齐效果。同样的不要在完成对齐以后改动文件内容,比如删除某些内容,这些操作可能破坏已经修改的内容以及对齐项,任何已经加到对齐项中的文件都不会被对齐。 zipalign通过修改在文件开头部分增加一些额外的域,存在于这个额外域的数据可能会被zipalign过程修改。 用法: 校验是否已经优化处理: zipalign -c -v <alignment> existing.apk 使用实例: zipalign -c -v 4 result.apk 进行优化处理: zipalign [-f] [-v] <alignment> infile.apk outfile.apk 使用实例: zipalign -v 4 1.apk result.apk 其中 zipalign 在大家adt\sdk\tools中就有,无需下载,
git clone ssh://lijianfeng@192.168.1.246:29418/GMGameSDK压栈:git stash查状态:git status切换到要修改的提交:git rebase -i HEAD~3提交完成后的修改:git commit -a --amend确认提交:git rebase --continue提交修改的代码到服务器:git push origin HEAD:refs/for/master出栈:git stash pop切换分支:git checkout 分支名,如切换到Chinanet分支:git checkout Chinanet把已提交的commit,从一个分支放到另外一个分支:git cherry-pick <commit id>若当前的分支为Chinanet,在master上有一个commit 298cb592bd0c65dabb758b6191fb1c3177a458ee,要该commit放到Chinanet上,命令如下:git cherry-pick 298cb592bd0c65dabb758b6191fb1c3177a458ee解决冲突git commit -agit push origin HEAD:refs/for/Chinanet
java.lang.Object android.graphics.drawable.DrawableKnown Direct Subclasses BitmapDrawable, ClipDrawable, ColorDrawable, DrawableContainer, GradientDrawable, InsetDrawable, LayerDrawable, NinePatchDrawable, PictureDrawable, RotateDrawable, ScaleDrawable, ShapeDrawableKnown Indirect Subclasses AnimationDrawable, LevelListDrawable, PaintDrawable, StateListDrawable, TransitionDrawable 这里 有一些常用xml表示的样式可以用代码来实现,比如 selector ,可以使用stateListDrawable来代码实现 连接在此:http://developer.android.com/reference/android/graphics/drawable/Drawable.html textView的文字点击效果,可以使用 ColorStateList来实现 连接在此: http://developer.android.com/reference/android/content/res/ColorStateList.html
原型模式是一种创建型设计模式,在java中可以直接调用object.clone()。 原型模式属于对象的创建模式。通过给出一个原型对象来指明所有创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象。这就是选型模式的用意。 而java中 所有的对象都是 object,在object中 就有了clone() 方法,因此大多数时候大家都不怎么关注,它一般可以与工厂模式一起使用。 对于clone来说,java中有深克隆 和浅克隆,原文称为: Shallow Clone&Deep Clone,区别在于: Object在对某个对象实施Clone时对其是一无所知的,它仅仅是简单地执行域对域的copy,这就是浅克隆Shallow Clone, 当Object 里面有一个域hireDay不是基本型别的变量,而是一个reference变量,经过Clone之后就会产生一个新的Date型别的reference,它和原始对象中对应的域指向同一个Date对象,这样克隆类就和原始类共享了一部分信息,而这样显然是不利的,这时候就需要深克隆 deep Clone。 使用深克隆 可以使用对象流复制的方式: 一般使用的clone()方法虽然可以实现深度克隆,但是需要的克隆对象里有其他引用对象,这个引用对象还有引用对象那么你重写clone()方法就非常的繁琐了所以建议使用输入输出流进行克隆 /* * 复制对象obj,类似于值传递,非引用 */ public static Object cloneObject(Object obj) throws Exception{ ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(byteOut); out.writeObject(obj); ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray()); ObjectInputStream in =new ObjectInputStream(byteIn); return in.readObject(); } 需要注意的是:对象要实现序列化的接口 :Serializable
android:shadowColor="#000000" android:shadowDx="1" android:shadowDy="1" android:shadowRadius="1" textView的阴影线 可以设置如上
集合几个工具方法,方便以后使用。 1.获取手机 分辨率屏幕: public static void printScreenInfor(Context context){ DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); int width = displayMetrics.widthPixels; int height = displayMetrics.heightPixels; float density = displayMetrics.density; float scaledDensity = displayMetrics.scaledDensity; Log.d(null, String.format("screen info: width = %d, height = %d, density = %f , scaledDensity = %f ", width, height, density, scaledDensity)); } 2.获取手机 密度 1 public static double getDensity(Activity context) { 2 DisplayMetrics displaysMetrics = new DisplayMetrics(); 3 if(context == null ){ 4 LogHelper.e(LogHelper.APPUTIL, "传入的应为activity"); 5 return 0; 6 } 7 context.getWindowManager().getDefaultDisplay().getMetrics(displaysMetrics); 8 float density = displaysMetrics.scaledDensity; 9 10 return density; 11 } 3.获取手机的dp和px转化: public static int dip2px(Context context, float dipValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dipValue * scale + 0.5f); } public static int px2dip(Context context, float pxValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (pxValue / scale + 0.5f); } 4.获取手机真实的物理尺寸 public static double getScreenPhysicalSize(Activity activity) { DisplayMetrics dm = new DisplayMetrics(); activity.getWindowManager().getDefaultDisplay().getMetrics(dm); double diagonalPixels = Math.sqrt(Math.pow(dm.widthPixels, 2) + Math.pow(dm.heightPixels, 2)); return diagonalPixels / (160 * dm.density); } 5.判断手机是否是平板 1 public static boolean isTablet(Context context) { 2 return (context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE; 3 } 6.获取手机的编译版本 制造厂商等 1 String osVersion = android.os.Build.VERSION.SDK;//编译版本 2 String model = Build.MODEL;//手机品牌 3 String manufacture = Build.manufacture;// 4 String cpu = Build.cpu_ABI; 7.获取手机mac地址等 1 public String getLocalMacAddress() { 2 WifiManager wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE); 3 WifiInfo info = wifi.getConnectionInfo(); 4 return info.getMacAddress(); 5 } 8.判断是有网络 1 public static boolean isNetworkAvailable(Context context) { 2 boolean flag = false; 3 if (context != null) { 4 ConnectivityManager connMgr = (ConnectivityManager) context 5 .getSystemService(Context.CONNECTIVITY_SERVICE); 6 NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); 7 if (networkInfo != null && networkInfo.isConnected()) { 8 flag = true; 9 } 10 } 11 return flag; 12 } 9.其他待补充
以前使用SVN很顺手,现在公司使用git来管理代码,因此学习git的基本使用。 一。首先介绍下SVN和git的简单比较: SVN是使用得最多的版本控制管理工具。 1.是一个集中式的版本管理工具。所有的文件都集中在一个服务器上,用户都是通过这个服务器进行更新,一旦服务器发生故障,那么则无法协 同工作。 2.SVN按文件存储。 所有的资源控制系统都是把文件的元信息隐藏在一个类似.svn,.cvs等的文件夹里。 Git的优势在于易于本地增加分支和分布式的特性,可离线提交,解决了异地团队协同开发等svn不能解决的问题。 1.分布式的版本管理攻击。用户拥有克隆版本库。 2.GIT按元数据存储。 如果把.git目录的体积大小跟.svn比较,会发现它 们差距很大。因为,.git目录是处于你的机器上的一个克隆版的版本库,它拥有中心版本库上所有的东西,例如标签,分支,版本记录等。 可以简单的理解为SVN保存的公共修改文件记录只有一份,存放在服务器上,当多人使用的时候一不小心,就可能覆盖别人的代码,造成项目损害。 而SVN保存的修改记录有本地记录和服务器记录,所有需要提交的代码必须先经过本地提交,本地拥有克隆版本库。 二。GIT使用 默认已经安装好git插件,配置好秘钥等。 1.git 创建新项目。 登陆到git服务器上,创建新项目: $ ssh git@gitthub.com $ cd de $ mkdir hello.git $ git init 上面这部分代码主要是在服务器上创建一个新的项目,然后进入到本地进行clone ".git"信息,同时将项目文件添加到工程中, 2.git clone 新项目 $ cd d: $ git clone git@github.com:de/hello $ cd hello 这部分主要是将hello 项目复制在本地d盘hello目录下,接下来需要将项目的文件放到这个目录下,然后将文件添加到文件记录中 3.git添加新文件和文件夹 $ git add . $ git commit -m "init project" git add后面有点,表示将当前的目录所有的文件和文件夹都加到索引中,然后提交到本地。 这时候我们可以通过下面这个命令查看当前项目的状态: $ git status 会有红色的文字提示你,新增加了那些文件。 4.git项目关联 如果是新项目一定要记得使用下面这个命令,进行本地项目和远程项目的关联, $ git remote add origin giot@github.com:de/hello.git 然后通过 $ git remote -v 查看是否关联成功。 5.项目提交服务器 这时候我们就可以进行本地项目提交了,使用下面命令: $ git push -u origin master 6.项目合并 如果项目发生更改以后,要使用合并命令,再提交 $ git merge origin master 三。使用注意 对于android项目来说,我们不需要 gen、bin目录,因此在第一次提交文件的时候不要复制这两个目录进来, 同时可以使用一个文件 “.gitignore”-里面的内容为: /gen /bin 表示忽略这两个文件夹内容。
三.接上一节,分析windowManager中添加一个悬浮框的方式,首先看代码 WindowManager.LayoutParams params = new LayoutParams(); params.width = width; params.height = height; params.format = PixelFormat.TRANSLUCENT; params.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;//后面窗口仍然可以处理点设备事件 params.setTitle("Toast"); params.gravity = gravity; params.windowAnimations = styleAnimations; WindowManager windowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); TextView float = new TextView(context); float.settext("this is a float window "); windowManager.addView(view, this.mLayoutParams); params中一般参数都容易理解,这里有几个特别要注意的地方:type、flags、windowAnimation 1.对于type : type 的取值: 应用程序窗口。 public static final int FIRST_APPLICATION_WINDOW = 1; 所有程序窗口的“基地”窗口,其他应用程序窗口都显示在它上面。 public static final int TYPE_BASE_APPLICATION =1; 普通应用功能程序窗口。token必须设置为Activity的token,以指出该窗口属谁。 public static final int TYPE_APPLICATION = 2; 用于应用程序启动时所显示的窗口。应用本身不要使用这种类型。 它用于让系统显示些信息,直到应用程序可以开启自己的窗口。 public static final int TYPE_APPLICATION_STARTING = 3; 应用程序窗口结束。 public static final int LAST_APPLICATION_WINDOW = 99; 子窗口。子窗口的Z序和坐标空间都依赖于他们的宿主窗口。 public static final int FIRST_SUB_WINDOW = 1000; 面板窗口,显示于宿主窗口上层。 public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW; 媒体窗口,例如视频。显示于宿主窗口下层。 public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW+1; 应用程序窗口的子面板。显示于所有面板窗口的上层。(GUI的一般规律,越“子”越靠上) public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW +2; 对话框。类似于面板窗口,绘制类似于顶层窗口,而不是宿主的子窗口。 public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW +3; 媒体信息。显示在媒体层和程序窗口之间,需要实现透明(半透明)效果。(例如显示字幕) public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW +4; 子窗口结束。( End of types of sub-windows ) public static final int LAST_SUB_WINDOW = 1999; 系统窗口。非应用程序创建。 public static final int FIRST_SYSTEM_WINDOW = 2000; 状态栏。只能有一个状态栏;它位于屏幕顶端,其他窗口都位于它下方。 public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW; 搜索栏。只能有一个搜索栏;它位于屏幕上方。 public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1; 电话窗口。它用于电话交互(特别是呼入)。它置于所有应用程序之上,状态栏之下。 public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2; 系统提示。它总是出现在应用程序窗口之上。 public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW +3; 锁屏窗口。 public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW +4; 信息窗口。用于显示toast。 public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW +5; 系统顶层窗口。显示在其他一切内容之上。此窗口不能获得输入焦点,否则影响锁屏。 public static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW +6; 电话优先,当锁屏时显示。此窗口不能获得输入焦点,否则影响锁屏。 public static final int TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW +7; 系统对话框。(例如音量调节框)。 public static final int TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW +8; 锁屏时显示的对话框。 public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW +9; 系统内部错误提示,显示于所有内容之上。 public static final int TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW +10; 内部输入法窗口,显示于普通UI之上。应用程序可重新布局以免被此窗口覆盖。 public static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW +11; 内部输入法对话框,显示于当前输入法窗口之上。 public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW +12; 墙纸窗口。 public static final int TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW +13; 状态栏的滑动面板。 public static final int TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW +14; 系统窗口结束。 public static final int LAST_SYSTEM_WINDOW = 2999; 这里需要注意的地方是: 对于需要依附于activity的选择2000以下的,独立于activity,使用2002 2.对于flag,这里详细的说明 int FLAGS_CHANGED int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON Window flag: as long as this window is visible to the user, allow the lock screen to activate while the screen is on. int FLAG_ALT_FOCUSABLE_IM Window flag: invert the state of FLAG_NOT_FOCUSABLE with respect to how this window interacts with the current method. int FLAG_BLUR_BEHIND This constant was deprecated in API level 14. Blurring is no longer supported. int FLAG_DIM_BEHIND Window flag: everything behind this window will be dimmed. int FLAG_DISMISS_KEYGUARD Window flag: when set the window will cause the keyguard to be dismissed, only if it is not a secure lock keyguard. int FLAG_DITHER This constant was deprecated in API level 17. This flag is no longer used. int FLAG_FORCE_NOT_FULLSCREEN Window flag: override FLAG_FULLSCREEN and force the screen decorations (such as the status bar) to be shown. int FLAG_FULLSCREEN Window flag: hide all screen decorations (such as the status bar) while this window is displayed. int FLAG_HARDWARE_ACCELERATED Indicates whether this window should be hardware accelerated. int FLAG_IGNORE_CHEEK_PRESSES Window flag: intended for windows that will often be used when the user is holding the screen against their face, it will aggressively filter the event stream to prevent unintended presses in this situation that may not be desired for a particular window, when such an event stream is detected, the application will receive a CANCEL motion event to indicate this so applications can handle this accordingly by taking no action on the event until the finger is released. int FLAG_KEEP_SCREEN_ON Window flag: as long as this window is visible to the user, keep the device's screen turned on and bright. int FLAG_LAYOUT_INSET_DECOR Window flag: a special option only for use in combination with FLAG_LAYOUT_IN_SCREEN. int FLAG_LAYOUT_IN_OVERSCAN Window flag: allow window contents to extend in to the screen's overscan area, if there is one. int FLAG_LAYOUT_IN_SCREEN Window flag: place the window within the entire screen, ignoring decorations around the border (such as the status bar). int FLAG_LAYOUT_NO_LIMITS Window flag: allow window to extend outside of the screen. int FLAG_NOT_FOCUSABLE Window flag: this window won't ever get key input focus, so the user can not send key or other button events to it. int FLAG_NOT_TOUCHABLE Window flag: this window can never receive touch events. int FLAG_NOT_TOUCH_MODAL Window flag: even when this window is focusable (its FLAG_NOT_FOCUSABLE is not set), allow any pointer events outside of the window to be sent to the windows behind it. int FLAG_SCALED Window flag: a special mode where the layout parameters are used to perform scaling of the surface when it is composited to the screen. int FLAG_SECURE Window flag: treat the content of the window as secure, preventing it from appearing in screenshots or from being viewed on non-secure displays. int FLAG_SHOW_WALLPAPER Window flag: ask that the system wallpaper be shown behind your window. int FLAG_SHOW_WHEN_LOCKED Window flag: special flag to let windows be shown when the screen is locked. int FLAG_SPLIT_TOUCH Window flag: when set the window will accept for touch events outside of its bounds to be sent to other windows that also support split touch. int FLAG_TOUCHABLE_WHEN_WAKING Window flag: when set, if the device is asleep when the touch screen is pressed, you will receive this first touch event. int FLAG_TURN_SCREEN_ON Window flag: when set as a window is being added or made visible, once the window has been shown then the system will poke the power manager's user activity (as if the user had woken up the device) to turn the screen on. int FLAG_WATCH_OUTSIDE_TOUCH Window flag: if you have set FLAG_NOT_TOUCH_MODAL, you can set this flag to receive a single special MotionEvent with the action MotionEvent.ACTION_OUTSIDE for touches that occur outside of your window. 3.对于windowAnimation 很多人说为什么我设置了windowAnimation,但是没有动画效果呢? 这里一定要注意,这里需要使用style,在style中添加如下: <style name="top"> <item name="@android:windowEnterAnimation">@anim/sanqiwan_toast_slide_top_enter</item> <item name="@android:windowExitAnimation">@anim/sanqiwan_toast_slide_top_exit</item> </style> 然后将style的resourceId赋给params.windowAnimation,如果是将动画的resourceId赋值给params.windowAnimation,死也看得不到动画效果滴。 欢迎转载,请标明出处。
本文将主要介绍addview方法,在windowManager、window、viewGroup中的实现原理。首先将介绍这些类结构关系,然后分析其内在联系,介绍实现原理,最后介绍重要的一个参数windowManager.layoutParams。 文章预计分为三个部分。 一、首先介绍一下上述接口、类的结构 接口:windowManager 用来在应用与window之间的管理接口,管理窗口顺序,消息等 public interface WindowManager extends android.view.ViewManager 抽象类:window 定义窗口样式和行为的抽象基类,用于作为顶层的view加到windowManager中。 唯一实现了这个抽象类的是PhoneWindow,实例化PhoneWindow需要一个窗口 public abstract class Window 其中有一个很重要的内部类 private class LocalWindowManager extends WindowManagerImpl.CompatModeWrapper{...}; 抽象类:viewGroup 包含其他view的容器,layouts和view 容器的基类。 public abstract class ViewGroup extends View implements ViewParent, ViewManager 相关接口:ViewParent 定义了一个view parent 的要负责的功能以及view和parent view之间的关联 public interface ViewParent { public void requestLayout(); public void createContextMenu(ContextMenu menu); public void bringChildToFront(View child); ..... } viewManager 用来添加和移除activity中的view的接口 public interface ViewManager{ public void addView(View view, ViewGroup.LayoutParams params); public void updateViewLayout(View view, ViewGroup.LayoutParams params); public void removeView(View view);} 二.他们之间的内在关系。 1. 对于view来说,添加到viewGroup中是通过addView();方式来实现的,在addView中实际上使用的是: addViewInner(child, index, params, false); 流程是: 1.首先是对子View是否已经包含到一个父容器中 2.对子View布局参数的处理 3.调用addInArray来添加View 4.设置父View为当前的ViewGroup 5.焦点的处理 6.当前View的AttachInfo信息 7.View树改变的监听 8.子View中的mViewFlags的设置 主要是通过 addInArray添加view,添加的实现为system.arrayCopy(....); 2. 对于viewGroup来说,都会显示在在一个窗口中,每个都有一个父节点mParent,,最顶上的节点也是一个viewGroup,也就是decorView。 对于每个activity只有一个decorView也就是ViewRoot,只有一个window,window的获取是通过下面方法获取的。 Window mWindow = PolicyManager.makeNewWindow(this); 在activity中使用setContentView(),其实是使用了 window.setContentView()完成的,window.setcontentView, 还是通过LocalWindowManager.addView(view, params)来实现的。这里LocalWindowManager是实现了WindowManagerImpl.CompatModeWrapper ,本质上就是WindowManager、viewManager接口中的addvidew方法。 3. 对于windowManager来说一个系统只有一个,它是由系统底层实现的,用于负责调度当前显示那个窗口,消息处理我们获得一个windowManager的方式如下: WindowManager windowManager = (WindowManager)context().getSystemService( Context.WINDOW_SERVICE); 这里windowManager其实是一个接口,而通过getSystemService的方式。通过这个方式可以获取很多的系统服务,比如电话、闹钟、电源管理等等。 同时windowManager和几个类之间的内在联系如下: 本节结束,下节讲述windowManager中WindowManager.layoutParams相关。
android 开发中: 在AndroidManifest.xml中,<meta-data>元素可以作为子元素, 被包含在<activity>、<application> 、<service>和<receiver>元素中, 不同的父元素,在应用时读取的方法也不同。 1 :在Activity应用<meta-data>元素。 xml代码段: <activity...> <meta-data android:name="meta_data_Name" android:value="hello my activity"></meta-data> </activity> java代码段: ActivityInfo info=this.getPackageManager() .getActivityInfo(getComponentName(), PackageManager.GET_META_DATA); String msg =info.metaData.getString("meta_data_Name"); Log.d(TAG, " msg == " + msg ); 2:在application应用<meta-data>元素。 xml代码段: <application...> <meta-data android:value="hello my application" android:name="meta_data_Name"></meta-data> </application> java代码段: ApplicationInfo appInfo = this.getPackageManager() .getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA); String msg=appInfo.metaData.getString("meta_data_Name"); Log.d(TAG, " msg == " + msg ); 3:在service应用<meta-data>元素。 xml代码段: <service android:name="MetaDataService"> <meta-data android:value="hello my service" android:name="meta_data_Name"></meta-data> </service> java代码段: ComponentName cn=new ComponentName(this, MetaDataService.class); ServiceInfo info=this.getPackageManager() .getServiceInfo(cn, PackageManager.GET_META_DATA); String msg=info.metaData.getString("meta_data_Name"); Log.d(TAG, " msg == " + msg ); 4: 在receiver应用<meta-data>元素。 xml代码段: <receiver android:name="MetaDataReceiver"> <meta-data android:value="hello my receiver" android:name="meta_data_Name"></meta-data> <intent-filter> <action android:name="android.intent.action.PHONE_STATE"></action> </intent-filter> </receiver> java代码段: ComponentName cn=new ComponentName(context, MetaDataReceiver.class); ActivityInfo info=context.getPackageManager() .getReceiverInfo(cn, PackageManager.GET_META_DATA); String msg=info.metaData.getString("meta_data_Name");
1.是不是应该把数据刷新操作放在onResume()中? @Override public void onResume() { super.onResume(); refresh(); } public void refresh(){ initData(); } 这样不合适,在什么时候刷新是根据需要来的,并不是每次onResume()的时候都需要刷新。 假如用户关闭了屏幕后再打开屏幕,页面会刷新,这样没有必要,也许几秒中前刚刷新过。 网易新闻客户端就不是这样的。 2.关于activity,fragment与弹窗 如果一个activity中有一个ViewPager,ViewPager中加载了两个fragment,这时要特别注意的是,两个fragment是同事加载的,虽然只显示了一个fragment的界面,但是另外一个也是加载了的,所以如果当前显示的fragment没有弹窗,但是另一个fragment弹窗了也会显示到当前activity中。 3.copy布局文件和复用布局文件的优缺点 重用优点:减少布局文件个数,如果几个界面的布局问个始终一致,那么可以使用 重用缺点:如果其中一个布局文件有所改动,那么其他页面将不能再使用,因为布局文件中没法使用 if else 。 copy优点:一个页面对应一个xml文件,完全解耦 copy缺点:会大量出现重复的代码 总结:需求随时都可能变动,还是把布局解耦比较好 4.优化代码的思路 哪段代码使用的频率越高就应该先优化哪段代码,这样会事半功倍! 5.千万要保护好自己的代码 昨天跟我说某某模块的接口改了,要重新做,我做了。 今天来的时候又跟我说暂时不改了,还是用之前的代码。 天啦,幸亏我没有删掉之前的代码,不然死的心都有了。 so,程序猿们,保护好你自己的代码。 6.模板方法没有写好会很悲剧 模板方法写好了,比如BaseAcitivity,然后开始写它的子类,忽然调试的时候发现模板的方法有问题,比如说要调整方法调用的顺序或者改变方法调用的位置,这下好了,你的N个子类都是按照之前的模板方法去写的,so 你就一个一个去修改子类吧。 另外,要千万注意别人修改了公用的模板方法,要及时pull和push代码,不然自己写的很多代码都白费了。 7.不要频繁重复的调用数据 比如你要获取10-个新闻列表数据,而且你要同一时间去获取,那就得创建10次Http链接,这很费时的。最好的方法是让服务器把10个新闻列表数据写在一个xml文件中,这样会显著的节约时间。wo so,当进行远程调用时,从数据提供者反复调用取得数据会严重影响性能,比如数据库调用、Web服务调用或者其他编解码调用。这种情况下可以使用Facade模式一次获得所有所需的数据,尽可能减小连接成本和在网络上传输数据的成本。 8.类中的成员变量前加"m" 本类的所有成员变量前加"m",好处是:敲"m"就能提示本类的一些成员变量 9.一定要有自己的技术博客和帖子 别人的博客和帖子是别人的,要有自己的,不断补充,不断完善,以后再查看的时候才方便。 另外 ,强迫自己写技术博客和帖子就是强迫自己总结,加深技术知识的印象。 10.一个类应该纯粹,一个类就只应该做一件事情 比如写一个瀑布流类(自定义View),干了很多事情,布局了瀑布流模型,设置了刷新功能。 这不应该在一个类中出现,瀑布流就是瀑布流,刷新是另外一回事,获取数据又是另外一回事!不要混在一起,写在一个类中! 一个类应该是纯粹的,尽量的简单,就是干一件事情。完成一个功能我们可以把多个类组合在一起,或者引用等等方法。 好处在于拆卸方便,当不需要什么功能的时候能快速明晰的剥离掉。如果什么代码都混在一块那是灰常难以剥离的。
亢奋了一段时间,争分夺秒的写代码,现在突然有点心不在焉,不知道为何,静不下来写代码,明明一个小时可以搞定的东西,就是要不能集中精神,愣是搞了大半天。 静心思故,沉心工作,努力加油
最近网络不错,一高兴把SDK升级了,结果你懂的----SVN只能检出,不能上传。 我的SDK升级到4.2.2了,ADT17,本来呢 eclipse安装的SVN插件1.6.18,服务器版本1.6, 结果SDK一升级 prefencese中tem SVN升到了1.7 ,然后呢,悲剧了,只能检出项目,不能上传,显示上传进度为0。 解决思路:将eclipse SVN相关包都要卸载,然后重新安装。 结果:由于一些包是1.82以上,依赖包也需要高版本的,不能使用1.6的。 对策:将安装的包全部卸载,重新下载安装。 重新下载安装还是提示如下错误:Android Native Development Tools 21.1.0.v201302060044-569685 妹的,还需要重新下载一个NDK的包,cdt-master-8.1.0.zip 下载地址在: http://www.eclipse.org/cdt/downloads.php 再次安装这个adt包,安装1.6.18插件,重启eclipse ,更新文件-----OK了。
由于工作原因,大家都可能需要反编译一些apk去学习别人优秀的界面设计或者代码实现,那么网上的关于androd APK反编译的贴已经很多了,大家肯定都非常清楚,有些贴子还非常贴心的给出了反编译工具的下载链接,非常温馨,对于像我这样下载的反编译工具有时找不见,然后又去翻帖子,可以很快的获得整套工具实现反编译,非常感谢他们!! 同时在看帖子的同时发现有个小小的问题,就是这些帖子给出的托管在服务器上链接下载地址,有时帖子写的比较早,下载的反编译工具版本低,就无法反编译使用新版本的aapt编译出来的apk,同时许多人下载以后并没有看看官网,这工具来源和版本,了解更多一些东西,当然这只是针对于android开发职业的要求,俗话说蛋好吃,无须知道是那只鸡下的蛋,但是如果你是专业下蛋的,那还是得了解下好哇。 背景交代完毕,下面给介绍一下反编译APK流程,同时附上最新的反编译工具下载地址以及谷歌官网下载链接地址,做专业下蛋。 工具列表: jd-gui,用来查看反编译源码的代码工具,版本比较稳定,一般来说不需要更新 官网地址为http://java.decompiler.free.fr/?q=jdgui, 下载地址为:http://jd.benow.ca/jd-gui/downloads/jd-gui-0.3.5.windows.zip apktools, 用来反编译源代码和图片、XML配置、语言资源等文件 ,反编译出来的源代码都是一些无法看的文件,这个工具主要用来反编译界面上配置,或者是用来汉化软件。 官网地址:http://code.google.com/p/android-apktool/ 托管地址 :https://code.google.com/p/android-apktool/downloads/list 注意最新的版本为1.5.2,2013.2.2更新的,低于这个版本需要更新。 1.5.2 下载链接: https://android-apktool.googlecode.com/files/apktool1.5.2.tar.bz2 dex2jar ,用来反编译源码的工具,由谷歌提供,版本在不断更新,反编译版本比编译版本低,就会发生无法反编译成功的意外。 这个工具理解上就是讲dex文件反编译成jar包,而jar我们可以通过jd_gui来查看源码,当然,这里反编译的代码是经过谷歌官方的混淆的。 官网地址: http://code.google.com/p/dex2jar/ 托管地址: http://code.google.com/p/dex2jar/downloads/list 注意最新版本为 dex2jar-0.0.9.11 , 2012.10.25更新的,低于这个版本需要更新。 0.0.9.11 下载地址:http://dex2jar.googlecode.com/files/dex2jar-0.0.9.13.zip 注意:经过一段时间版本可能会更新,如果大家按照正常的方式提示编译不成功的话,大家则去到官网查看最新版本,是否需要升级 。 以下部分为新手反编译准备,也欢迎大家指正错误,谢谢。 下面是反编译百度地图的步骤: 1.工具准备 ---百度地图 apk+dex2jar+apktools+jd-jui 2.反编译代码 首先将apk文件后缀改为zip并解压,得到其中的classes.dex,它就是java文件编译再通过dx工具打包而成的,将classes.dex复制到dex2jar.bat所在目录dex2jar-0.0.9.13文件夹。 在命令行下定位到dex2jar.bat所在目录,运行 1 D:\360Downloads\Apk\dex2jar-0.0.9.13>dex2jar.bat classes.dex 2 this cmd is deprecated, use the d2j-dex2jar if possible 3 dex2jar version: translator-0.0.9.13 4 dex2jar classes.dex -> classes_dex2jar.jar 5 Done. 使用jd-gui打开,如图: 3. 使用apktools,反编译如下: C:\Users\Administrator>d: D:\360Downloads\Apk\apktool1.5.2>apktool.bat d -f baidumap.apk baidu Input file (baidumap.apk) was not found or was not readable. D:\360Downloads\Apk\apktool1.5.2>apktool.bat d -f D:\360Downloads\Apk\baidumap.a pk D:\360Downloads\Apk\baidumap I: Baksmaling... I: Loading resource table... I: Loaded. I: Decoding AndroidManifest.xml with resources... I: Loading resource table from file: C:\Users\Administrator\apktool\framework\1. apk I: Loaded. I: Regular manifest package... I: Decoding file-resources... I: Decoding values */* XMLs... I: Done. I: Copying assets and libs... D:\360Downloads\Apk\apktool1.5.2> 接下来看下反编译出来的东西 代码都是smali,所以你是很难看懂的啦。 通过看assert里面文件夹,其实发现百度地图加载页面其实本地已经有了,服务器传输一些数据过来显示即可,这好像很多程序都是这样做的, 但是注意了,百度的是放在assert里面不是res中,为什么呢?大家想想。 我这次主要就是想看看manifest文件, 我现在关注manifest中,启动百度地图的方式和原理是神马,大家请看下面: 百度manifest文件: 谷歌manifest文件: 百度intent-filter全部如下: View Code 1 <intent-filter> 2 <action android:name="android.intent.action.VIEW" /> 3 <category android:name="android.intent.category.DEFAULT" /> 4 <data android:mimeType="vnd.android.cursor.item/postal-address_v2" /> 5 </intent-filter> 6 <intent-filter> 7 <action android:name="android.intent.action.CREATE_SHORTCUT" /> 8 </intent-filter> 9 <intent-filter> 10 <action android:name="android.intent.action.VIEW" /> 11 <category android:name="android.intent.category.DEFAULT" /> 12 <data android:mimeType="vnd.android.cursor.item/postal-address" /> 13 </intent-filter> 14 <intent-filter> 15 <action android:name="android.intent.action.VIEW" /> 16 <category android:name="android.intent.category.DEFAULT" /> 17 <data android:scheme="geo" /> 18 </intent-filter> 19 <intent-filter> 20 <action android:name="android.intent.action.VIEW" /> 21 <category android:name="android.intent.category.DEFAULT" /> 22 <data android:scheme="http" android:host="j.map.baidu.com" /> 23 </intent-filter> 24 <intent-filter> 25 <action android:name="android.intent.action.VIEW" /> 26 <category android:name="android.intent.category.DEFAULT" /> 27 <category android:name="android.intent.category.BROWSABLE" /> 28 <data android:scheme="bdapp" android:host="map" /> 29 </intent-filter> 30 <intent-filter> 31 <action android:name="android.intent.action.VIEW" /> 32 <category android:name="android.intent.category.DEFAULT" /> 33 <data android:scheme="http" android:host="api.map.baidu.com" /> 34 </intent-filter> 接下来分析一下intent-filter, 1.通过 发送联系人地址 启动百度地图 APP 在这个intent-filter中,开始的两行说 当你想查看 联系人地址 时,使用地图直接帮你定位到地图中,非常人性化吧, 但是为什么这里给了两个intent-filter呢? postal-address_v2 以及 postal-address,你懂的,android版本差异咯。 对比谷歌的查看联系人地址信息intent-filter 1 <intent-filter android:label="@string/MAPS_APP_NAME"> 2 <action android:name="android.intent.action.VIEW" /> 3 <category android:name="android.intent.category.DEFAULT" /> 4 <data android:mimeType="vnd.android.cursor.item/postal-address" /> 5 </intent-filter> 6 <intent-filter android:label="@string/MAPS_APP_NAME"> 7 <action android:name="android.intent.action.VIEW" /> 8 <category android:name="android.intent.category.DEFAULT" /> 9 <data android:mimeType="vnd.android.cursor.item/postal-address_v2" /> 10 </intent-filter> 2.通过 geo,启动百度地图APP intent发送geo方式, 百度的 1 <intent-filter> 2 <action android:name="android.intent.action.VIEW" /> 3 <category android:name="android.intent.category.DEFAULT" /> 4 <data android:scheme="geo" /> 5 </intent-filter> 这个意思就是视同intent.action.view,方式来查看某个地址,而且很巧的是这个地址的开头是geo,那么就通过百度地图APP来打开 谷歌的 1 <intent-filter android:label="@string/MAPS_APP_NAME"> 2 <action android:name="android.intent.action.VIEW" /> 3 <category android:name="android.intent.category.DEFAULT" /> 4 <category android:name="android.intent.category.BROWSABLE" /> 5 <data android:scheme="geo" /> 6 </intent-filter> 这个意思就是说通过浏览器打开一个地址,而且很巧的是这个地址的开头是geo,那么就通过百度地图APP来打开 3.http打开百度地图 百度 View Code 1 <intent-filter> 2 <action android:name="android.intent.action.VIEW" /> 3 <category android:name="android.intent.category.DEFAULT" /> 4 <data android:scheme="http" android:host="j.map.baidu.com" /> 5 </intent-filter> 6 <intent-filter> 7 <action android:name="android.intent.action.VIEW" /> 8 <category android:name="android.intent.category.DEFAULT" /> 9 <categoryandroid:name="android.intent.category.BROWSABLE" /> 10 <data android:scheme="bdapp" android:host="map" /> 11 </intent-filter> 12 <intent-filter> 13 <action android:name="android.intent.action.VIEW" /> 14 <category android:name="android.intent.category.DEFAULT" /> 15 <dataandroid:scheme="http"android:host="api.map.baidu.com" /> 16 </intent-filter> 这个意思是说,使用intent-filter-view,启动一个activity,intent中带的数据sceme = http 或者bdapp,且 host= j.baidu.com或者是api.map.baidu.com 的时候,那么就启动百度地图APP 谷歌的 View Code 1 <intent-filter> 2 <action android:name="android.intent.action.VIEW" /> 3 <category android:name="android.intent.category.DEFAULT" /> 4 <category android:name="android.intent.category.BROWSABLE" /> 5 <data android:scheme="http" /> 6 <data android:scheme="https" /> 7 <data android:host="maps.google.com.br" android:path="/" /> 8 </intent-filter> 9 <intent-filter> 10 <action android:name="android.intent.action.VIEW" /> 11 <category android:name="android.intent.category.DEFAULT" /> 12 <category android:name="android.intent.category.BROWSABLE" /> 13 <data android:scheme="http" /> 14 <data android:scheme="https" /> 15 <data android:host="maps.google.com.br" android:pathPrefix="/maps" /> 16 </intent-filter> 意思是使用intent-filter-view,启动一个activity,intent中带的数据sceme = http,且 host= j.baidu.com 的时候,那么就启动谷歌地图APP, 那么这里有个地方 host,谷歌给很多国家都对应一个host 比如英国:maps.google.com.br 法国:maps.google.fr 等等。 4.谷歌地图有的但是百度地图没有的启动方式 a) 通过nfc近场通讯 谷歌的: 1 <intent-filter> 2 <action android:name="android.nfc.action.NDEF_DISCOVERED" /> 3 <category android:name="android.intent.category.DEFAULT" /> 4 <data android:scheme="http" /> 5 <data android:scheme="https" /> 6 <data android:host="maps.google.com.br" android:path="/" /> 7 </intent-filter> 8 <intent-filter> 9 <action android:name="android.nfc.action.NDEF_DISCOVERED" /> 10 <category android:name="android.intent.category.DEFAULT" /> 11 <data android:scheme="http" /> 12 <data android:scheme="https" /> 13 <data android:host="maps.google.com.br" android:pathPrefix="/maps" /> 14 </intent-filter> b)通过search来 启动 1 <intent-filter> 2 <action android:name="android.intent.action.SEARCH" /> 3 <category android:name="android.intent.category.DEFAULT" /> 4 </intent-filter> c) 通过设置 网络来启动 1 <intent-filter android:label="@string/MAPS_APP_NAME"> 2 <action android:name="android.intent.action.MANAGE_NETWORK_USAGE" /> 3 <category android:name="android.intent.category.DEFAULT" /> 4 </intent-filter> 5 <intent-filter android:label="@string/MAPS_APP_NAME"> 6 <action android:name="com.google.android.apps.maps.LOCATION_SETTINGS" /> 7 <category android:name="android.intent.category.DEFAULT" /> 8 </intent-filter> d)通过 全景图 来启动 1 <intent-filter android:label="@string/MAPS_APP_NAME"> 2 <action android:name="android.intent.action.SEND" /> 3 <category android:name="android.intent.category.DEFAULT" /> 4 <data android:mimeType="application/vnd.google.panorama360+jpg" /> 5 </intent-filter> 这里大家可能不清楚 application/vnd.google.panorama360+jpg 这是个什么玩意,其实这个是谷歌应用市场上的一个付费app。具体大家可以看到 https://play.google.com/store/apps/details?id=com.occipital.panorama e) 这个我也不知道是用来干嘛的 lol 。。。。 1 <intent-filter android:label="@string/MAPS_APP_NAME"> 2 <action android:name="android.intent.action.VIEW" /> 3 <category android:name="android.intent.category.DEFAULT" /> 4 <data android:scheme="google.layeritemdetails" /> 5 </intent-filter> 有什么不对,请大家指正。谢谢
已经通过实测解决了昨天的问题,但是现在此刻眼下火烧眉头的说这个问题真是困扰我了。实在无法得知,如何解决??。求解啊!!!! 使用make以后报错如下: host C: acp <= build/tools/acp/acp.c cc: error trying to exec 'cc1': execvp: 没有那个文件或目录 make: *** [out/host/linux-x86/obj/EXECUTABLES/acp_intermediates/acp.o] 错误 1 我配置的环境,以及系统版本如下: 环境中安装source.android.com中都有配置 系统ubuntu 12.10 32位 jdk安装如下 cxl@cxl:~/WORKING_DIRECTORY$ java -version java version "1.6.0_43" Java(TM) SE Runtime Environment (build 1.6.0_43-b01) Java HotSpot(TM) Server VM (build 20.14-b01, mixed mode) gcc与g++版本如下: cxl@cxl:~/WORKING_DIRECTORY$ gcc -v Using built-in specs. Target: i686-linux-gnu Configured with: ../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.4.7-2ubuntu1' --with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs --enable-languages=c,c++,fortran --prefix=/usr --program-suffix=-4.4 --enable-shared --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.4 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-targets=all --disable-werror --with-arch-32=i686 --with-tune=generic --enable-checking=release --build=i686-linux-gnu --host=i686-linux-gnu --target=i686-linux-gnu Thread model: posix gcc version 4.4.7 (Ubuntu/Linaro 4.4.7-2ubuntu1) cxl@cxl:~/WORKING_DIRECTORY$ g++ -v Using built-in specs. Target: i686-linux-gnu Configured with: ../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.4.7-2ubuntu1' --with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs --enable-languages=c,c++,fortran --prefix=/usr --program-suffix=-4.4 --enable-shared --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.4 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-targets=all --disable-werror --with-arch-32=i686 --with-tune=generic --enable-checking=release --build=i686-linux-gnu --host=i686-linux-gnu --target=i686-linux-gnu Thread model: posix gcc version 4.4.7 (Ubuntu/Linaro 4.4.7-2ubuntu1) cxl@cxl:~/WORKING_DIRECTORY$ 环境配置如下: cxl@cxl:~/WORKING_DIRECTORY$ echo $PATH /opt/jdk1.6.0_38/bin:/opt/jdk1.6.0_38/lib:/opt/jdk1.6.0_43:/usr/lib/lightdm/lightdm:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games 使用whereis 查看,发现cc1真的是没有。 cxl@cxl:~/WORKING_DIRECTORY$ whereis cc cc: /usr/bin/cc /usr/bin/X11/cc /usr/share/man/man1/cc.1.gz cxl@cxl:~/WORKING_DIRECTORY$ whereis cc1 cc1: 求解啊。。。。。
近期在进行android源码编译,环境搭建神码痛苦不堪,在编译过程中更是错误不断,想想在windows环境下习惯了,切换到ubuntu上来操作,真真到一时难以适应。 各位看官,下面问题是出现在-------环境已经正确配置完成,执行make时出现的错误以及解决办法。希望能对大家有所帮助。 首先说一下,目前环境搭建/系统版本/android版本, 环境搭建 :大家可以按照官网, source.android.com ,执行。 系统版本: ubuntu 12.10 32位系统 android版本 4.0.1r1 jdk:1.6(这里大家注意,编译android ) 注:1.因为我安装系统语言位中文,如果大家到系统版本位英文 提示错误信息中 “错误” 会为“error”。 2.以下说多少行,可能会不一样,可以通过查找定位。我使用的grepedit,. 一. make: *** [out/host/linux-x86/obj/EXECUTABLES/emugen_intermediates/main.o] 错误 1 或者 make: *** [out/host/linux-x86/obj/EXECUTABLES/emugen_intermediates/main.o] error 1 解决办法: 需要在 development/tools/emulator/opengl/host/tools/emugen/main.cpp 在声明中增加一条头文件声明 #include <getopt.h> 二. frameworks/base/include/utils/KeyedVector.h:193:31: 错误: ‘indexOfKey’ was not declared in this scope, and no declarations were found by argument-dependent lookup at the point of instantiation [-fpermissive] 或者 frameworks/base/include/utils/KeyedVector.h:193:31: error: ‘indexOfKey’ was not declared in this scope, and no declarations were found by argument-dependent lookup at the point of instantiation [-fpermissive] 解决办法:在 development/tools/emulator/opengl/Android.mk 增加 '-fpermissive' 到25行: EMUGL_COMMON_CFLAGS := -DWITH_GLES2 -fpermissive 三. make: *** [out/host/linux-x86/obj/EXECUTABLES/aapt_intermediates/AaptAssets.o] 错误 1 或者 make: *** [out/host/linux-x86/obj/EXECUTABLES/aapt_intermediates/AaptAssets.o] Error 1 解决办法: frameworks/base/tools/aapt/Android.mk 在第31行增加: LOCAL_CFLAGS += -Wno-format-y2k -fpermissive 四. make: *** [out/host/linux-x86/obj/EXECUTABLES/obbtool_intermediates/Main.o] 错误 1 make: *** [out/host/linux-x86/obj/EXECUTABLES/obbtool_intermediates/Main.o] error 1 解决办法:系版本高,在配置环境的时候,gcc安装了高到版本,所以gcc版本太高导致,需要降低gcc版本级别。 ubuntu 32bit系统下安装gcc 4.4的最好方法是仅用以下两条命令,不需要其它命令,否则编译时可能会出错。 sudo apt-get install gcc-4.4 sudo apt-get install g++-4.4 操作过程见: gcc降级: sudo rm -rf /usr/bin/gcc sudo ln -s /usr/bin/gcc-4.4 /usr/bin/gcc gcc -v g++降级 sudo rm -rf /usr/bin/g++ sudo ln -s /usr/bin/g++-4.4 /usr/bin/g++ g++ -v 演示过程: View Code 1 cxl@cxl:/usr/lib$ gcc -v 2 使用内建 specs。 3 COLLECT_GCC=gcc 4 COLLECT_LTO_WRAPPER=/usr/lib/gcc/i686-linux-gnu/4.7/lto-wrapper 5 目标:i686-linux-gnu 6 配置为:../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.7.2-2ubuntu1' --with-bugurl=file:///usr/share/doc/gcc-4.7/README.Bugs --enable-languages=c,c++,go,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.7 --enable-shared --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.7 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --enable-plugin --enable-objc-gc --enable-targets=all --disable-werror --with-arch-32=i686 --with-tune=generic --enable-checking=release --build=i686-linux-gnu --host=i686-linux-gnu --target=i686-linux-gnu 7 线程模型:posix 8 gcc 版本 4.7.2 (Ubuntu/Linaro 4.7.2-2ubuntu1) 9 cxl@cxl:/usr/lib$ ls -l gcc 10 总用量 8 11 drwxr-xr-x 3 root root 4096 4月 1 21:25 i586-mingw32msvc 12 drwxr-xr-x 4 root root 4096 4月 2 10:46 i686-linux-gnu 13 cxl@cxl:/usr/lib$ sudo mv gcc gcc.bak 14 cxl@cxl:/usr/lib$ sudo ln -s gcc-4.4 gcc 15 cxl@cxl:/usr/lib$ gcc -v 16 使用内建 specs。 17 COLLECT_GCC=gcc 18 目标:i686-linux-gnu 19 配置为:../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.7.2-2ubuntu1' --with-bugurl=file:///usr/share/doc/gcc-4.7/README.Bugs --enable-languages=c,c++,go,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.7 --enable-shared --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.7 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --enable-plugin --enable-objc-gc --enable-targets=all --disable-werror --with-arch-32=i686 --with-tune=generic --enable-checking=release --build=i686-linux-gnu --host=i686-linux-gnu --target=i686-linux-gnu 20 线程模型:posix 21 gcc 版本 4.7.2 (Ubuntu/Linaro 4.7.2-2ubuntu1) 22 cxl@cxl:/usr/lib$ clear 23 24 cxl@cxl:/usr/lib$ ls -l gcc* 25 lrwxrwxrwx 1 root root 7 4月 2 11:05 gcc -> gcc-4.4 26 27 gcc.bak: 28 总用量 8 29 drwxr-xr-x 3 root root 4096 4月 1 21:25 i586-mingw32msvc 30 drwxr-xr-x 4 root root 4096 4月 2 10:46 i686-linux-gnu 31 cxl@cxl:/usr/lib$ gcc -v 32 使用内建 specs。 33 COLLECT_GCC=gcc 34 目标:i686-linux-gnu 35 配置为:../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.7.2-2ubuntu1' --with-bugurl=file:///usr/share/doc/gcc-4.7/README.Bugs --enable-languages=c,c++,go,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.7 --enable-shared --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.7 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --enable-plugin --enable-objc-gc --enable-targets=all --disable-werror --with-arch-32=i686 --with-tune=generic --enable-checking=release --build=i686-linux-gnu --host=i686-linux-gnu --target=i686-linux-gnu 36 线程模型:posix 37 gcc 版本 4.7.2 (Ubuntu/Linaro 4.7.2-2ubuntu1) 38 cxl@cxl:/usr/lib$ rm -rf /usr/bin/gcc 39 rm: 无法删除"/usr/bin/gcc": 权限不够 40 cxl@cxl:/usr/lib$ sudo rm -rf /usr/bin/gcc 41 cxl@cxl:/usr/lib$ sudo ln -s /usr/bin/gcc-4.4 /usr/bin/gcc 42 cxl@cxl:/usr/lib$ gcc -v 43 Using built-in specs. 44 Target: i686-linux-gnu 45 Configured with: ../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.4.7-2ubuntu1' --with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs --enable-languages=c,c++,fortran --prefix=/usr --program-suffix=-4.4 --enable-shared --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.4 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-targets=all --disable-werror --with-arch-32=i686 --with-tune=generic --enable-checking=release --build=i686-linux-gnu --host=i686-linux-gnu --target=i686-linux-gnu 46 Thread model: posix 47 gcc version 4.4.7 (Ubuntu/Linaro 4.4.7-2ubuntu1) View Code cxl@cxl:/usr/lib$ g++ -v 使用内建 specs。 COLLECT_GCC=g++ 目标:i686-linux-gnu 配置为:../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.7.2-2ubuntu1' --with-bugurl=file:///usr/share/doc/gcc-4.7/README.Bugs --enable-languages=c,c++,go,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.7 --enable-shared --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.7 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --enable-plugin --enable-objc-gc --enable-targets=all --disable-werror --with-arch-32=i686 --with-tune=generic --enable-checking=release --build=i686-linux-gnu --host=i686-linux-gnu --target=i686-linux-gnu 线程模型:posix gcc 版本 4.7.2 (Ubuntu/Linaro 4.7.2-2ubuntu1) cxl@cxl:/usr/lib$ sudo rm -rf /usr/bin/g++ cxl@cxl:/usr/lib$ sudo ln -s /usr/bin/g++-4.4 /usr/bin/g++ cxl@cxl:/usr/lib$ g++ -v Using built-in specs. Target: i686-linux-gnu Configured with: ../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.4.7-2ubuntu1' --with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs --enable-languages=c,c++,fortran --prefix=/usr --program-suffix=-4.4 --enable-shared --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.4 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-targets=all --disable-werror --with-arch-32=i686 --with-tune=generic --enable-checking=release --build=i686-linux-gnu --host=i686-linux-gnu --target=i686-linux-gnu Thread model: posix gcc version 4.4.7 (Ubuntu/Linaro 4.4.7-2ubuntu1) 五.make: *** [out/host/linux-x86/obj/STATIC_LIBRARIES/libutils_intermediates/AssetManager.o] 错误 1 或者 make: *** [out/host/linux-x86/obj/STATIC_LIBRARIES/libutils_intermediates/AssetManager.o] Error 1 解决办法:在 frameworks/base/libs/utils/Android.mk 在第60行后面增加-fpermissive: LOCAL_CFLAGS += -DLIBUTILS_NATIVE=1 $(TOOL_CFLAGS) -fpermissive 六. make: *** [out/host/linux-x86/obj/EXECUTABLES/grxmlcompile_intermediates/grxmlcompile.o] 错误 1 或者 make: *** [out/host/linux-x86/obj/EXECUTABLES/grxmlcompile_intermediates/grxmlcompile.o] Error 1 解决办法:cd external/srec 复制拷贝下面的命令到终端: wget "https://github.com/CyanogenMod/android_external_srec/commit/4d7ae7b79eda47e489669fbbe1f91ec501d42fb2.diff" patch -p1 < 4d7ae7b79eda47e489669fbbe1f91ec501d42fb2.diff rm -f 4d7ae7b79eda47e489669fbbe1f91ec501d42fb2.diff cd ../.. 七. make: *** [/home/arun/cm10.1/out/target/product/s100/obj/STATIC_LIBRARIES/libwebcore_intermediates/Source/WebCore/bindings/V8CSSCharsetRule.h] 错误 1 或者make: *** [/home/arun/cm10.1/out/target/product/s100/obj/STATIC_LIBRARIES/libwebcore_intermediates/Source/WebCore/bindings/V8CSSCharsetRule.h] Error 1 解决办法:sudo apt-get install libdigest-md5-file-perl 八. make: *** [out/host/linux-x86/obj/EXECUTABLES/acp_intermediates/acp.o] 错误 1 或者 make: *** [out/host/linux-x86/obj/EXECUTABLES/acp_intermediates/acp.o] error 1 解决办法:
http://blog.csdn.net/mapdigit/article/details/8711799
首先接着上一篇,为什么谷歌设计联系人显示的时候姓名和电话不一起显示? 这里我们先到谷歌官方看联系人的介绍: The Contacts Provider is an Android content provider component. It maintains three types of data about a person, each of which corresponds to a table offered by the provider, as illustrated in figurel. 联系人数据是通过contentprovider来提供对外数据访问的。联系人内容提供者包含了联系人的三种类型数据,每一个 对应于内容提供者的一张表,关系如下图1所示: The three tables are commonly referred to by the names of their contract classes. The classes define constants for content URIs, column names, and column values used by the tables: 上面三张表通常被contact类名所引用,下面的这些表使用了一些类,而这些类定义了uris常量,列名以及列对应的值。 ContactsContract.Contacts table Rows representing different people, based on aggregations of raw contact rows. ContactsContract.RawContacts table Rows containing a summary of a person's data, specific to a user account and type. ContactsContract.Data table Rows containing the details for raw contact, such as email addresses or phone numbers. 主要就是这三张表了。 下面来看看contactprovider对外提供方问方式: 1 public static final String AUTHORITY = "com.android.contacts"; 2 /** A content:// style uri to the authority for the contacts provider */ 3 public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY); 1 /** 2 * The content:// style URI for this table, which requests a directory 3 * of data rows matching the selection criteria. 4 */ 5 public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "data"); 那么访问这些数据是通过封装好的query,delete update,insert等方法,而这些方法具有局限性, 比如查询: 1 Cursor c = getContentResolver().query(RawContacts.CONTENT_URI, 2 new String[]{RawContacts._ID}, 3 RawContacts.CONTACT_ID + "=?", 4 new String[]{String.valueOf(contactId)}, null); 5 那么这里就有一个问题,就是一次只能查一张表,不能进行联合查询,因为使用resolver的默认方法中,它是将里面的内容填到sql语句对应的条件中,这种做法的好处是可以方便使用,其中封装对一些特殊字符的处理,但是问题是在相关的条件中没有联合查询如外连接 内连接这些,因此导致了在使用contentprovider访问系统数据就会有一次只能访问一个表的结果。 那么系统提供一个范例,那就是可以先查询Raw.contact这个表,显示联系人的姓名,当点击这个联系人再根据联系人_id,通过data这个表获得联系人的其他信息。因此在很多厂商通讯录app在设计上就规避了这种风险,显示的时候,先显示姓名,通过多一步的操作来完成联系人详细信息的展示。 那么假设如果产品经理要求你显示的时候就如同下面这样,该怎么办? 做还是不做?? 要做的话 要怎么做?
android通信录想必大家都不会陌生,有系统自带的联系人,也有比如qq通信录,百度通讯录,,来电通,go联系人等。每种大家可能都有有偏好喜爱,但是这里我个人推荐大家使用qq通信录、百度通讯录以及来电通。 这些在界面上都具有相同的东西,比如下方一般是3-4个tab,分别为通话(拨号),联系人,信息,设置(工具),使用tabactivity,完成框架。都具有云同步的功能,云同步里面就有比较多的内容,账号、个人信息、各种备份、各种还原、归属地、隐私设置保存同步、等等。 下面就通讯录中联系人模块进行比较: qq、百度通讯录在ui上总是会有不时的创意,这在视觉效果上更加让人保持新鲜感,qq的listview可以拖拉出来,直接拨号,这个功能非常实用,而且尤其是qq集成本身的一些其他功能,比如qq平台,微信平台,使用非常方便; QQ通信录 一.联系人主界面。 这里可以看到qq通讯录比一般的通讯录多了一个分组概念以及多了字母索引条多了一个 "?",同时"#"在最上面。 如果没有头像则显示姓名首个汉字,若有检索内容则显示检索内容首个汉字。 而且多了一个分组的popwindow,这个也是很实用的东西。 主界面比较柔和近人,比上个版本蓝色那种格调要高,设计师很有色系强迫症的人,设计了主色为灰色但是在同时完全是灰色那种需要很好品味才能接受,就像房子装修中主灰色调,一般人都受不了是一样的,所以主色调又做了妥协。 二.字母索引条与listview listview当前显示的首字母与右边字母列表对应,同时右边字母索引可以看到是使用居中对齐的,J L W这些显示视觉上不会突兀。 三.listview悬浮view 在listview中没有按照姓名首字母分组。 四.组标题导航 在listview中没有按照姓名首字母分组。也就没有组标题。这里可以按照联系人所属分组进行归类显示。 五.联系人检索效率与联系人查找 联系人检索上手机大约450条通话记录,18个联系人,4百条短信,第一次打开,程序不会卡,但是会有点慢。 常规的数字 字母 首字母检索 这些都是没有问题的,下面进行一些智能化检索测试看看 第一、wan与wang的智能化匹配 第二 连续检索wanj 这里 把WAnGJiAn给剔除了,也就是说名字不能进行非连续行关键字检索。 第三 这里再试试拼音是否可以进行非连续性检索,比如“见王见”,忘记这人中间是个什么字了,只知道叫“见*见”,输入“jj”,是否可以检索,看下结果: 说明qq无法进行非连续检索 第四,多音字的处理 单这个字 可以使 shan 可以是 dan 还可以是chán,那么分别输入这几个拼音看看是否可以。 由此可见qq支持多音字检索,qq使用检索的原理应该是使用全字库,然后遍历检索拼音,这样不管怎么搜都能检索出来。 来电通 一.联系人主界面。来电通的检索联系人的界面也是不赖,对联系人按照首字母进行分组显示 二.字母索引条与listview。当前显示联系人分组与右边字母索引条应该是要保持一致的。 第一、发现来电通在处理右边的字母索引条的字母J Q W这些对齐方式上有一些处理不是非常好的地方,他们的item布局应该是默认的距左对齐,导致了字母没有居中,在视觉上存在一点不足。修正方案:将字母索引条item布局改为 居中即可。 第二、发现listview和第二个组标题以后的交界处存在一条线,当然这些是细节。 三.listview悬浮view。悬浮当前显示的组标题view没有,不知道是不是特意如此。 (系统) 四.组标题导航。检索的时候点击字母索引条,界面上出现一个view,提示用户该分组分属下面的姓名首个字有哪些。也可点击首字母分组标题进入另外一个界面选择,按照字母和特殊符号归类,没有所属分组则灰色不可用显示。这种方式比qq、百度通讯录都特别,而且好在点击区域很大,方便用户操作尤其是中老年人,考虑到字母索引其实点击区域很小,不方便用户选择,考虑到了用户交互性这一点非常可取的。但是同时也会带来一些缺点---用户操作上的繁琐,要多麻烦用户点击一次,而且切换到另外一个界面,这在用户粘合度上会差一些。在用户统计上,这个功能使用的应该不是太多。 五.联系人检索效率与联系人查找 联系人检索上手机大约450条通话记录,18个联系人,4百条短信,第一次打开来电通,会感觉到程序慢卡,切换tab时,点不动。 常规的数字 字母 首字母检索 这些都是没有问题的,下面进行一些智能化检索测试看看 联系人查找: 第一、wan与wang的智能化匹配 可以看到这里可以比较智能的匹配到后缀 "g",方便许多拼音不标准的同学。 第二 连续检索wanj 可以看到这里也可以实现智能化的连续检索,即使某个拼音出错了依然可以完成匹配。 但是这里有一点遗憾的是,把WAnGJiAn给剔除了,也就是说名字不能进行非连续行关键字检索。 第三 这里再试试拼音是否可以进行非连续性检索,比如“见王见”,忘记这人中间是个什么字了,只知道叫“见*见”,输入“jj”,是否可以检索,看下结果: 这里发现来电通都是不能进行非连续性的检索联系人的。 第四,多音字的处理 单这个字 可以使 shan 可以是 dan 还可以是chán,那么分别输入这几个拼音看看是否可以。 这我表示凌乱了,难道来电通字库少了 chan ??? 总结:1.通讯录界面元素 总共就是 界面顶部标题+搜索内容框+字母索引条+listview,显示的是这样,还有一些可能有popwindow等。 2. 联系人界面关键的东西就是三个:UI+联系人读取+联系人检索 3.大家可能都没有注意到,所有的通讯录app联系人显示都是显示联系人的名字,连系统的通讯录也是这样设计的,如果要看到联系人电话,需要多操作一步,给用户带来不便, qq通讯录还可以拖动item 实现快速拨号。那么这里有个问题,明明带电话和姓名一起显示的给用户更好的体验,为什么谷歌要这样设计,而且市面上通讯录app也遵循这样的设计呢? 请见下回分解。 当然其他的app不是说完全不好,但是使用时候个人感觉更加满意,再加上更新上更加及时,这里仁者见仁智者见智,大家勿喷。
http://www.oschina.net/question/129540_64132
原文:http://www.cnblogs.com/productivity/archive/2012/10/25/2738238.html 如何摆脱低智商的社会,让自己脱颖而出? 看了大前研一的【低智商社会】后深有感触,日本如此发达的社会,大前研一却对于日本年轻人沉溺于动漫、网络游戏、网络社交,出现集体智商衰退的现象忧国忧民。日本人真的是一代不如一代?按照大前研一的说法,就是现在日本已经进入到了“低智商社会”。本书从日本的政治、经济、网络社会、教育等各个范畴去分析,点出了各种现实存在的低智商现象,给想在21世纪生存下去的日本人最后的当头棒喝,也给其他国家的人们一个强烈的警示,从而使我们反思面对的问题。 其实在中国更严重,在IT团队跟更更严重! 我经常对我的同事们讲的一个观点:在IT界成功真是太容易了。1.最容易成功和获得一份不错薪资的行业就是IT,只要你有学习能力,因为你所有要学的东西,都在网上,通过Google可以获得你想要提高的所有东西。2.只要你坚持不断的写程序,少说多写,你就很容易脱颖而出,因为你周围大部分人都在玩,有的在看垃圾小说,有的在聊天和微博,有的在低头玩弄智能手机。很少有人能够驱动自己从头到尾,从前端到后端,从页面到数据库,独立写一个完整的软件,并为之不断的改进。 网络是个让人又爱又恨的地方,我们写软件无法摆脱网络,但是网络让我们效率是如此的低下。十一国庆,我在家,老婆孩子会娘家了,没有一个人打扰我,我发誓要在8天之内,用C#重写原来的通信服务器软件。可是这8天内我做了什么呢?1.因为在家,很松弛,我发现我非常不愿意动手写代码,我愿意查资料,我也不愿意写代码,结果第一天我查了和下载了一通资料和代码;2.第二天,好声音决赛,我在优酷上把好声音看了够;3.第三天我的负罪感加深了,我决定建立个工程,开始写代码,我有了初步的进展,有一个工程了,并把以前的C++的代码翻看了一遍,思考要改进的地方,写下了自己的设计思路,画了2张草图,并写了Socket的公用库。这距离我的目标还很遥远,中间我又花时间升级了我的android手机还看了个电视剧,白天想着晚上有时间,到晚上吃完饭犯困,9点多就睡了。4.第四天我觉得写底层代码,推进太慢,从界面驱动入手,要让自己的工程可以运行和测试,从而不断的推进自己的工作。在写完界面后,我有点得意,放下工作,又犯了浏览网络博客的老毛病,看了很多精彩的博客文章,并放入自己的Evernote中。5.后面的几天,老婆孩子回来了,工作效率更低了,基本上写了界面和底层的协议库,软件从整体上没有运行起来。6.上班后,坐在办公室的感觉真好,由花了一个星期的时间,总算运行起来了。这个工作总共花费了15天,实际上我估算,如果集中精神,每天工作满8个小时,7天就完事了。 在现实中,我们的注意力都无法保持一个稳定的状态,网上的东西太多了,如果没有外界的压力,我们的工作效率就更低了,如果我们能超越这一点,我们就能做到和周边的人不一样,这样我们就能很容易脱颖而出得到上司的注意。因为别人都手慢,而你手快,你执行力强,你能够很快的写出一个可以运行的东西,上司就愿意委以重任。 现在几乎所有的项目经理在制定计划时,都会迁就我们低效的工作状态,美名其曰,留出冗余。其实我们很多的功能,如果半天完成,必定一天,如果两天能完事,必定要三天、四天的这样拖着。很多人都像有毒瘾一样,从上学开始,大学中,考试前几天,才开始看书,项目中总是在Milestone的最后时刻,才开始稍微动作起来。 大部分人几乎整年的不看书不买书,经常在网上浏览文章,会造成知识的碎片化,微博就更加严重,一本好书是从头到尾的让你系统化。无论是技术还是项目管理的书都是这样。我们不能满足于一点点的技巧性的文章。你有没有发现让你解释个东西的时候,你会结结巴巴的,无法从头到尾系统的阐述某个东西。 大部分人的能力和知识面都很局限,看看下面,自己能不能做到:1.有没有自己一个人写过一个完整的可以运行的软件,B/S或C/S都行;2.有没有完整的看过非小说的书,如测试驱动、配置管理、项目管理等等;3.知不知道时间管理,想不想对改进自己的时间管理的能力,有没有使用过GTD或番茄时间管理法之类的工具,把自己的事情系统的整理一下;4.有没有持续集中自己的注意力,不浏览网络,写一个小时代码的经历。 我只是想深刻的反省自己,并提高自己,和大家做个分享,如果我们8个小时能够做到高效率,我们的生活不会那么苦,反而会获得升迁和提拔。 集中精力多做实事
http://www.cnblogs.com/loulijun/archive/2012/02/03/2337230.html 原文地址 许多联网应用都在开始运行的时候检查当前网络状态,如果没有开启则去开启它,记录一下以前写程序时的网络检查,发现人的记忆力真是有限,总是隔段时间久忘记,所以记录下来是最好的记忆。 我们可以在一开始启动程序的时候检查网络连接状况,如果没有开启则弹出对话框设置网络 首先需要加入权限 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission> <uses-permission android:name="android.permission.INTERNET"/> 检查网络状态代码如下 public boolean CheckNetworkState() { boolean flag = false; ConnectivityManager manager = (ConnectivityManager)getSystemService( Context.CONNECTIVITY_SERVICE); if(manager.getActiveNetworkInfo() != null) { flag = manager.getActiveNetworkInfo().isAvailable(); } if(!flag) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setIcon(android.R.drawable.ic_dialog_alert); builder.setTitle(R.string.netstate); builder.setMessage(R.string.setnetwork); builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // TODO Auto-generated method stub Intent mIntent = new Intent("/"); ComponentName comp = new ComponentName("com.android.settings", "com.android.settings.WirelessSettings"); mIntent.setComponent(comp); mIntent.setAction("android.intent.action.VIEW"); startActivity(mIntent); } }); builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } }); builder.create(); builder.show(); } return flag; } 效果如下,用户可以点击确定来设置网络,比如启动wifi 如果希望网络连接时做一些事情的话,需要判断当前网络状态是否为true,为真则进行一些操作,否则设置网络。还可以用另外一种方式,其实差不多 //setnetwork public void setNetwork() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setIcon(android.R.drawable.ic_dialog_alert); builder.setTitle(R.string.netstate); builder.setMessage(R.string.setnetwork); builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // TODO Auto-generated method stub Intent mIntent = new Intent("/"); ComponentName comp = new ComponentName("com.android.settings", "com.android.settings.WirelessSettings"); mIntent.setComponent(comp); mIntent.setAction("android.intent.action.VIEW"); startActivity(mIntent); } }); builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } }); builder.create(); builder.show(); } //NETWORK public boolean isNetworkAvailable() { Context context = getApplicationContext(); ConnectivityManager connect = (ConnectivityManager)context.getSystemService( Context.CONNECTIVITY_SERVICE); if(connect==null) { return false; }else//get all network info { NetworkInfo[] info = connect.getAllNetworkInfo(); if(info!=null) { for(int i=0;i<info.length;i++) { if(info[i].getState()==NetworkInfo.State.CONNECTED) { return true; } } } } return false; } 然后通过判断如果当前状态可用则执行相关代码,不可用则设置网络 if(isNetworkAvailable()) { 相关代码 }else { setNetwork(); } 可以通过ConnectivityManager获取当前网络连接状态,通过状态值判别当前网络连接 NetworkInfo info = conMan.getActiveNetworkInfo(); if(info !=null && info.getType() == ConnectivityManager.TYPE_MOBILE) { // NETWORK_TYPE_EVDO_A是电信3G //NETWORK_TYPE_EVDO_A是中国电信3G的getNetworkType //NETWORK_TYPE_CDMA电信2G是CDMA //移动2G卡 + CMCC + 2//type = NETWORK_TYPE_EDGE //联通的2G经过测试 China Unicom 1 NETWORK_TYPE_GPRS if(info.getSubtype() == TelephonyManager.NETWORK_TYPE_GPRS || info.getSubtype() == TelephonyManager.NETWORK_TYPE_CDMA || info.getSubtype() == TelephonyManager.NETWORK_TYPE_EDGE){ System.out.println("mobile connected"); } else{ System.out.println("type:"+info.getSubtype()); System.out.println("not mobile"); } }else System.out.println("not mobile connected"); 通过getType的值可以得到以下2g|3g网络networktype 1~15分别为:联通2G,移动2G,电信2G,电信3G,电信3G,电信3G,联通3G,联通3G,LTE,IDEN,HSUPA,HSPA,HSPAP
com.android.settings.AccessibilitySettings 辅助功能设置 com.android.settings.ActivityPicker 选择活动 com.android.settings.ApnSettings APN设置 com.android.settings.ApplicationSettings 应用程序设置 com.android.settings.BandMode 设置GSM/UMTS波段 com.android.settings.BatteryInfo 电池信息 com.android.settings.DateTimeSettings 日期和坝上旅游网时间设置 com.android.settings.DateTimeSettingsSetupWizard 日期和时间设置 com.android.settings.DevelopmentSettings 应用程序设置=》开发设置 com.android.settings.DeviceAdminSettings 设备管理器 com.android.settings.DeviceInfoSettings 关于手机 com.android.settings.Display 显示——设置显示字体大小及预览 com.android.settings.DisplaySettings 显示设置 com.android.settings.DockSettings 底座设置 com.android.settings.IccLockSettings SIM卡锁定设置 com.android.settings.InstalledAppDetails 语言和键盘设置 com.android.settings.LanguageSettings 语言和键盘设置 com.android.settings.LocalePicker 选择手机语言 com.android.settings.LocalePickerInSetupWizard 选择手机语言 com.android.settings.ManageApplications 已下载(安装)软件列表 com.android.settings.MasterClear 恢复出厂设置 com.android.settings.MediaFormat 格式化手机闪存 com.android.settings.PhysicalKeyboardSettings 设置键盘 com.android.settings.PrivacySettings 隐私设置 com.android.settings.ProxySelector 代理设置 com.android.settings.RadioInfo 手机信息 com.android.settings.RunningServices 正在运行的程序(服务) com.android.settings.SecuritySettings 位置和安全设置 com.android.settings.Settings 系统设置 com.android.settings.SettingsSafetyLegalActivity 安全信息 com.android.settings.SoundSettings 声音设置 com.android.settings.TestingSettings 测试——显示手机信息、电池信息、使用情况统计、Wifi information、服务信息 com.android.settings.TetherSettings 绑定与便携式热点 com.android.settings.TextToSpeechSettings 文字转语音设置 com.android.settings.UsageStats 使用情况统计 com.android.settings.UserDictionarySettings 用户词典 com.android.settings.VoiceInputOutputSettings 语音输入与输出设置 com.android.settings.WirelessSettings 无线和网络设置
private void addContactToGroup(int contactId,int groupId) { //judge whether the contact has been in the group boolean b1 = ifExistContactInGroup(contactId, groupId); if (b1) { //the contact has been in the group return; } else { ContentValues values = new ContentValues(); values.put(ContactsContract.CommonDataKinds.GroupMembership.RAW_CONTACT_ID,contactId); values.put(ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID,groupId); values.put(ContactsContract.CommonDataKinds.GroupMembership.MIMETYPE,ContactsContract.CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE); getContentResolver().insert(ContactsContract.Data.CONTENT_URI, values); } } private boolean ifExistContactInGroup(int contactId, int groupId) { String where = Data.MIMETYPE + " = '" + ContactsContract.CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE + "' AND " + Data.DATA1 + " = '" + groupId + "' AND " + Data.RAW_CONTACT_ID + " = '" + contactId + "'"; Cursor markCursor = getContentResolver().query(Data.CONTENT_URI, new String[]{Data.DISPLAY_NAME}, where, null, null); if (markCursor.moveToFirst()) { return true; }else { return false; } }
final ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>(); ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI); builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0); builder.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE); builder.withValue(StructuredName.DISPLAY_NAME, name); operationList.add(builder.build()); builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0); builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); builder.withValue(Phone.TYPE, phoneType); builder.withValue(Phone.NUMBER, phoneNumber); builder.withValue(Data.IS_PRIMARY, 1); operationList.add(builder.build()); if (emailAddresses != null) { for (String emailAddress : emailAddressArray) { builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); builder.withValueBackReference(Email.RAW_CONTACT_ID, 0); builder.withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE); builder.withValue(Email.TYPE, Email.TYPE_MOBILE); builder.withValue(Email.DATA, emailAddress); operationList.add(builder.build()); } } resolver.applyBatch(ContactsContract.AUTHORITY, operationList);
这篇文章只是总结下getView里面优化视图的几种写法,就像孔乙己写茴香豆的茴字的几种写法一样,高手勿喷,勿笑,只是拿出来分享,有错误的地方欢迎大家指正,谢谢。 listview A view that shows items in a vertically scrolling list 。一个显示一个垂直的滚动子项的列表视图 在android开发中,使用listview的地方很多,用它来展现数据,成一个垂直的视图。使用listview是一个标准的适配器模式,用数据--,界面--xml以及适配器--adapter,数据被适配器按照需要的方式展现出来,xml描写了数据如何展现,activity中控制这些活动。 其中使用自定义的adapter,会要重写getView方法,在getView方法产生给用户item的视图以及数据。 见图: 这里有一个优化的地方,就是重用view,这样减少内存消耗,同时加快item加载速度。 在getView中优化的地方,大家想必都非常情况,下面我总结了三种优化的写法,请大家指正。 第一: 重用了convertView,很大程度上的减少了内存的消耗。通过判断convertView是否为null,是的话就需要产生一个视图出来,然后给这个视图数据,最后将这个视图返回给底层,呈献给用户。 特点:如果当前的convertView为null,则通过LayoutInflat产生一个view。 View Code public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = LayoutInflater.from(context).inflate(R.layout.section_list_item1, null); } TextView tv_name = (TextView)convertView.findViewById(R.id.contact_contactinfoitem_tv_name); TextView tv_phone = (TextView)convertView.findViewById(R.id.contact_contactinfoitem_tv_phoneNum); ContactInfo1 confo = contacts.get(position); if (confo != null) {//to set every item's text tv_name.setText(confo.getContactName()); tv_phone.setText(confo.getContact_Phone()); } return convertView; } 第二: 上面的写法会有一个缺点,就是每次在getVIew的时候,都需要重新的findViewById,重新找到控件,然后进行控件的赋值以及事件相应设置。这样其实在做重复的事情,因为的geiview中,其实包含有这些控件,而且这些控件的id还都是一样的,也就是其实只要在view中findViewById一次,后面无需要每次都要findViewById了。 下面给出第二种写法 写发的特点,通常有一个内部类class ViewHolder,这个ViewHolder,用来标识view中一些控件,方便进行一些事件相应操作的设置,比如onClick等等,这样可以不用每次都要findViewById了,减少了性能的消耗。同时重用了convertView,很大程度上的减少了内存的消耗。 View Code public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder ; if (convertView == null) { convertView = LayoutInflater.from(context).inflate(R.layout.section_list_item1, null); holder = new ViewHolder(); holder.tv_name = (TextView)convertView.findViewById(R.id.contact_contactinfoitem_tv_name); holder.tv_phone = (TextView)convertView.findViewById(R.id.contact_contactinfoitem_tv_phoneNum); convertView.setTag(holder); } else { holder = (ViewHolder)convertView.getTag(); } ContactInfo1 confo = contacts.get(position); Log.i("my", "confo "+confo.getContactName()); if (confo != null) {//to set every item's text holder.tv_name.setText(confo.getContactName()); holder.tv_phone.setText(confo.getContact_Phone()); } return convertView; } class ViewHolder { TextView tv_name,tv_phone; } 第三: 个人觉得这个写法是最舒服的,最舒服的意思是看着代码有一种很爽,看的很清晰。 特点,使用了内部类class ViewHolder、重用了convertView。 区别第二种写法是,使用了一个临时变量View view = convertView,然后修改view,最后返回view View Code @Override public View getView(int position, View convertView, ViewGroup parent) { View view = convertView; ViewHolder holder ; if (view == null) { view = LayoutInflater.from(context).inflate(R.layout.section_list_item1, null); holder = new ViewHolder(); holder.tv_name = (TextView)view.findViewById(R.id.contact_contactinfoitem_tv_name); holder.tv_phone = (TextView)view.findViewById(R.id.contact_contactinfoitem_tv_phoneNum); view.setTag(holder); } else { holder = (ViewHolder)view.getTag(); } ContactInfo1 confo = contacts.get(position); Log.i("my", "confo "+confo.getContactName()); if (confo != null) {//to set every item's text holder.tv_name.setText(confo.getContactName()); holder.tv_phone.setText(confo.getContact_Phone()); } return view; } class ViewHolder { TextView tv_name,tv_phone; } 以上就是集中写法,供新手学习和总结。 源代码如下:LisViewTest.zip 根据楼下朋友提供的建议,发现还有优化的地方,最新更新如下: View Code @Override public View getView(int position, View convertView, ViewGroup parent) { View view = convertView; ViewHolder holder ; if (view == null) { view = LayoutInflater.from(context).inflate(R.layout.section_list_item1, null); holder = new ViewHolder(); holder.tv_name = (TextView)view.findViewById(R.id.contact_contactinfoitem_tv_name); holder.tv_phone = (TextView)view.findViewById(R.id.contact_contactinfoitem_tv_phoneNum); view.setTag(holder); } else { holder = (ViewHolder)view.getTag(); } ContactInfo1 confo = contacts.get(position); Log.i("my", "confo "+confo.getContactName()); if (confo != null) {//to set every item's text holder.tv_name.setText(confo.getContactName()); holder.tv_phone.setText(confo.getContact_Phone()); } return view; } <font color="\"#0000ff\""> </font>static class ViewHolder { TextView tv_name,tv_phone; } 注意:static class ViewHolder 这里设置ViewHolder 为static,也就是静态的,静态类只会在第一次加载时 会耗费比较长时间,但是后面就可以很好帮助加载,同时保证了内存中只有一个ViewHolder,节省了内存的开销。 非常感谢大家提出建议以及大家的关注!
写这边文章之前,犹豫再三,不知道会不会冒犯一位朋友,他给我之前的一篇文章提出的意见。但我声明真心无意,只是想把问题拿出来分析一下,希望获得理解。 listview在android开发中很地方都用到了,通常我们需要定制item里面的视图,就要重写adapter。而item中的控件根据需要来添加。但是如果出现了某些特定的item控件,就可能导致listview 的onItemClickListener不起作用。 下面是一个范例,说明这种情况。 首先还是先看代码: 布局xml文件如下: View Code item布局xml如下: View Code <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <ImageView android:id="@+id/iv_photo" android:layout_width="45dp" android:layout_height="45dp" /> <TableLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_weight="1.0" android:gravity="center_vertical" > <TableRow> <TextView android:id="@+id/tv_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1.0" android:ellipsize="marquee" android:marqueeRepeatLimit="marquee_forever" android:singleLine="true" android:text="xxx" /> </TableRow> <TableRow> <TextView android:id="@+id/tv_phoneNum" android:layout_width="match_parent" android:layout_height="wrap_content" android:ellipsize="marquee" android:marqueeRepeatLimit="marquee_forever" android:singleLine="true" android:text="xxx" /> </TableRow> </TableLayout> <LinearLayout android:layout_width="43dp" android:layout_height="match_parent" android:gravity="left|center_vertical" > <ImageButton android:id="@+id/ib_call" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_launcher" /> </LinearLayout> </LinearLayout> 注意里面有一个ImageButton。 在activity中设置listview的onItemClickListener,需要做的事情就是当点击item的时候出现log信息,代码如下: View Code listView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { // TODO Auto-generated method stub Log.i("mm", " onItemClick "); } }); 以及设置listview的onTouchListener,需要的事情只是当touch的时候MotionEvent的事件,代码如下: View Code listView.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { // TODO Auto-generated method stub if(event.getAction() == MotionEvent.ACTION_DOWN) { Log.i("mm", "MotionEvent.ACTION_DOWN"); } else if(event.getAction() == MotionEvent.ACTION_UP) { Log.i("mm", "MotionEvent.ACTION_UP"); } else if(event.getAction() == MotionEvent.ACTION_MOVE) { Log.i("mm", "MotionEvent.ACTION_MOVE"); } return false; } }); 在adapter中设置ImageButton的onClicklistener,需要做的事情只是打出log信息,代码如下: View Code holder.iv_call.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub Log.i("mm", "holder.iv_call.setOnClickListener "); } }); 好了,以上都做完了,接下来运行工程。出现界面以后,我们来使劲的点item位置(除了imageButton), 结果log中没有出现打印的 Log.i("mm", " onItemClick ");接下来拖动item看看touch事件打出log如下: 11-07 08:42:27.793: I/mm(540): MotionEvent.ACTION_MOVE 11-07 08:42:28.681: I/mm(540): MotionEvent.ACTION_MOVE 11-07 08:42:28.832: I/mm(540): MotionEvent.ACTION_MOVE 11-07 08:42:28.992: I/mm(540): MotionEvent.ACTION_UP 大家可以看到没有action_down事件,也就是没有了点击事件,一个完整的touch是down--move--move--up,而这里没有了,这是为什么呢? 再点击imagButton,看看打印信息: 11-07 08:44:31.131: I/mm(540): holder.iv_call.setOnClickListener 出现了我们期望的打印信息。 在这里总结一下上面问题出现背景,item中有ImageButton,其余和平常使用listview一样的.当点击item时,onItemClickListener不起作用,ontouchListener中motionEvent.down消失了,事件只有点击item中的imagButton起作用。 我们分析一下,当item出现了imageButton时,onItemClickListener不起作用,而在Touch中没有了down事件,首先说明onItemClickListener处理的和MotionEvent的down事件有关,然后问题的关键是这个down事件去了哪里呢? 经过排查当item中有Checkable类以及Button类控件的时候,item的焦点会被子项获得,此时这些子控件会将焦点获取到,所以常常当点击item时变化的是子控件,item本身的点击没有响应。从而导致onItemClickListener不起作用。 已经得知了问题导致的原因就是因为item没有获得焦点,焦点被子项拿走了,那么怎么解决这类问题?本人认为处理的途径无非就是通过设置子项不能获得焦点,同时item要获得焦点。 这里采用的方法,要用到两个属性: 一: android:focusable="false" 这个属性的具体介绍可以i看我上一篇文章,设置的目的在于使得某个控件不能获得焦点。 二: android:descendantFocusability="blocksDescendants" 这个属性用来设置子项焦点的处理先后顺序。 android:descendantFocusability Defines the relationship between the ViewGroup and its descendants when looking for a View to take focus. Must be one of the following constant values. android:beforeFocusability viewgroup在子项处理之前获得焦点 android:afterFocusability viewGroup在子项处理之后获得焦点 android:blocksFocusability viewGroup阻止子项获得焦点 上面就是说子项焦点能力,定义了viewgroup和它的子元素处理的关系,这关系是指当一个view在获得焦点的时候,值必须是下面的常量之一。 那么,我们肯定imageButton不能获得焦点,因此添加ImageButton属性 focusable="false",同时我们希望item中子项不能获得焦点,所以要把给android:descendantFocusability="blocksDescendants"属性添加到imageButton的父元素即可,简单的做可以设置item的根节点上。 以上作完后,我们在测试一下。 点击item,出现以下log: 11-07 09:48:19.671: I/mm(1077): MotionEvent.ACTION_DOWN 11-07 09:48:19.751: I/mm(1077): MotionEvent.ACTION_UP 11-07 09:48:19.952: I/mm(1077): onItemClick touch事件有了,ItemClick也有有了, 再次imageButton,出现以下log: 11-07 09:50:01.673: I/mm(1077): holder.iv_call.setOnClickListener 说明点击ImageBUtton也获得点击事件。 以上完美的解决问题了。 总结:本次出现的onItemClickListener不能响应的原因是在item中有button类(子类)或者checkable类(子类)控件导致了item的焦点在子项的控件上,处理的办法就是将子项的控件焦点去掉,同时在item中xml设置阻止子项获得焦点的属性,即可解决尚需问题 综述: 出现onItemClickListener不能响应,原因可能有多种,本人总结了有两种情况,一种是isEnable中返回值为false导致不能点击和选择,一种是因为item中有了checkable或者button类(子类)控件导致了item的焦点失去,从不能响应。因此需要仔细分析,问题导致的具体原因,才更好的解决问题。 最后:感谢上一篇文章中给我给意见的同学。 欢迎转载,但请标明出处,谢谢
最近写了一个项目,界面使用的是帧布局,里面放置了listview显示联系人,以及右侧有对联系人的字母索引定位。 结果在对联系人listview设置onItemClickListener时,发现竟然不起作用。 下面的是布局文件以及设置代码 <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/contact_fl" > <ListView android:id="@+id/contact_lv" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1.0" android:focusable="true" android:focusableInTouchMode="true" android:dividerHeight="1px" android:scrollbars="none" /> <LinearLayout android:layout_width="28dp" android:layout_height="match_parent" android:layout_gravity="right|center" android:layout_marginBottom="6dp" android:layout_marginTop="10dp" android:gravity="right" android:orientation="vertical" > <ListView android:id="@+id/contact_letter" android:layout_width="28dp" android:layout_height="match_parent" android:layout_gravity="center_horizontal" android:focusable="true" android:focusableInTouchMode="true" android:scrollbars="none" /> </LinearLayout> </FrameLayout> 在activity中设置onItemCllickListener,代码如下: lv_contact.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Log.i("my", "onItemClick clicked"); } }); item的布局如下: <ImageView android:scaleType="centerInside" android:layout_marginLeft="12dp" android:layout_marginTop="5dp" android:layout_marginRight="4dp" android:layout_marginBottom="3dp" android:layout_width="45dp" android:layout_height="45dp" android:src="@drawable/ic_contact_picture" android:id="@+id/contact_contactinfoitem_iv_photo" /> <TextView android:singleLine="true" android:ellipsize="marquee" android:textStyle="bold" android:marqueeRepeatLimit="marquee_forever" android:layout_height="wrap_content" android:layout_width="match_parent" android:layout_weight="1.0" android:id="@+id/contact_contactinfoitem_tv_name" android:text="xxx" /> <TextView android:singleLine="true" android:ellipsize="marquee" android:textStyle="bold" android:marqueeRepeatLimit="marquee_forever" android:layout_height="wrap_content" android:layout_width="match_parent" android:layout_weight="1.0" android:id="@+id/contact_contactinfoitem_tv_phone" android:text="xxx" /> 当点击联系人listiew的时候,没有打出对应的log,非常奇怪,开始的时候还以为是设置了onTouchListener的原因,因为onTouchListener返回结果会影响是否需要继续处理屏幕事件。 检查发现onTouchListener里面,返回结果是false,不是true,这意味着屏幕事件是继续传递处理的,不会影响到onItemClickListener的处理。 去网上查了下发现有说 xml中有个焦点属性会影响onTouchListener,需要将其改为false 再次检测xml文件,里面确实设置有这两个属性 android:focusable="true" android:focusableInTouchMode="true" 这两个属性的看名字就知道到意思,android:focusable="true"------- 设置是否获得焦点。若有requestFocus()被调用时,后者优先处理。注意在表单中想设置某一个如EditText获取焦点,光设置这个是不行的,需要将这个EditText前面的focusable都设置为false才行。在Touch模式下获取焦点需要设置focusableInTouchMode为true android:focusableInTouchMode="true"---- 设置在Touch模式下View是否能取得焦点。 在xml中修改,这属性为false,运行工程,发现还是一样的onItemCllickListener不起作用,这就纠结了。 因为赶时间,干脆在adapter中,写item的onclicklistener算了。代码如下: contact_fl.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { Log.i("my", "onClick clicked"); } }); 运行项目,item的点击效果有了。但是同时有新的问题出现了。在我的ontouchListener种,onkeydown事件消失了。。。。 只有onkeymove和onkeyup。 经过分析得出结论,那就是肯定有方法或者属性,设置的时候冲突了,item的布局文件中没有button之类的空间。重点查看是否有方法或者属性使得click事件消失了 再次到adapter中仔细检查,发现有个两个这样的方法: @Override public boolean areAllItemsEnabled() { // all items are separator return false; } @Override public boolean isEnabled(int position) { // all items are separator return false; } 查看说明: Returns true if the item at the specified position is not a separator. (A separator is a non-selectable, non-clickable item). The result is unspecified if position is invalid. An ArrayIndexOutOfBoundsException should be thrown in that case for fast failure. 意思就是说,如果当前指定的位置不是一个separator--分隔符(分隔符是一个不能选中,不能点击的item),那么返回true。 那么赶紧改为true,运行项目,效果都有了。 成功解决。
写好Notification , 按Home回到主界面,再按通知栏的消息(Notification), 回到退出之前正在运行的Acticity . 在代码中加入两行代码作为声名即可。 : Intent notificationIntent = new Intent(this,this.getClass()); /* */ notificationIntent.setAction(Intent.ACTION_MAIN); notificationIntent.addCategory(Intent.CATEGORY_LAUNCHER); PendingIntent contentIntent = PendingIntent.getActivity(this.context, 0, notificationIntent,PendingIntent.FLAG_UPDATE_CURRENT); notification.setLatestEventInfo(context, title, text, contentIntent); mNotificationManager.notify(NOTIFICATION_SERVICE_ID,notification);
相比较onMeasure ,layout过程要简单多了,正如layout的中文意思“布局”中表达的一样,layout的过程就是确定View在屏幕上显示的具体位置,在代码中就是设置其成员变量mLeft,mTop,mRight,mBottom的值,这几个值构成的矩形区域就是该View显示的位置,不过这里的具体位置都是相对与父视图的位置。 与onMeasure过程类似,ViewGroup在onLayout函数中通过调用其children的layout函数来设置子视图相对与父视图中的位置,具体位置由函数layout的参数决定,当我们继承ViewGroup时必须重载onLayout函数(ViewGroup中onLayout是abstract修饰),然而onMeasure并不要求必须重载,因为相对与layout来说,measure过程并不是必须的,具体后面会提到。首先我们来看下View.java中函数layout和onLayout的源码: public void layout(int l, int t, int r, int b) { int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; boolean changed = setFrame(l, t, r, b); if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) { if (ViewDebug.TRACE_HIERARCHY) { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT); } onLayout(changed, l, t, r, b); mPrivateFlags &= ~LAYOUT_REQUIRED; ListenerInfo li = mListenerInfo; if (li != null && li.mOnLayoutChangeListeners != null) { ArrayList<OnLayoutChangeListener> listenersCopy = (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone(); int numListeners = listenersCopy.size(); for (int i = 0; i < numListeners; ++i) { listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB); } } } mPrivateFlags &= ~FORCE_LAYOUT; } 函数layout的主体过程还是很容易理解的,首先通过调用setFrame函数来对4个成员变量(mLeft,mTop,mRight,mBottom)赋值,然后回调onLayout函数,最后回调所有注册过的listener的onLayoutChange函数。 对于View来说,onLayout只是一个空实现,一般情况下我们也不需要重载该函数: protected void onLayout(boolean changed, int left, int top, int right, int bottom) { } 接着我们来看下ViewGroup.java中layout的源码: public final void layout(int l, int t, int r, int b) { if (mTransition == null || !mTransition.isChangingLayout()) { super.layout(l, t, r, b); } else { // record the fact that we noop'd it; request layout when transition finishes mLayoutSuppressed = true; } } super.layout(l, t, r, b)调用的即是View.java中的layout函数,相比之下ViewGroup增加了LayoutTransition的处理,LayoutTransition是用于处理ViewGroup增加和删除子视图的动画效果,也就是说如果当前ViewGroup未添加LayoutTransition动画,或者LayoutTransition动画此刻并未运行,那么调用super.layout(l, t, r, b),继而调用到ViewGroup中的onLayout,否则将mLayoutSuppressed设置为true,等待动画完成时再调用requestLayout()。 上面super.layout(l, t, r, b)会调用到ViewGroup.java中onLayout,其源码实现如下: @Override protected abstract void onLayout(boolean changed, int l, int t, int r, int b); 和前面View.java中的onLayout实现相比,唯一的差别就是ViewGroup中多了关键字abstract的修饰,也就是说ViewGroup类只能用来被继承,无法实例化,并且其子类必须重载onLayout函数,而重载onLayout的目的就是安排其children在父视图的具体位置。重载onLayout通常做法就是起一个for循环调用每一个子视图的layout(l, t, r, b)函数,传入不同的参数l, t, r, b来确定每个子视图在父视图中的显示位置。 那layout(l, t, r, b)中的4个参数l, t, r, b如何来确定呢?联想到之前的measure过程,measure过程的最终结果就是确定了每个视图的mMeasuredWidth和mMeasuredHeight,这两个参数可以简单理解为视图期望在屏幕上显示的宽和高,而这两个参数为layout过程提供了一个很重要的依据(但不是必须的),为了说明这个过程,我们来看下LinearLayout的layout过程: void layoutVertical() { …… for (int i = 0; i < count; i++) { final View child = getVirtualChildAt(i); if (child == null) { childTop += measureNullChild(i); } else if (child.getVisibility() != GONE) { final int childWidth = child.getMeasuredWidth(); final int childHeight = child.getMeasuredHeight(); …… setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight); childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child); i += getChildrenSkipCount(child, i); } } } private void setChildFrame(View child, int left, int top, int width, int height) { child.layout(left, top, left + width, top + height); } 从setChildFrame可以看到LinearLayout中的子视图的右边界等于left + width,下边界等于top+height,也就是说在LinearLayout中其子视图显示的宽和高由measure过程来决定的,因此measure过程的意义就是为layout过程提供视图显示范围的参考值。 layout过程必须要依靠measure计算出来的mMeasuredWidth和mMeasuredHeight来决定视图的显示大小吗?事实并非如此,layout过程中的4个参数l, t, r, b完全可以由视图设计者任意指定,而最终视图的布局位置和大小完全由这4个参数决定,measure过程得到的mMeasuredWidth和mMeasuredHeight提供了视图大小的值,但我们完全可以不使用这两个值,可见measure过程并不是必须的。\\ 说到这里就不得不提getWidth()、getHeight()和getMeasuredWidth()、getMeasuredHeight()这两对函数之间的区别,getMeasuredWidth()、getMeasuredHeight()返回的是measure过程得到的mMeasuredWidth和mMeasuredHeight的值,而getWidth()和getHeight()返回的是mRight - mLeft和mBottom - mTop的值,看View.java中的源码便一清二楚了: public final int getMeasuredWidth() { return mMeasuredWidth & MEASURED_SIZE_MASK; } public final int getWidth() { return mRight - mLeft; } 这也解释了为什么有些情况下getWidth()和getMeasuredWidth()以及getHeight()和getMeasuredHeight()会得到不同的值。 总结:整个layout过程比较容易理解,一般情况下layout过程会参考measure过程中计算得到的mMeasuredWidth和mMeasuredHeight来安排子视图在父视图中显示的位置,但这不是必须的,measure过程得到的结果可能完全没有实际用处,特别是对于一些自定义的ViewGroup,其子视图的个数、位置和大小都是固定的,这时候我们可以忽略整个measure过程,只在layout函数中传入的4个参数来安排每个子视图的具体位置。