首页 文章 精选 留言 我的

精选列表

搜索[安卓系统],共10000篇文章
优秀的个人博客,低调大师

Sqlbean 安卓版 1.1.1 发布,Sqlite 自动生成 Sql 语句工具

Sqlbean For Android 介绍 Sqlbean是一款使用Java面向对象思想来编写并生成Sql语句的工具,在此基础上对Android SQLite实现轻量级插件支持。其中内置大量常用SQL执行的方法,可以非常方便的达到你想要的目的,相对复杂的SQL语句也得以支持,在常规的项目开发几乎做到不写SQL,可以有效的提高项目开发的效率,让开发者更专注于业务代码的编写。 🚀特点: 零入侵, 自动建表, 连表查询, 乐观锁,分页 💻环境: Android 4.0+ Sqlbean-Core与Java-Spring版请移步这里👉gitee,github 简单上手 1.引入Gradle依赖 implementation 'cn.vonce:vonce-sqlbean-android:1.1.1' annotationProcessor 'cn.vonce:vonce-sqlbean-android:1.1.1' 本次更新内容 1:sql执行时增加日志输出; 2:修复映射时父类字段的问题; 3:修复连表时父类字段生成问题; 4:修复实体类父类字段没有正确将驼峰转下划线的问题;

优秀的个人博客,低调大师

安卓开发_自定义控件_界面的简单侧滑

主界面 package com.itheima.news; import android.os.Bundle; import android.app.Activity; import android.view.Menu; import android.view.Window; public class NewsHomeActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //去掉头信息 requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_news_home); } } <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <com.itheima.news.MenuListGrouView android:layout_width="match_parent" android:layout_height="match_parent" > <include layout="@layout/item_menu_list" /> <include layout="@layout/main_showpage" /> </com.itheima.news.MenuListGrouView> </RelativeLayout> 显示布局文件1 <?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="200dp" android:layout_height="match_parent" > <LinearLayout android:layout_width="200dp" android:layout_height="match_parent" android:background="@drawable/menu_bg" android:orientation="vertical" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:drawableLeft="@drawable/tab_dingyue" android:gravity="center" android:text="text1" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:drawableLeft="@drawable/tab_juhe" android:gravity="center" android:text="text1" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:drawableLeft="@drawable/tab_local" android:gravity="center" android:text="text1" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:drawableLeft="@drawable/tab_news" android:gravity="center" android:text="text1" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="5dp" android:drawableLeft="@drawable/tab_ties" android:gravity="center" android:text="text1" /> </LinearLayout> </ScrollView> 显示布局文件2 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/top_bar_bg" android:gravity="center" android:padding="10dp" android:text="我的小新闻 " android:textSize="20sp" /> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:text="点击浏览内容 " /> </LinearLayout> 自定义控件 的页面 public class MenuListGrouView extends ViewGroup { private int downX; private int interDownX; private int interDownY; public MenuListGrouView(Context context) { this(context,null); } public MenuListGrouView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public MenuListGrouView(Context context, AttributeSet attrs) { this(context, attrs,-1); } //对子控件进行测量 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //获取子控件 View liftMenuView = getChildAt(0); View rightMainView = getChildAt(1); //对子控件进行测量 rightMainView.measure(widthMeasureSpec, heightMeasureSpec); liftMenuView.measure(liftMenuView.getLayoutParams().width, heightMeasureSpec); } //进行排版设置 @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { //找到相关控件 View liftMenuView = getChildAt(0); View rightMainView = getChildAt(1); //对控件进行排版 rightMainView.layout(l, t, r, b); liftMenuView.layout(-liftMenuView.getMeasuredWidth(), 0,0, b); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //获取按下时的坐标 downX = (int) event.getX(); break; case MotionEvent.ACTION_MOVE: int moveX = (int) event.getX(); //计算偏移量 int shiftingX =downX -moveX ; //判断边界 int scrollX = getScrollX()+shiftingX; if (scrollX>0) { scrollTo(0, 0);//移动到指定的位置 } else if (scrollX<-getChildAt(0).getWidth()) { scrollTo(-getChildAt(0).getWidth(), 0); } else { //在上一次的移动的基础上进行移动 scrollBy(shiftingX,0); } downX = moveX ; break; case MotionEvent.ACTION_UP: //进行判断 int scrollUpX = (int) getScrollX(); if (scrollUpX>-getChildAt(0).getWidth()/2) { //如果向右滑动的距离小于右面控的一半的时候,那么就不显示右面的控件 scrollTo(0, 0); } else { //如果向右滑动的距离大于右面控件的一半的时候,那么就显示右面的控件 scrollTo(-getChildAt(0).getWidth(), 0); } break; default: break; } return true; } //消息传递机制来进行点击事件的滑动控制 @Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: interDownX = (int) ev.getX(); interDownY = (int) ev.getY(); break; case MotionEvent.ACTION_MOVE: int interMoveX = (int) ev.getX(); int interMoveY = (int) ev.getY(); //计算偏移量 int interShiftingX = Math.abs(interMoveX - interDownX); int interShiftingY = Math.abs( interMoveY - interDownY); if (interShiftingX>interShiftingY) { //说明是侧滑 return true; } case MotionEvent.ACTION_UP: break; default: break; } return super.onInterceptTouchEvent(ev); } } 效果图

优秀的个人博客,低调大师

谷歌要求2022年所有安卓应用添加隐私标签

虽然致力于“杀死第三方Cookie”的Floc(同类群组联合学习)遭遇业界广泛抵制,但是谷歌公司并未停下“隐私保护”的脚步。谷歌近日宣布,将要求应用程序开发人员在其Google Play应用商店产品页面中披露其收集的数据以及相关的安全措施。 谷歌周四公布了详细计划,将在Google Play中创建一个“安全区域”,该计划称“将帮助人们了解应用收集或共享的数据(如果该数据是安全的),以及影响隐私和安全性的其他详细信息。” 确切的政策将在2021年第3季度公布。目前,谷歌表示除其他事项外,我们将要求开发人员分享: 收集和存储哪种类型的数据:可能的示例包括大概或精确的位置、联系人、个人信息(例如姓名、电子邮件地址)、照片、视频、音频文件和存储文件; 数据的使用方式:潜在功能的示例包括应用功能和个性化。 在Play商店中,应用程序的开发人员还将被要求披露应用程序是否: 具有安全惯例,例如数据加密; 遵循Google的家庭政策; 需要此数据才能运行,或者用户可以选择是否共享该数据; 应用安全性已由独立的第三方验证; 使用户可以决定删除数据(如果他们决定卸载应用)。 开发人员将负责编写自己的披露。 Google Play将引入一项政策,要求开发人员提供准确的信息,谷歌的宣传员说:“如果我们发现开发人员歪曲了他们提供的数据并且违反了政策,我们将要求开发人员对其进行修复。不合规的应用将受到政策强制执行。” 但是,Google尚未说明它将如何执行该政策、如何评估披露的准确性、是否对托管的数百万个应用程序进行评估、是否会复查应用程序的披露频率或更正所需的时间范围。 谷歌的公告说:“开发人员同意用户应对个人数据具有知情权和控制权。开发人员还希望提供更多背景信息,以解释数据使用以及安全实践如何影响应用程序体验。” Google的时间表要求开发人员在2021年第4季度开始添加所需的信息,然后在2022年第1季度开始向用户显示。在2022年第2季度(具体日期待定),所有应用都必须记录其信息。即使是Google自己的应用程序也必须遵守。 对于同时为iOS和Android开发应用程序的开发人员来说,谷歌的要求并不是什么新鲜事,因为早在2020年12月,苹果就已经开始要求开发者添加类似的隐私保护标签。 参考资料:https://india.googleblog.com/2021/05/new-safety-section-in-google-play-will.html 【本文是51CTO专栏作者“安全牛”的原创文章,转载请通过安全牛(微信公众号id:gooann-sectv)获取授权】 戳这里,看该作者更多好文

优秀的个人博客,低调大师

安卓日常开发记录-键盘的相关处理方式

点击EditText之外隐藏键盘的实现方式 重写事件分发dispatchTouchEvent,注意不要在onTouchEvent中操作,因为onTouchEvent并非任何情况下都会被调用。通过计算EditText在布局中的位置,进行键盘的显示和隐藏处理 /** * 点击区域在输入框之外都隐藏掉键盘 * @param ev * @return */ @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { View v = getCurrentFocus(); if (isShouldHideInput(v, ev)) { hideSoftKeyboard(v); } return super.dispatchTouchEvent(ev); } // 必不可少,否则所有的组件都不会有TouchEvent了 if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); } public boolean isShouldHideInput(View v, MotionEvent event) { if (v != null && (v instanceof EditText)) { int[] leftTop = { 0, 0 }; //获取输入框当前的location位置 v.getLocationInWindow(leftTop); int left = leftTop[0]; int top = leftTop[1]; int bottom = top + v.getHeight(); int right = left + v.getWidth(); if (event.getX() > left && event.getX() < right && event.getY() > top && event.getY() < bottom) { // 点击的是输入框区域,保留点击EditText的事件 return false; } else { return true; } } return false; } 监听键盘弹起和隐藏的方式 自定义一个view,因为项目中用的LinearLayout,所以以这个布局为例,将其作为根布局,通过布局的高度判断键盘的隐藏和显示,通过接口将结果回调出去 需要在AndroidManifest中配置键盘属性 android:windowSoftInputMode="adjustUnspecified|stateHidden" android:windowSoftInputMode="adjustResize|stateHidden" 经过测试上边两种配置都可以实现,但是下边这种不行,具体原因,你可以去看看 adjustUnspecified adjustResize adjustPan的区别 android:windowSoftInputMode="adjustPan|stateHidden" package com.anjuke.library.uicomponent.view; import android.content.Context; import android.util.AttributeSet; import android.widget.LinearLayout; /** * Author: renzhenming * Time: 2018/8/11 16:58 * Email: renzhenming@58ganji.com * Version:12.3 * Description: 用于监听键盘的隐藏和出现 */ public class AjkAdjustSizeLinearLayout extends LinearLayout { public AjkAdjustSizeLinearLayout(Context context) { super(context); } public AjkAdjustSizeLinearLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public AjkAdjustSizeLinearLayout(Context context, AttributeSet attrs) { super(context, attrs); } private int mChangeSize = 200; /** * 键盘弹起时,布局的高度因为受到挤压,会变小,所以新的高度h减去旧的高度oldh会得到一个负数 * 这个负数的绝对值等于键盘的高度,而且基本可以确定的是键盘的高度一定是大于200的,所以满足 * (oldw != 0 && h - oldh < -mChangeSize)就可以当做键盘弹起 * * 键盘收起时,布局高度恢复到最初,新的高度h减去oldh得到一个正数,这个数值正好就是键盘的高度 * 所以满足(oldw != 0 && h - oldh > mChangeSize)时,可以看做是键盘收起 * @param w * @param h * @param oldw * @param oldh */ @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (oldw == 0 || oldh == 0) return; if (boardListener != null) { if (oldw != 0 && h - oldh < -mChangeSize) { boardListener.keyBoardVisible(Math.abs(h - oldh)); } if (oldw != 0 && h - oldh > mChangeSize) { boardListener.keyBoardInvisible(Math.abs(h - oldh)); } } } public interface SoftKeyBoardListener { void keyBoardVisible(int move); void keyBoardInvisible(int move); } SoftKeyBoardListener boardListener; public void setSoftKeyBoardListener(SoftKeyBoardListener boardListener) { this.boardListener = boardListener; } }

优秀的个人博客,低调大师

安卓应用安全指南 4.1.2 创建/使用活动 规则书

4.1.2 创建/使用活动 规则书 原书:Android Application Secure Design/Secure Coding Guidebook 译者:飞龙 协议:CC BY-NC-SA 4.0 创建或向活动发送意图时,请务必遵循以下规则。 4.1.2.1 仅在应用内部使用的活动必须设置为私有(必需) 仅在单个应用中使用的活动,不需要能够从其他应用接收任何意图。 开发人员经常假设,应该是私有的活动不会受到攻击,但有必要将这些活动显式设置为私有,以阻止恶意内容被收到。 AndroidManifest.xml <!-- Private activity --> <!-- *** POINT 3 *** Explicitly set the exported attribute to false. --> <activity android:name=".PrivateActivity" android:label="@string/app_name" android:exported="false" /> 意图过滤器不应该设置在仅用于单个应用的活动中。 由于意图过滤器的特性,以及工作原理,即使您打算向内部的私有活动发送意图,但如果通过意图过滤器发送,则可能会无意中启动另一个活动。 更多详细信息,请参阅高级主题“4.1.3.1 结合导出属性和意图过滤器设置(用于活动)”。 AndroidManifest.xml(不推荐) <!-- Private activity --> <!-- *** POINT 3 *** Explicitly set the exported attribute to false. --> <activity android:name=".PictureActivity" android:label="@string/picture_name" android:exported="false" > <intent-filter> <action android:name="org.jssec.android.activity.OPEN /> </intent-filter> </activity> 4.1.2.2 不要指定taskAffinity(必需) 在 Android OS 中,活动由任务管理。 任务名称由根活动所具有的 Affinity 决定。 另一方面,对于根活动以外的活动,活动所属的任务不仅仅取决于 Affinity,还取决于活动的启动模式。 更多详细信息,请参阅“4.1.3.4 根活动”。 在默认设置中,每个活动使用其包名称作为其 Affinity。 因此,任务根据应用分配,因此单个应用中的所有活动都属于同一个任务。 要更改任务分配,您可以在AndroidManifest.xml文件中显式声明 Affinity,或者您可以在发送给活动的意图中,设置一个标志。 但是,如果更改任务分配,则存在风险,即其他应用可能读取一些意图,它发送给属于其他任务的活动。 请务必不要在AndroidManifest.xml文件中指定android:taskAffinity,并使用默认设置,将 affinity 作为包名,以防止其他应用读取发送或接收的意图中的敏感信息。 以下是用于创建和使用私有活动的AndroidManifest.xml示例文件。 AndroidManifest.xml <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <!-- Private activity --> <!-- *** POINT 1 *** Do not specify taskAffinity --> <activity android:name=".PrivateActivity" android:label="@string/app_name" android:exported="false" /> </application> 任务和 Affinity 的更多信息,请参阅“Google Android 编程指南” [2],Google 开发者 API 指南“任务和返回栈” [3],“4.1.3.3 读取发送到活动的意图”和“4.1.3.4 根活动” [2] Author Egawa, Fujii, Asano, Fujita, Yamada, Yamaoka, Sano, Takebata, “Google Android Programming Guide”, ASCII Media Works, July 2009 [3] http://developer.android.com/guide/components/tasks-and-back-stack.html 4.1.2.3 不要指定launchMode(必需) 活动的启动模式,用于控制启动活动时的设置,它用于创建新任务和活动实例。 默认情况下,它被设置为"standard"。 在"standard"设置中,新实例总是在启动活动时创建,任务遵循属于调用活动的任务,并且不可能创建新任务。 创建新任务时,其他应用可能会读取调用意图的内容,因此当敏感信息包含在意图中时,需要使用"standard"活动启动模式设置。 活动的启动模式可以在AndroidManifest.xml文件的android:launchMode属性中显式设置,但由于上面解释的原因,这不应该在活动的声明中设置,并且该值应该保留为默认的"standard"。 AndroidManifest.xml <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <!-- Private activity --> <!-- *** POINT 2 *** Do not specify launchMode --> <activity android:name=".PrivateActivity" android:label="@string/app_name" android:exported="false" /> </application> 请参阅“4.1.3.3 读取发送到活动的意图”和“4.1.3.4 根活动”。 4.1.2.4 不要为启动活动的意图设置FLAG_ACTIVITY_NEW_TASK标志(必需) 执行startActivity()或startActivityForResult()时,可以更改Activity的启动模式,并且在某些情况下可能会生成新任务。 因此有必要在执行期间不更改Activity的启动模式。 要更改Activity启动模式,使用setFlags()或addFlags()设置Intent标志,并将该Intent用作startActivity()或startActivityForResult()的参数。 FLAG_ACTIVITY_NEW_TASK是用于创建新任务的标志。 当设置FLAG_ACTIVITY_NEW_TASK时,如果被调用的Activity不存在于后台或前台,则会创建一个新任务。 FLAG_ACTIVITY_MULTIPLE_TASK标志可以与FLAG_ACTIVITY_NEW_TASK同时设置。 在这种情况下,总会创建一个新的任务。 新任务可以通过任一设置创建,因此不应使用处理敏感信息的意图来设置这些东西。 // *** POINT 6 *** Do not set the FLAG_ACTIVITY_NEW_TASK flag for the intent to start an activity. Intent intent = new Intent(this, PrivateActivity.class); intent.putExtra("PARAM", "Sensitive Info"); startActivityForResult(intent, REQUEST_CODE); 另外,即使通过明确设置FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS标志创建了新任务,您也可能认为有一种方法可以防止读取Intent的内容。 但是,即使使用此方法,内容也可以由第三方读取,因此您应该避免使用FLAG_ACTIVITY_NEW_TASK。 请参阅“4.1.3.1 结合导出属性和意图过滤设置(针对活动)”,“4.1.3.3 读取发送到活动的意图”和“4.1.3.4 根活动”。 4.1.2.5 小心和安全地处理收到的意图 风险因Activity的类型而异,但在处理收到的Intent数据时,您应该做的第一件事是输入验证。 由于公共活动可以从不受信任的来源接收意图,它们可能会受到恶意软件的攻击。 另一方面,私有活动永远不会直接从其他应用收到任何意图,但目标应用中的公共活动可能会将恶意Intent转发给私有活动,因此您不应该认为私有活动不会收到任何恶意输入。 由于伙伴活动和内部活动也有恶意意图转发给他们的风险,因此有必要对这些意图进行输入验证。 请参阅“3.2 仔细和安全地处理输入数据” 4.1.2.6 在验证签名权限由内部应用定义之后,使用内部定义的签名权限(必需) 确保在创建活动时,通过定义内部签名权限来保护您的内部活动。 由于在AndroidManifest.xml文件中定义权限或声明权限请求不能提供足够的安全性,请务必参考“5.2.1.2 如何使用内部定义的签名权限,在内部应用之间进行通信”。 4.1.2.7 返回结果时,请注意目标应用产生的可能的信息泄露(必需) 当您使用setResult()返回数据时,目标应用的可靠性将取决于Activity类型。 当公共活动用于返回数据时,目标可能会成为恶意软件,在这种情况下,可能会以恶意方式使用该信息。 对于私有和内部活动,不需要过多担心返回的数据被恶意使用,因为它们被返回到您控制的应用。 伙伴活动中间有些东西。 如上所述,当从活动中返回数据时,您需要注意来自目标应用的信息泄漏。 public void onReturnResultClick(View view) { // *** POINT 6 *** Information that is granted to be disclosed to a partner application can be returned. Intent intent = new Intent(); intent.putExtra("RESULT", "Sensitive Info"); setResult(RESULT_OK, intent); finish(); } 4.1.2.8 如果目标活动是预先确定的,则使用显式意图(必需) 当通过隐式意图使用Activity时,Intent发送到的Activity由 Android OS 确定。 如果意图被错误地发送到恶意软件,则可能发生信息泄漏。 另一方面,当通过显式意图使用Activity时,只有预期的Activity会收到Intent,所以这样更安全。 除非用户需要确定意图应该发送到哪个应用活动,否则应该使用显式意图并提前指定目标。 Intent intent = new Intent(this, PictureActivity.class); intent.putExtra("BARCODE", barcode); startActivity(intent); Intent intent = new Intent(); intent.setClassName( "org.jssec.android.activity.publicactivity", "org.jssec.android.activity.publicactivity.PublicActivity"); startActivity(intent); 但是,即使通过显式意图使用其他应用的公共活动,目标活动也可能是恶意软件。 这是因为,即使通过软件包名称限制目标,恶意应用仍可能伪造与真实应用相同的软件包名称。 为了消除这种风险,有必要考虑使用伙伴或内部活动。 请参阅“4.1.3.1 组合导出属性和意图过滤器设置(对于活动)” 4.1.2.9 小心并安全地处理来自被请求活动的返回数据(必需) 根据您访问的活动类型,风险略有不同,但在处理作为返回值的收到的Intent数据,您始终需要对接收到的数据执行输入验证。 公共活动必须接受来自不受信任来源的返回意图,因此在访问公共活动时,返回的意图实际上可能是由恶意软件发送的。 人们往往错误地认为,私有活动返回的所有内容都是安全的,因为它们来源于同一个应用。 但是,由于从不可信来源收到的意图可能会间接转发,因此您不应盲目信任该意图的内容。 伙伴和内部活动在私有和公共活动中间有一定风险。 一定也要对这些活动输入验证。 更多信息,请参阅“3.2 仔细和安全地处理输入数据”。 4.1.2.10 如果与其他公司的应用链接,请验证目标活动(必需) 与其他公司的应用链接时,确保确定了白名单。 您可以通过在应用内保存公司的证书散列副本,并使用目标应用的证书散列来检查它。 这将防止恶意应用欺骗意图。 具体实现方法请参考示例代码“4.1.1.3 创建/使用伙伴活动”部分。 技术细节请参阅“4.1.3.2 验证请求应用”。 4.2.11 提供二手素材时,素材应受到同等保护(必需) 当受到权限保护的信息或功能素材被另一个应用提供时,您需要确保它具有访问素材所需的相同权限。 在 Android OS 权限安全模型中,只有已获得适当权限的应用才可以直接访问受保护的素材。 但是,存在一个漏洞,因为具有素材权限的应用可以充当代理,并允许非特权应用程序访问它。 基本上这与重新授权相同,因此它被称为“重新授权”问题。 请参阅“5.2.3.4 重新授权问题”。 4.2.12 敏感信息的发送应该尽可能限制(推荐) 您不应将敏感信息发送给不受信任的各方。 即使您正在连接特定的应用程序,仍有可能无意中将Intent发送给其他应用程序,或者恶意第三方可能会窃取您的意图。 请参阅“4.1.3.5 使用活动时的日志输出”。 将敏感信息发送到活动时,您需要考虑信息泄露的风险。 您必须假设,发送到公共活动的Intent中的所有数据都可以由恶意第三方获取。 此外,根据实现,向伙伴或内部活动发送意图时,也存在各种信息泄漏的风险。 即使将数据发送到私有活动,也存在风险,意图中的数据可能通过LogCat泄漏。 意图附加部分中的信息不会输出到LogCat`,因此最好在那里存储敏感信息。 但是,不首先发送敏感数据,是防止信息泄露的唯一完美解决方案,因此您应该尽可能限制发送的敏感信息的数量。 当有必要发送敏感信息时,最好的做法是只发送给受信任的活动,并确保信息不能通过LogCat泄露。 另外,敏感信息不应该发送到根活动。 根活动是创建任务时首先调用的活动。 例如,从启动器启动的活动始终是根活动。 根活动的更多详细信息,请参阅“4.1.3.3 发送到活动的意图”和“4.1.3.4 根活动”。

优秀的个人博客,低调大师

安卓应用安全指南 4.1.1 创建/使用活动 示例代码

4.1.1 创建/使用活动 示例代码 原书:Android Application Secure Design/Secure Coding Guidebook 译者:飞龙 协议:CC BY-NC-SA 4.0 使用活动的风险和对策取决于活动的使用方式。 在本节中,我们根据活动的使用情况,对 4 种活动进行了分类。 你可以通过下面的图表来找出,你应该创建哪种类型的活动。 由于安全编程最佳实践根据活动的使用方式而有所不同,因此我们也将解释活动的实现。 表 4-1 活动类型的定义 类型 定义 私有 不能由其他应用加载,所以是最安全的活动 公共 应该由很多未指定的应用使用的活动 伙伴 只能由可信的伙伴公司开发的应用使用的活动 内部 只能由其他内部应用使用的活动 4.1.1.1 创建/使用私有活动 私有活动是其他应用程序无法启动的活动,因此它是最安全的活动。 当使用仅在应用程序中使用的活动(私有活动)时,只要你对类使用显示意图,那么你不必担心将它意外发送到任何其他应用程序。 但是,第三方应用程序可能会读取用于启动活动的意图。 因此,如果你将敏感信息放入用于启动活动的意图中,有必要采取对策,来确保它不会被恶意第三方读取。 下面展示了如何创建私有活动的示例代码。 要点(创建活动): 1) 不要指定taskAffinity。 2) 不要指定launchMode。 3) 将导出属性明确设置为false。 4) 仔细和安全地处理收到的意图,即使意图从相同的应用发送。 5) 敏感信息可以发送,因为它发送和接收所有同一应用中的信息。 AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.jssec.android.activity.privateactivity" > <application android:allowBackup="false" android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <!-- Private activity --> <!-- *** POINT 1 *** Do not specify taskAffinity --> <!-- *** POINT 2 *** Do not specify launchMode --> <!-- *** POINT 3 *** Explicitly set the exported attribute to false. --> <activity android:name=".PrivateActivity" android:label="@string/app_name" android:exported="false" /> <!-- Public activity launched by launcher --> <activity android:name=".PrivateUserActivity" android:label="@string/app_name" android:exported="true" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> PrivateActivity.java package org.jssec.android.activity.privateactivity; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Toast; public class PrivateActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.private_activity); // *** POINT 4 *** Handle the received Intent carefully and securely, even though the Intent was sent from the same application. // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely." String param = getIntent().getStringExtra("PARAM"); Toast.makeText(this, String.format("Received param: ¥"%s¥"", param), Toast.LENGTH_LONG).show(); } public void onReturnResultClick(View view) { // *** POINT 5 *** Sensitive information can be sent since it is sending and receiving all within the same application. Intent intent = new Intent(); intent.putExtra("RESULT", "Sensitive Info"); setResult(RESULT_OK, intent); finish(); } } 下面展示如何使用私有活动的示例代码。 要点(使用活动); 6) 不要为意图设置FLAG_ACTIVITY_NEW_TASK标志来启动活动。 7) 使用显式意图,以及用于调用相同应用中的活动的特定的类。 8) 由于目标活动位于同一个应用中,因此只能通过putExtra()发送敏感信息 [1]。 警告:如果不遵守第 1, 2 和 6 点,第三方可能会读到意图。 更多详细信息,请参阅第 4.1.2.2 和 4.1.2.3 节。 9) 即使数据来自同一应用中的活动,也要小心并安全地处理收到的结果数据。 PrivateUserActivity.java package org.jssec.android.activity.privateactivity; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Toast; public class PrivateUserActivity extends Activity { private static final int REQUEST_CODE = 1; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.user_activity); } public void onUseActivityClick(View view) { // *** POINT 6 *** Do not set the FLAG_ACTIVITY_NEW_TASK flag for intents to start an activity. // *** POINT 7 *** Use the explicit Intents with the class specified to call an activity in the same application. Intent intent = new Intent(this, PrivateActivity.class); // *** POINT 8 *** Sensitive information can be sent only by putExtra() since the destination activity is in the same application. intent.putExtra("PARAM", "Sensitive Info"); startActivityForResult(intent, REQUEST_CODE); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode != RESULT_OK) return; switch (requestCode) { case REQUEST_CODE: String result = data.getStringExtra("RESULT"); // *** POINT 9 *** Handle the received data carefully and securely, // even though the data comes from an activity within the same application. // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely." Toast.makeText(this, String.format("Received result: ¥"%s¥"", result), Toast.LENGTH_LONG).show(); break; } } } 4.1.1.2 创建/使用公共活动 公共活动是应该由大量未指定的应用程序使用的活动。 有必要注意的是,公共活动可能收到恶意软件发送的意图。 另外,使用公共活动时,有必要注意恶意软件也可以接收或阅读发送给他们的意图。 要点(创建活动): 1) 将导出属性显式设置为true。 2) 小心并安全地处理接收到的意图。 3) 返回结果时,请勿包含敏感信息。 下面展示了创建公共活动的示例代码。 AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.jssec.android.activity.publicactivity" > <application android:allowBackup="false" android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <!-- Public Activity --> <!-- *** POINT 1 *** Explicitly set the exported attribute to true. --> <activity android:name=".PublicActivity" android:label="@string/app_name" android:exported="true"> <!-- Define intent filter to receive an implicit intent for a specified action --> <intent-filter> <action android:name="org.jssec.android.activity.MY_ACTION" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> </application> </manifest> PublicActivity.java package org.jssec.android.activity.publicactivity; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Toast; public class PublicActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // *** POINT 2 *** Handle the received intent carefully and securely. // Since this is a public activity, it is possible that the sending application may be malware. // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely." String param = getIntent().getStringExtra("PARAM"); Toast.makeText(this, String.format("Received param: ¥"%s¥"", param), Toast.LENGTH_LONG).show(); } public void onReturnResultClick(View view) { // *** POINT 3 *** When returning a result, do not include sensitive information. // Since this is a public activity, it is possible that the receiving application may be malware. // If there is no problem if the data gets received by malware, then it can be returned as a result. Intent intent = new Intent(); intent.putExtra("RESULT", "Not Sensitive Info"); setResult(RESULT_OK, intent); finish(); } } 接下来,这里是公共活动用户端的示例代码。 要点(使用活动): 4) 不要发送敏感信息。 5) 收到结果时,请仔细并安全地处理数据。 PublicUserActivity.java package org.jssec.android.activity.publicuser; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Toast; public class PublicUserActivity extends Activity { private static final int REQUEST_CODE = 1; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } public void onUseActivityClick(View view) { try { // *** POINT 4 *** Do not send sensitive information. Intent intent = new Intent("org.jssec.android.activity.MY_ACTION"); intent.putExtra("PARAM", "Not Sensitive Info"); startActivityForResult(intent, REQUEST_CODE); } catch (ActivityNotFoundException e) { Toast.makeText(this, "Target activity not found.", Toast.LENGTH_LONG).show(); } } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); // *** POINT 5 *** When receiving a result, handle the data carefully and securely. // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely." if (resultCode != RESULT_OK) return; switch (requestCode) { case REQUEST_CODE: String result = data.getStringExtra("RESULT"); Toast.makeText(this, String.format("Received result: ¥"%s¥"", result), Toast.LENGTH_LONG).show(); break; } } } ```#### 4.1.1.3 创建/使用伙伴活动 伙伴活动是只能由特定应用程序使用的活动。 它们在想要安全共享信息和功能的伙伴公司之间使用。 第三方应用程序可能会读取用于启动活动的意图。 因此,如果你将敏感信息放入用于启动活动的意图中,有必要采取对策来确保其无法被恶意第三方读取。 创建伙伴活动的示例代码如下所示。 要点(创建活动): 1) 不要指定`taskAffinity`。 2) 不要指定`launchMode`。 3) 不要定义意图过滤器,并将导出属性明确设置为`true`。 4) 通过预定义白名单验证请求应用程序的证书。 5) 尽管意图是从伙伴应用程序发送的,仔细和安全地处理接收到的意图。 6) 只返回公开给伙伴应用的信息。 请参阅“4.1.3.2 验证和请求应用”,了解如何通过白名单验证应用。 此外,请参阅“5.2.1.3 如何验证应用证书的哈希”,了解如何验证白名单中指定目标应用的证书哈希。 AndroidManifest.xml <div class="se-preview-section-delimiter"></div> ```xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" android:padding="5dp" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:text="@string/description" /> <Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:onClick="onReturnResultClick" android:text="@string/return_result" /> </LinearLayout> PartnerActivity.java package org.jssec.android.activity.partneractivity; import org.jssec.android.shared.PkgCertWhitelists; import org.jssec.android.shared.Utils; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Toast; public class PartnerActivity extends Activity { // *** POINT 4 *** Verify the requesting application's certificate through a predefined whitelist. private static PkgCertWhitelists sWhitelists = null; private static void buildWhitelists(Context context) { boolean isdebug = Utils.isDebuggable(context); sWhitelists = new PkgCertWhitelists(); // Register certificate hash value of partner application org.jssec.android.activity.partneruser .sWhitelists.add("org.jssec.android.activity.partneruser", isdebug ? // Certificate hash value of "androiddebugkey" in the debug.keystore. "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255" : // Certificate hash value of "partner key" in the keystore. "1F039BB5 7861C27A 3916C778 8E78CE00 690B3974 3EB8259F E2627B8D 4C0EC35A"); // Register the other partner applications in the same way. } private static boolean checkPartner(Context context, String pkgname) { if (sWhitelists == null) buildWhitelists(context); return sWhitelists.test(context, pkgname); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // *** POINT 4 *** Verify the requesting application's certificate through a predefined whitelist. if (!checkPartner(this, getCallingActivity().getPackageName())) { Toast.makeText(this, "Requesting application is not a partner application.", Toast.LENGTH_LONG).show(); finish(); return; } // *** POINT 5 *** Handle the received intent carefully and securely, even though the intent was sent from a partner application. // Omitted, since this is a sample. Refer to "3.2 Handling Input Data Carefully and Securely." Toast.makeText(this, "Accessed by Partner App", Toast.LENGTH_LONG).show(); } public void onReturnResultClick(View view) { // *** POINT 6 *** Only return Information that is granted to be disclosed to a partner application. Intent intent = new Intent(); intent.putExtra("RESULT", "Information for partner applications"); setResult(RESULT_OK, intent); finish(); } } PkgCertWhitelists.java package org.jssec.android.shared; import java.util.HashMap; import java.util.Map; import android.content.Context; public class PkgCertWhitelists { private Map<String, String> mWhitelists = new HashMap<String, String>(); public boolean add(String pkgname, String sha256) { if (pkgname == null) return false; if (sha256 == null) return false; sha256 = sha256.replaceAll(" ", ""); if (sha256.length() != 64) return false; // SHA-256 -> 32 bytes -> 64 chars sha256 = sha256.toUpperCase(); if (sha256.replaceAll("[0-9A-F]+", "").length() != 0) return false; // found non hex char mWhitelists.put(pkgname, sha256); return true; } public boolean test(Context ctx, String pkgname) { // Get the correct hash value which corresponds to pkgname. String correctHash = mWhitelists.get(pkgname); // Compare the actual hash value of pkgname with the correct hash value. return PkgCert.test(ctx, pkgname, correctHash); } } PkgCert.java package org.jssec.android.shared; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.Signature; public class PkgCert { public static boolean test(Context ctx, String pkgname, String correctHash) { if (correctHash == null) return false; correctHash = correctHash.replaceAll(" ", ""); return correctHash.equals(hash(ctx, pkgname)); } public static String hash(Context ctx, String pkgname) { if (pkgname == null) return null; try { PackageManager pm = ctx.getPackageManager(); PackageInfo pkginfo = pm.getPackageInfo(pkgname, PackageManager.GET_SIGNATURES); if (pkginfo.signatures.length != 1) return null; // Will not handle multiple signatures. Signature sig = pkginfo.signatures[0]; byte[] cert = sig.toByteArray(); byte[] sha256 = computeSha256(cert); return byte2hex(sha256); } catch (NameNotFoundException e) { return null; } } private static byte[] computeSha256(byte[] data) { try { return MessageDigest.getInstance("SHA-256").digest(data); } catch (NoSuchAlgorithmException e) { return null; } } private static String byte2hex(byte[] data) { if (data == null) return null; final StringBuilder hexadecimal = new StringBuilder(); for (final byte b : data) { hexadecimal.append(String.format("%02X", b)); } return hexadecimal.toString(); } } 使用伙伴活动的示例代码如下: 7) 验证目标应用的证书是否已在白名单中注册。 8) 不要为启动活动的意图设置FLAG_ACTIVITY_NEW_TASK标志。 9) 仅通过putExtra()发送公开给伙伴活动的信息。 10) 使用显示意图调用伙伴活动。 11) 使用startActivityForResult()来调用伙伴活动。 12) 即使数据来自伙伴应用程序,也要小心并安全地处理收到的结果数据。 请参阅“4.1.3.2 验证请求应用”了解如何通过白名单验证应用程序。 另请参阅“5.2.1.3 如何验证应用证书的哈希”,了解如何验证白名单中指定目标应用的证书哈希。 AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.jssec.android.activity.partneruser" > <application android:allowBackup="false" android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:name="org.jssec.android.activity.partneruser.PartnerUserActivity" android:label="@string/app_name" android:exported="true" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> PartnerUserActivity.java package org.jssec.android.activity.partneruser; import org.jssec.android.shared.PkgCertWhitelists; import org.jssec.android.shared.Utils; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Toast; public class PartnerUserActivity extends Activity { // *** POINT 7 *** Verify if the certificate of a target application has been registered in a whitelist. private static PkgCertWhitelists sWhitelists = null; private static void buildWhitelists(Context context) { boolean isdebug = Utils.isDebuggable(context); sWhitelists = new PkgCertWhitelists(); // Register the certificate hash value of partner application org.jssec.android.activity.partner activity .sWhitelists.add("org.jssec.android.activity.partneractivity", isdebug ? // The certificate hash value of "androiddebugkey" is in debug.keystore. "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255" : // The certificate hash value of "my company key" is in the keystore. "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA"); // Register the other partner applications in the same way. } private static boolean checkPartner(Context context, String pkgname) { if (sWhitelists == null) buildWhitelists(context); return sWhitelists.test(context, pkgname); } private static final int REQUEST_CODE = 1; // Information related the target partner activity private static final String TARGET_PACKAGE = "org.jssec.android.activity.partneractivity"; private static final String TARGET_ACTIVITY = "org.jssec.android.activity.partneractivity.PartnerActivity"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } public void onUseActivityClick(View view) { // *** POINT 7 *** Verify if the certificate of the target application has been registered in the own white list. if (!checkPartner(this, TARGET_PACKAGE)) { Toast.makeText(this, "Target application is not a partner application.", Toast.LENGTH_LONG).show(); return; } try { // *** POINT 8 *** Do not set the FLAG_ACTIVITY_NEW_TASK flag for the intent that start an activity. Intent intent = new Intent(); // *** POINT 9 *** Only send information that is granted to be disclosed to a Partner Activity only by putExtra(). intent.putExtra("PARAM", "Info for Partner Apps"); // *** POINT 10 *** Use explicit intent to call a Partner Activity. intent.setClassName(TARGET_PACKAGE, TARGET_ACTIVITY); // *** POINT 11 *** Use startActivityForResult() to call a Partner Activity. startActivityForResult(intent, REQUEST_CODE); } catch (ActivityNotFoundException e) { Toast.makeText(this, "Target activity not found.", Toast.LENGTH_LONG).show(); } } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode != RESULT_OK) return; switch (requestCode) { case REQUEST_CODE: String result = data.getStringExtra("RESULT"); // *** POINT 12 *** Handle the received data carefully and securely, // even though the data comes from a partner application. // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely." Toast.makeText(this, String.format("Received result: ¥"%s¥"", result), Toast.LENGTH_LONG).show(); break; } } } PkgCertWhitelists.java package org.jssec.android.shared; import java.util.HashMap; import java.util.Map; import android.content.Context; public class PkgCertWhitelists { private Map<String, String> mWhitelists = new HashMap<String, String>(); public boolean add(String pkgname, String sha256) { if (pkgname == null) return false; if (sha256 == null) return false; sha256 = sha256.replaceAll(" ", ""); if (sha256.length() != 64) return false; // SHA-256 -> 32 bytes -> 64 chars sha256 = sha256.toUpperCase(); if (sha256.replaceAll("[0-9A-F]+", "").length() != 0) return false; // found non hex char mWhitelists.put(pkgname, sha256); return true; } public boolean test(Context ctx, String pkgname) { // Get the correct hash value which corresponds to pkgname. String correctHash = mWhitelists.get(pkgname); // Compare the actual hash value of pkgname with the correct hash value. return PkgCert.test(ctx, pkgname, correctHash); } } PkgCert.java package org.jssec.android.shared; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.Signature; public class PkgCert { public static boolean test(Context ctx, String pkgname, String correctHash) { if (correctHash == null) return false; correctHash = correctHash.replaceAll(" ", ""); return correctHash.equals(hash(ctx, pkgname)); } public static String hash(Context ctx, String pkgname) { if (pkgname == null) return null; try { PackageManager pm = ctx.getPackageManager(); PackageInfo pkginfo = pm.getPackageInfo(pkgname, PackageManager.GET_SIGNATURES); if (pkginfo.signatures.length != 1) return null; // Will not handle multiple signatures. Signature sig = pkginfo.signatures[0]; byte[] cert = sig.toByteArray(); byte[] sha256 = computeSha256(cert); return byte2hex(sha256); } catch (NameNotFoundException e) { return null; } } private static byte[] computeSha256(byte[] data) { try { return MessageDigest.getInstance("SHA-256").digest(data); } catch (NoSuchAlgorithmException e) { return null; } } private static String byte2hex(byte[] data) { if (data == null) return null; final StringBuilder hexadecimal = new StringBuilder(); for (final byte b : data) { hexadecimal.append(String.format("%02X", b)); } return hexadecimal.toString(); } } 4.1.1.4 创建/使用内部活动 内部活动是禁止其他内部应用以外的应用使用的活动。 它们用于内部开发的应用,以便安全地共享信息和功能。 第三方应用可能会读取用于启动活动的意图。 因此,如果你将敏感信息放入用于启动活动的意图中,有必要采取对策来确保它不会被恶意第三方读取。 下面展示了创建内部活动的示例代码。 要点(创建活动): 1) 定义内部签名权限。 2) 不要指定taskAffinity。 3) 不要指定launchMode。 4) 需要内部签名权限。 5) 不要定义意图过滤器,并将导出属性显式设为true。 6) 确认内部签名权限是由内部应用的。 7) 尽管意图是从内部应用发送的,仔细和安全地处理接收到的意图。 8) 由于请求的应用是内部的,因此可以返回敏感信息。 9) 导出 APK 时,请使用与目标应用相同的开发人员密钥对 APK 进行签名。 AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.jssec.android.activity.inhouseactivity" > <!-- *** POINT 1 *** Define an in-house signature permission --> <permission android:name="org.jssec.android.activity.inhouseactivity.MY_PERMISSION" android:protectionLevel="signature" /> <application android:allowBackup="false" android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <!-- In-house Activity --> <!-- *** POINT 2 *** Do not specify taskAffinity --> <!-- *** POINT 3 *** Do not specify launchMode --> <!-- *** POINT 4 *** Require the in-house signature permission --> <!-- *** POINT 5 *** Do not define the intent filter and explicitly set the exported attribute to true --> <activity android:name="org.jssec.android.activity.inhouseactivity.InhouseActivity" android:exported="true" android:permission="org.jssec.android.activity.inhouseactivity.MY_PERMISSION" /> </application> </manifest> InhouseActivity.java package org.jssec.android.activity.inhouseactivity; import org.jssec.android.shared.SigPerm; import org.jssec.android.shared.Utils; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Toast; public class InhouseActivity extends Activity { // In-house Signature Permission private static final String MY_PERMISSION = "org.jssec.android.activity.inhouseactivity.MY_PERMISSION"; // In-house certificate hash value private static String sMyCertHash = null; private static String myCertHash(Context context) { if (sMyCertHash == null) { if (Utils.isDebuggable(context)) { // Certificate hash value of "androiddebugkey" in the debug.keystore. sMyCertHash = "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255"; } else { // Certificate hash value of "my company key" in the keystore. sMyCertHash = "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA"; } } return sMyCertHash; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // *** POINT 6 *** Verify that the in-house signature permission is defined by an in-house application. if (!SigPerm.test(this, MY_PERMISSION, myCertHash(this))) { Toast.makeText(this, "The in-house signature permission is not declared by in-house application.", Toast.LENGTH_LONG).show(); finish(); return; } // *** POINT 7 *** Handle the received intent carefully and securely, even though the intent was sent from an in-house application. // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely." String param = getIntent().getStringExtra("PARAM"); Toast.makeText(this, String.format("Received param: ¥"%s¥"", param), Toast.LENGTH_LONG).show(); } public void onReturnResultClick(View view) { // *** POINT 8 *** Sensitive information can be returned since the requesting application is inhouse. Intent intent = new Intent(); intent.putExtra("RESULT", "Sensitive Info"); setResult(RESULT_OK, intent); finish(); } } SigPerm.java package org.jssec.android.shared; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PermissionInfo; public class SigPerm { public static boolean test(Context ctx, String sigPermName, String correctHash) { if (correctHash == null) return false; correctHash = correctHash.replaceAll(" ", ""); return correctHash.equals(hash(ctx, sigPermName)); } public static String hash(Context ctx, String sigPermName) { if (sigPermName == null) return null; try { // Get the package name of the application which declares a permission named sigPermName. PackageManager pm = ctx.getPackageManager(); PermissionInfo pi; pi = pm.getPermissionInfo(sigPermName, PackageManager.GET_META_DATA); String pkgname = pi.packageName; // Fail if the permission named sigPermName is not a Signature Permission if (pi.protectionLevel != PermissionInfo.PROTECTION_SIGNATURE) return null; // Return the certificate hash value of the application which declares a permission named sigPermName. return PkgCert.hash(ctx, pkgname); } catch (NameNotFoundException e) { return null; } } } PkgCert.java package org.jssec.android.shared; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.Signature; public class PkgCert { public static boolean test(Context ctx, String pkgname, String correctHash) { if (correctHash == null) return false; correctHash = correctHash.replaceAll(" ", ""); return correctHash.equals(hash(ctx, pkgname)); } public static String hash(Context ctx, String pkgname) { if (pkgname == null) return null; try { PackageManager pm = ctx.getPackageManager(); PackageInfo pkginfo = pm.getPackageInfo(pkgname, PackageManager.GET_SIGNATURES); if (pkginfo.signatures.length != 1) return null; // Will not handle multiple signatures. Signature sig = pkginfo.signatures[0]; byte[] cert = sig.toByteArray(); byte[] sha256 = computeSha256(cert); return byte2hex(sha256); } catch (NameNotFoundException e) { return null; } } private static byte[] computeSha256(byte[] data) { try { return MessageDigest.getInstance("SHA-256").digest(data); } catch (NoSuchAlgorithmException e) { return null; } } private static String byte2hex(byte[] data) { if (data == null) return null; final StringBuilder hexadecimal = new StringBuilder(); for (final byte b : data) { hexadecimal.append(String.format("%02X", b)); } return hexadecimal.toString(); } } 要点 9:导出 APK 时,请使用与目标应用相同的开发人员密钥对 APK 进行签名。 使用内部活动的代码如下: 要点(使用活动): 10) 声明你要使用内部签名权限。 11) 确认内部签名权限是由内部应用定义的。 12) 验证目标应用是否使用内部证书签名。 13) 由于目标应用是内部的,所以敏感信息只能由putExtra()发送。 14) 使用显式意图调用内部活动。 15) 即使数据来自内部应用,也要小心并安全地处理接收到的数据。 16) 导出 APK 时,请使用与目标应用相同的开发人员密钥对 APK 进行签名。 AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.jssec.android.activity.inhouseuser" > <!-- *** POINT 10 *** Declare to use the in-house signature permission --> <uses-permission android:name="org.jssec.android.activity.inhouseactivity.MY_PERMISSION" /> <application android:allowBackup="false" android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:name="org.jssec.android.activity.inhouseuser.InhouseUserActivity" android:label="@string/app_name" android:exported="true" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> InhouseUserActivity.java package org.jssec.android.activity.inhouseuser; import org.jssec.android.shared.PkgCert; import org.jssec.android.shared.SigPerm; import org.jssec.android.shared.Utils; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Toast; public class InhouseUserActivity extends Activity { // Target Activity information private static final String TARGET_PACKAGE = "org.jssec.android.activity.inhouseactivity"; private static final String TARGET_ACTIVITY = "org.jssec.android.activity.inhouseactivity.InhouseActivity"; // In-house Signature Permission private static final String MY_PERMISSION = "org.jssec.android.activity.inhouseactivity.MY_PERMISSION"; // In-house certificate hash value private static String sMyCertHash = null; private static String myCertHash(Context context) { if (sMyCertHash == null) { if (Utils.isDebuggable(context)) { // Certificate hash value of "androiddebugkey" in the debug.keystore. sMyCertHash = "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255"; } else { // Certificate hash value of "my company key" in the keystore. sMyCertHash = "D397D343 A5CBC10F 4EDDEB7C A10062DE 5690984F 1FB9E88B D7B3A7C2 42E142CA"; } } return sMyCertHash; } private static final int REQUEST_CODE = 1; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } public void onUseActivityClick(View view) { // *** POINT 11 *** Verify that the in-house signature permission is defined by an in-house application. if (!SigPerm.test(this, MY_PERMISSION, myCertHash(this))) { Toast.makeText(this, "The in-house signature permission is not declared by in-house application.", Toast.LENGTH_LONG).show(); return; } // ** POINT 12 *** Verify that the destination application is signed with the in-house certificate. if (!PkgCert.test(this, TARGET_PACKAGE, myCertHash(this))) { Toast.makeText(this, "Target application is not an in-house application.", Toast.LENGTH_LONG).show(); return; } try { Intent intent = new Intent(); // *** POINT 13 *** Sensitive information can be sent only by putExtra() since the destination application is in-house. intent.putExtra("PARAM", "Sensitive Info"); // *** POINT 14 *** Use explicit intents to call an In-house Activity. intent.setClassName(TARGET_PACKAGE, TARGET_ACTIVITY); startActivityForResult(intent, REQUEST_CODE); } catch (ActivityNotFoundException e) { Toast.makeText(this, "Target activity not found.", Toast.LENGTH_LONG).show(); } } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode != RESULT_OK) return; switch (requestCode) { case REQUEST_CODE: String result = data.getStringExtra("RESULT"); // *** POINT 15 *** Handle the received data carefully and securely, // even though the data came from an in-house application. // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely." Toast.makeText(this, String.format("Received result: ¥"%s¥"", result), Toast.LENGTH_LONG).show(); break; } } } SigPerm.java package org.jssec.android.shared; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PermissionInfo; public class SigPerm { public static boolean test(Context ctx, String sigPermName, String correctHash) { if (correctHash == null) return false; correctHash = correctHash.replaceAll(" ", ""); return correctHash.equals(hash(ctx, sigPermName)); } public static String hash(Context ctx, String sigPermName) { if (sigPermName == null) return null; try { // Get the package name of the application which declares a permission named sigPermName. PackageManager pm = ctx.getPackageManager(); PermissionInfo pi; pi = pm.getPermissionInfo(sigPermName, PackageManager.GET_META_DATA); String pkgname = pi.packageName; // Fail if the permission named sigPermName is not a Signature Permission if (pi.protectionLevel != PermissionInfo.PROTECTION_SIGNATURE) return null; // Return the certificate hash value of the application which declares a permission named sigPermName. return PkgCert.hash(ctx, pkgname); } catch (NameNotFoundException e) { return null; } } } PkgCert.java package org.jssec.android.shared; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.Signature; public class PkgCert { public static boolean test(Context ctx, String pkgname, String correctHash) { if (correctHash == null) return false; correctHash = correctHash.replaceAll(" ", ""); return correctHash.equals(hash(ctx, pkgname)); } public static String hash(Context ctx, String pkgname) { if (pkgname == null) return null; try { PackageManager pm = ctx.getPackageManager(); PackageInfo pkginfo = pm.getPackageInfo(pkgname, PackageManager.GET_SIGNATURES); if (pkginfo.signatures.length != 1) return null; // Will not handle multiple signatures. Signature sig = pkginfo.signatures[0]; byte[] cert = sig.toByteArray(); byte[] sha256 = computeSha256(cert); return byte2hex(sha256); } catch (NameNotFoundException e) { return null; } } private static byte[] computeSha256(byte[] data) { try { return MessageDigest.getInstance("SHA-256").digest(data); } catch (NoSuchAlgorithmException e) { return null; } } private static String byte2hex(byte[] data) { if (data == null) return null; final StringBuilder hexadecimal = new StringBuilder(); for (final byte b : data) { hexadecimal.append(String.format("%02X", b)); } return hexadecimal.toString(); } } 要点 16:导出 APK 时,请使用与目标应用相同的开发人员密钥对 APK 进行签名。

优秀的个人博客,低调大师

安卓应用安全指南 4.1.3 创建/使用活动 高级话题

4.1.3 创建/使用活动 高级话题 原书:Android Application Secure Design/Secure Coding Guidebook 译者:飞龙 协议:CC BY-NC-SA 4.0 4.1.3.1 组合导出属性和意图过滤器(对于活动) 我们已经解释了如何实现本指南中的四类活动:私有活动,公共活动,伙伴活动和内部活动。 下表中定义了每种类型的导出属性的允许的设置,和intent-filter元素的各种组合,它们在AndroidManifest.xml文件中定义。 请使用你尝试创建的活动,验证导出属性和intent-filter元素的兼容性。 导出属性的值 True 意图过滤器已定义 公开 意图过滤器未定义 公开、伙伴、内部 表 4.1-2 当未指定Activity的导出属性时,Activity是否为公开的,取决于Activity的意图过滤器的存在与否 [4]。 但是,在本手册中,禁止将导出属性设置为未指定。通常,如前所述,最好避免依赖任何给定 API 的默认行为的实现;此外,如果存在明确的方法(例如导出属性)来启用重要的安全相关设置,那么使用这些方法总是一个好主意。 如果定义了任何意图过滤器,则该活动是公开的;否则它是私有的。更多信息请参阅 https://developer.android.com/guide/topics/manifest/activity-element.html#exported。 不应该使用未定义的意图过滤器和导出属性false的原因,是 Android 的行为存在漏洞,并且由于意图过滤器的工作原理,其他应用的活动可能会意外调用它。下面的两个图展示了这个解释。图 4.1-4 是一个正常行为的例子,其中私有活动(应用 A)只能由同一个应用的隐式Intent调用。 意图过滤器(action ="X")被定义为仅在应用 A 内部工作,所以这是预期的行为。 下面的图 4.1-5 展示了一个场景,其中在应用 B 和应用 A 中定义了相同的意图过滤器(action ="X")。应用 A 试图通过发送隐式意图,来调用同一应用中的私有活动 ,但是这次显示了对话框,询问用户选择哪个应用,以及应用 B 中的公共活动 B-1 ,由于用户的选择而错误调用。 由于这个漏洞,可能会将敏感信息发送到其他应用,或者应用可能会收到意外的返回值。 如上所示,使用意图过滤器,将隐式意图发送到私有应用,可能会导致意外行为,因此最好避免此设置。 另外,我们已经验证了这种行为不依赖于应用 A 和应用 B 的安装顺序。 4.1.3.2 验证请求应用 我们在此解释一些技术信息,关于如何实现伙伴活动。 伙伴应用只允许白名单中注册的特定应用访问,并且所有其他应用都被拒绝。 由于除内部应用之外的其他应用也需要访问权限,因此我们无法使用签名权限进行访问控制。 简而言之,我们希望验证尝试使用伙伴活动的应用,通过检查它是否在预定义的白名单中注册,如果是,则允许访问,如果不是,则拒绝访问。 应用验证的方式是,从请求访问的应用获取证书,并将其与白名单中的散列进行比较。 一些开发人员可能会认为,仅仅比较软件包名称而不获取证书就足够了,但是,很容易伪装成合法应用的软件包名称,因此这不是检查真实性的好方法。 任意指定的值不应用于认证。 另一方面,由于只有应用开发人员拥有用于签署证书的开发人员密钥,因此这是识别的更好方法。 由于证书不容易被伪造,除非恶意第三方可以窃取开发人员密钥,否则恶意应用被信任的可能性很小。 虽然可以将整个证书存储在白名单中,但为了使文件大小最小,仅存储 SHA-256 散列值就足够了。 使用这个方法有两个限制: 请求应用需要使用startActivityForResult()而不是startActivity()。 请求应用应该只从Activity调用。 第二个限制是由于第一个限制而施加的限制,因此技术上只有一个限制。 由于Activity.getCallingPackage()的限制,它获取调用应用的包名称,所以会发生此限制。 Activity.getCallingPackage()仅在由startActivityForResult()调用时,才返回源(请求)应用的包名,但不幸的是,当它由startActivity()调用时,它仅返回null。 因此,使用此处解释的方法时,源(请求)应用需要使用startActivityForResult(),即使它不需要获取返回值。 另外,startActivityForResult()只能在Activity类中使用,所以源(请求者)仅限于活动。 PartnerActivity.java package org.jssec.android.activity.partneractivity; import org.jssec.android.shared.PkgCertWhitelists; import org.jssec.android.shared.Utils; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Toast; public class PartnerActivity extends Activity { // *** POINT 4 *** Verify the requesting application's certificate through a predefined whitelist. private static PkgCertWhitelists sWhitelists = null; private static void buildWhitelists(Context context) { boolean isdebug = Utils.isDebuggable(context); sWhitelists = new PkgCertWhitelists(); // Register certificate hash value of partner application org.jssec.android.activity.partneruser .sWhitelists.add("org.jssec.android.activity.partneruser", isdebug ? // Certificate hash value of "androiddebugkey" in the debug.keystore. "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255" : // Certificate hash value of "partner key" in the keystore. "1F039BB5 7861C27A 3916C778 8E78CE00 690B3974 3EB8259F E2627B8D 4C0EC35A"); // Register the other partner applications in the same way. } private static boolean checkPartner(Context context, String pkgname) { if (sWhitelists == null) buildWhitelists(context); return sWhitelists.test(context, pkgname); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // *** POINT 4 *** Verify the requesting application's certificate through a predefined whitelist. if (!checkPartner(this, getCallingActivity().getPackageName())) { Toast.makeText(this, "Requesting application is not a partner application.", Toast.LENGTH_LONG).show(); finish(); return; } // *** POINT 5 *** Handle the received intent carefully and securely, even though the intent was sent from a partner application. // Omitted, since this is a sample. Refer to "3.2 Handling Input Data Carefully and Securely." Toast.makeText(this, "Accessed by Partner App", Toast.LENGTH_LONG).show(); } public void onReturnResultClick(View view) { // *** POINT 6 *** Only return Information that is granted to be disclosed to a partner application. Intent intent = new Intent(); intent.putExtra("RESULT", "Information for partner applications"); setResult(RESULT_OK, intent); finish(); } } PkgCertWhitelists.java package org.jssec.android.shared; import java.util.HashMap; import java.util.Map; import android.content.Context; public class PkgCertWhitelists { private Map<String, String> mWhitelists = new HashMap<String, String>(); public boolean add(String pkgname, String sha256) { if (pkgname == null) return false; if (sha256 == null) return false; sha256 = sha256.replaceAll(" ", ""); if (sha256.length() != 64) return false; // SHA-256 -> 32 bytes -> 64 chars sha256 = sha256.toUpperCase(); if (sha256.replaceAll("[0-9A-F]+", "").length() != 0) return false; // found non hex char mWhitelists.put(pkgname, sha256); return true; } public boolean test(Context ctx, String pkgname) { // Get the correct hash value which corresponds to pkgname. String correctHash = mWhitelists.get(pkgname); // Compare the actual hash value of pkgname with the correct hash value. return PkgCert.test(ctx, pkgname, correctHash); } } PkgCert.java package org.jssec.android.shared; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.Signature; public class PkgCert { public static boolean test(Context ctx, String pkgname, String correctHash) { if (correctHash == null) return false; correctHash = correctHash.replaceAll(" ", ""); return correctHash.equals(hash(ctx, pkgname)); } public static String hash(Context ctx, String pkgname) { if (pkgname == null) return null; try { PackageManager pm = ctx.getPackageManager(); PackageInfo pkginfo = pm.getPackageInfo(pkgname, PackageManager.GET_SIGNATURES); if (pkginfo.signatures.length != 1) return null; // Will not handle multiple signatures. Signature sig = pkginfo.signatures[0]; byte[] cert = sig.toByteArray(); byte[] sha256 = computeSha256(cert); return byte2hex(sha256); } catch (NameNotFoundException e) { return null; } } private static byte[] computeSha256(byte[] data) { try { return MessageDigest.getInstance("SHA-256").digest(data); } catch (NoSuchAlgorithmException e) { return null; } } private static String byte2hex(byte[] data) { if (data == null) return null; final StringBuilder hexadecimal = new StringBuilder(); for (final byte b : data) { hexadecimal.append(String.format("%02X", b)); } return hexadecimal.toString(); } } 4.1.3.3 读取发送给活动的意图 在 Android 5.0(API Level 21)及更高版本中,使用getRecentTasks()得到的信息仅限于调用者自己的任务,并且可能还有一些其他任务,例如已知不敏感的其他任务。 但是支持 Android 5.0(API Level 21)版本的应用应该防止泄露敏感信息。 以下描述了问题内容,它出现在 Android 5.0 及更早版本中。 发送到任务的根Activity的意图,被添加到任务历史中。 根活动是在任务中启动的第一个活动。 任何应用都可以通过使用ActivityManager类,读取添加到任务历史的意图。 下面显示了从应用中读取任务历史的示例代码。 要浏览任务历史,请在AndroidManifest.xml文件中指定GET_TASKS权限。 AndroidManifest.xml <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.jssec.android.intent.maliciousactivity" > <!-- Use GET_TASKS Permission --> <uses-permission android:name="android.permission.GET_TASKS" /> <application android:allowBackup="false" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MaliciousActivity" android:label="@string/title_activity_main" android:exported="true" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> MaliciousActivity.java package org.jssec.android.intent.maliciousactivity; import java.util.List; import java.util.Set; import android.app.Activity; import android.app.ActivityManager; import android.content.Intent; import android.os.Bundle; import android.util.Log; public class MaliciousActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.malicious_activity); // Get am ActivityManager instance. ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE); // Get 100 recent task info. List<ActivityManager.RecentTaskInfo> list = activityManager .getRecentTasks(100, ActivityManager.RECENT_WITH_EXCLUDED); for (ActivityManager.RecentTaskInfo r : list) { // Get Intent sent to root Activity and Log it. Intent intent = r.baseIntent; Log.v("baseIntent", intent.toString()); Log.v(" action:", intent.getAction()); Log.v(" data:", intent.getDataString()); if (r.origActivity != null) { Log.v(" pkg:", r.origActivity.getPackageName() + r.origActivity.getClassName()); } Bundle extras = intent.getExtras(); if (extras != null) { Set<String> keys = extras.keySet(); for(String key : keys) { Log.v(" extras:", key + "=" + extras.get(key).toString()); } } } } } 你可以使用AcitivityManager类的getRecentTasks()函数,来获取任务历史的指定条目。 每个任务的信息存储在ActivityManager.RecentTaskInfo类的实例中,但发送到任务根Activity的意图存储在其成员变量baseIntent中。 由于根Activity是创建任务时启动的Activity,请务必在调用Activity时,不要满足以下两个条件。 新的任务在活动被调用时创建 被调用的活动是任务的根活动,它已经在前台或者后台存在 4.1.3.4 根活动 根活动是作为任务起点的活动。 换句话说,这是创建任务时启动的活动。 例如,当默认活动由启动器启动时,此活动将是根活动。 根据 Android 规范,发送到根Activity的意图的内容可以从任意应用中读取。 因此,有必要采取对策,不要将敏感信息发送到根活动。 在本指南中,已经制定了以下三条规则来避免被调用的Activity成为根活动。 不要指定taskAffinity 不要指定launchMode 发送给活动的意图中,不要设置FLAG_ACTIVITY_NEW_TASK 我们考虑一个情况,活动可以成为下面的根活动。 被调用的活动成为根活动,取决于以下内容。 被调用活动的启动模式 被调用活动的任务及其启动模式 首先,让我解释一下“被调用活动的启动模式”。 可以通过在AndroidManifest.xml中编写android:launchMode来设置Activity的启动模式。 当它没有编写时,它被认为是“标准”。 另外,启动模式也可以通过设置意图的标志来更改。 标志FLAG_ACTIVITY_NEW_TASK以singleTask模式启动活动。 启动模式可以指定为这些。我会解释它们和根活动的关系。 标准(standard) 此模式调用的活动不会是根,它属于调用者端的任务。 每次调用时,都会生成活动实例。 singleTop 这个启动模式和“标准”相同,除了启动一个活动,它显示在前台任务的最前面时,不会生成实例。 singleTask 这个启动模式根据 Affinity 值确定活动所属的任务。 当匹配Activity的 Affinity 的任务不存在于后台或前台时,新任务随Activity的实例一起生成。 当任务存在时,它们都不会被生成。 在前者中,已启动的Activity实例成为根。 singleInstance 与singleTask相同,但以下几点不同。 只有根活动可以属于新生成的任务。 因此,通过此模式启动的活动实例,始终是根活动。 现在,我们需要注意的是,虽然任务已经存在,并且名称和被调用Activity的 Affinity 相同,但是被调用Activity的类名和包含在任务中的Activity的类名是不同的。 从上面我们可以知道,由singleTask或singleInstance启动的Activity有可能成为根。 为了确保应用的安全性,它不应该由这些模式启动。 接下来,我将解释“被调用活动的任务及其启动模式”。 即使Activity以“标准”模式调用,它也会成为根Activity。在某些情况下,取决于Activity所属的任务状态。 例如,考虑被调用Activity的任务已经在后台运行的情况。 这里的问题是,任务的活动实例以singleInstance启动,当以“标准”调用的Activity的 Affinity 与任务相同时,新任务的生成受到现有的singleInstance 活动的限制。但是,当每个活动的类名称相同时,不会生成任务,并使用现有活动实例。在任何情况下,被调用活动都将成为根活动。 如上所述,调用根Activity的条件很复杂,例如取决于执行状态。 因此,在开发应用时,最好设法以“标准”来调用活动。 这是一个示例,其中发送给私有活动的意图,可以从其他应用中读取。示例代码表明,私有活动的调用方活动以singleInstance模式启动。 在这个示例代码中,私有活动以“标准”模式启动,但由于调用方Activity的singleInstance条件,这个私有活动成为新任务的根Activity。 此时,发送给私有活动的敏感信息,在任务历史中记录,因此可以从其他应用读取。 仅供参考,调用方活动和私有活动都具有相同的 Affinity。 AndroidManifest.xml(不推荐) <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.jssec.android.activity.singleinstanceactivity" > <application android:allowBackup="false" android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <!-- Set the launchMode of the root Activity to "singleInstance". --> <!-- Do not use taskAffinity --> <activity android:name="org.jssec.android.activity.singleinstanceactivity.PrivateUserActivity" android:label="@string/app_name" android:launchMode="singleInstance" android:exported="true" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <!-- Private activity --> <!-- Set the launchMode to "standard." --> <!-- Do not use taskAffinity --> <activity android:name="org.jssec.android.activity.singleinstanceactivity.PrivateActivity" android:label="@string/app_name" android:exported="false" /> </application> </manifest> 私有活动仅仅将结果返回个收到的意图。 PrivateActivity.java package org.jssec.android.activity.singleinstanceactivity; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Toast; public class PrivateActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.private_activity); // Handle intent securely, even though the intent sent from the same application. // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely." String param = getIntent().getStringExtra("PARAM"); Toast.makeText(this, String.format("Received param: ¥"%s¥"", param), Toast.LENGTH_LONG).show(); } public void onReturnResultClick(View view) { Intent intent = new Intent(); intent.putExtra("RESULT", "Sensitive Info"); setResult(RESULT_OK, intent); finish(); } } 在私有活动的调用方,私有活动以“标准”模式启动,意图不带有任何标志。 PrivateUserActivity.java package org.jssec.android.activity.singleinstanceactivity; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Toast; public class PrivateUserActivity extends Activity { private static final int REQUEST_CODE = 1; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.user_activity); } public void onUseActivityClick(View view) { // Start the Private Activity with "standard" lanchMode. Intent intent = new Intent(this, PrivateActivity.class); intent.putExtra("PARAM", "Sensitive Info"); startActivityForResult(intent, REQUEST_CODE); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode != RESULT_OK) return; switch (requestCode) { case REQUEST_CODE: String result = data.getStringExtra("RESULT"); // Handle received result data carefully and securely, // even though the data came from the Activity in the same application. // Omitted, since this is a sample. Please refer to "3.2 Handling Input Data Carefully and Securely." Toast.makeText(this, Str(); break; } } } 4.1.3.5 使用活动时的日志输出 当使用一个活动时,意图的内容通过ActivityManager输出到LogCat。 以下内容将被输出到LogCat,因此在这种情况下,敏感信息不应该包含在这里。 目标包名称 目标类名称 由Intent#setData()设置的 URI 例如,当应用发送邮件时,如果应用将邮件地址指定为 URI,则邮件地址不幸会输出到LogCat。 所以,最好通过设置Extras来发送。 如下所示发送邮件时,邮件地址会显示给logCat。 MainActivity.java // URI is output to the LogCat. Uri uri = Uri.parse("mailtoest@gmail.com"); Intent intent = new Intent(Intent.ACTION_SENDTO, uri); startActivity(intent); 当使用Extras时,邮件地址不会再展示给LogCat了。 MainActivity.java // Contents which was set to Extra, is not output to the LogCat. Uri uri = Uri.parse("mailto:"); Intent intent = new Intent(Intent.ACTION_SENDTO, uri); intent.putExtra(Intent.EXTRA_EMAIL, new String[] {"test@gmail.com"}); startActivity(intent); 但是,有些情况下,其他应用可以使用ActivityManager#getRecentTasks()读取意图的附加数据。 请参阅“4.1.2.2 不指定taskAffinity(必需)”,“4.1.2.3 不指定launchMode(必需)”和“4.1.2.4 不要为启动活动的Intent设置FLAG_ACTIVITY_NEW_TASK标志(必需)”。 4.1.3.6 防止PreferenceActivity中的Fragment注入 当从PreferenceActivity派生的类是公共活动时,可能会出现称为片段注入 [5] 的问题。 为了防止出现这个问题,有必要重写PreferenceActivity.IsValidFragment(),并检查其参数的有效性,来确保Activity不会无意中处理任何Fragment`。 (输入数据安全的更多信息,请参见第3.2节“小心和安全地处理输入数据”。) [5] Fragement注入的更多信息,请参考:https://securityintelligence.com/new-vulnerability-android-framework-fragment-injection/。 下面我们显示一个覆盖IsValidFragment()的示例。 请注意,如果源代码已被混淆,则类名称和参数值比较的结果可能会更改。 在这种情况下,有必要寻求替代对策。 覆盖的isValidFragment()方法的示例 protected boolean isValidFragment(String fragmentName) { // If the source code is obfuscated, we must pursue alternative strategies return PreferenceFragmentA.class.getName().equals(fragmentName) || PreferenceFragmentB.class.getName().equals(fragmentName) || PreferenceFragmentC.class.getName().equals(fragmentName) || PreferenceFragmentD.class.getName().equals(fragmentName); } 请注意,如果应用的targetSdkVersion为 19 或更大,不覆盖PreferenceActivity.isValidFragment()将导致安全异常,并在插入Fragment时终止应用 [调用isValidFragment()时],因此在这种情况下,覆盖 PreferenceActivity.isValidFragment()是强制性的。

优秀的个人博客,低调大师

Unity 5.3 安装完没有Android(安卓)或IOS Module(模块)?

文章目录[点击展开](?)[+] Unity5.3 模块独立 Unity5.3把各个模块分开来了,主程序安装包更轻巧,在官网下载的话,能下载到Unity安装程序,Unity编辑器等一些资源package,其它的模块可以通过Unity安装程序进行下载 下载存档链接:http://unity3d.com/cn/get-unity/download/archive 模块文件 下面是我首次下载的文件 只有web player平台? 安装UnitySetup64-5.3.1f1之后,我发现在安装目录没有Document文档, 打开Unity也会发现只有Web Player平台,其它平台提示模块没有下载,那怎么办呢? 解决方法 1、运行UnityDownloadAssistant(下载助手),此助手可以下载Unity的各个模块,如下图所示,根据需要选择相应的模块 2、设置下载文件的存放,确认Unity的安装目录 ,继续下一步。 下载的模块文件 3、下载成功后,模块会自动安装(日后发给同事他们可以双击安装) 查看已安装模块 4、在Unity的模块管理面板可以看到已安装的模块。 本文转自赵青青博客园博客,原文链接:http://www.cnblogs.com/zhaoqingqing/p/5061732.html,如需转载请自行联系原作者

优秀的个人博客,低调大师

安卓7.0遇到 android.os.FileUriExposedException: file:///storage/emulated.....

1、在AndroidManifest.xml中添加如下代码 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" ... <application ... <provider android:name="android.support.v4.content.FileProvider" android:authorities="${applicationId}.provider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths"/> </provider> </application> </manifest> 2、在res目录下新建一个xml文件夹,并且新建一个provider_paths的xml文件 <?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-path name="external_files" path="."/> </paths> 3、修改代码 Uri photoURI = Uri.fromFile(createImageFile()); 变成:Uri photoURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".provider", createImageFile()); ,如需转载请自行联系原作者

优秀的个人博客,低调大师

为安卓开发者介绍的移动开发框架 Flutter

我是TrueFace的高级软件工程师 Adel Boussaken。今天我要给大家介绍 Flutter 开发。欢迎加入 Dart 阵营。 我将跳过所有有关安装和运行 Flutter 应用程序的部分,直接进行操作,如果您不确定 Flutter 是什么,并且有许多问题,请阅读关于 Flutter 的演进之路。 Dart 如果你认为 Kotlin 是 Java 的替代,等到你看到 Dart 后,你会发现它是直截了当,没有样板代码的: 默认为public,通过前缀“_” 进行私有化; 集合;点击试用 一切都是对象 - 没有“primitives” 命名参数,可选参数,默认值:点击试用 属性 -无需在任何地方写“get”方法 级联 -一切都是构建者 使用strong mode类型推导 - 只需要在本地写“var” 命名构造函数,自动分配到字段;点击试用 字符串插值,几种类型的字符串;点击试用 dartfmt -如果不想格式化也可以不用这个工具 这里有一个 Dart 代码的例子,一个获取 IP 地址的私有方法: _getIPAddress() async { String url = 'https://httpbin.org/ip'; var httpClient = createHttpClient(); var response = await httpClient.read(url); Map data = JSON.decode(response); String ip = data['origin']; } 设计模式 我很确定你已将你的 Android 技巧从无序代码扩展到 MVC、MVP、MVVM,Dagger2、DataBinding 和Android 架构组件,Flutter 基本上就是一个 V(View),响应式视图,它可以是无状态或有状态的 widget,就连 AppCompatActivity 也是一个 widget。 让我们创建一个非常简单的 Activity,我们需要一个文本视图和一个更新该文本的按钮,下面是 Activity 代码(注意没有 XML): public class Click extends Activity { int count = 0; protected void onCreate(Bundle bundle) { super.onCreate(bundle); setContentView(R.layout.main); final Button button = (Button) findViewById(R.id.button); final TextView text = (TextView) findViewById(R.id.counter); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { count++; text.setText("Button clicked " + counter + " times"); } }); } } 在 Flutter,你不需要操纵视图,就像 setText() 中的代码,你可以使用 state 来替代,并且 Flutter 将会处理好屏幕更新,在 Flutter 中和上述代码等价的是: import 'package:flutter/material.dart';class Activity extends StatefulWidget { @override ActivityState createState() => new ActivityState(); }class ActivityState extends State<Activity> { int counter = 0; @override Widget build(BuildContext context) { return new Scaffold( body: new Center( child: new Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new Text( 'Button clicked ${counter} times', ), new FlatButton( child: new Text('Increment'), onPressed: () { setState(() { counter++; }); }, ), ], ), ), ); } } 你也注意到,我们在 setState 中增加了 counter 的值,一旦 onPressed 被触发(之后是 setState),Flutter 将会使用新的 counter 值来重新渲染整个 widget 和 text 对象。 分离 widget 和 state(对于 Android 开发人员来说)的最大优点是,如果你在 Java 代码中旋转设备或调整窗口大小(支持多窗口),则计数器的值将被重置为零,你必须编写大量重复代码来管理 Activity 的生命周期。而在 Flutter 中,这是不需要的,现成的功能,无需额外的代码。 虽然 Dart 最终编译为 native 代码,但它不会访问硬件设备,例如,如果你要获取用户位置,你必须使用平台通道编写特定平台的代码,与此同时,确实有很多插件来处理这些情况,但你依然希望登录Android Studio(或 Xcode)在代码之间完成该事项。 Async/Await 在 Android 开发中,UI 线程非常重要,完成速度稍慢的事情工作,会显示以下神秘行: I/Choreographer(1378): Skipped 55 frames! The application may be doing too much work on its main thread. 这种情况你只能使用线程来处理,如下所示: public void onClick(View v) { new Thread(new Runnable() { public void run() { // a potentially time consuming task final Bitmap bitmap = processBitMap("image.png"); mImageView.post(new Runnable() { public void run() { mImageView.setImageBitmap(bitmap); } }); } }).start(); } 上述代码可以很简单的使用 Dart 完成: onClick() async { Bitmap bitmap = await processBitMap("image.png"); // you don't want to update non-existent widget if (!mounted) return; setState(() { imageView = bitmap; }); } 这里发生了什么? async 修饰符标记的方法会安排稍后执行,该方法变成非阻塞的,因此你的应用程序不会丢失任何帧,await 修饰符挂起方法的执行,等待 processBitMap 完成,然后执行将会恢复。 Widget 目前有大量在 Android 之上运行的各种 OEM 软件和皮肤,这都是可以理解的,因为每个制造商都希望为其设备建立自己的附加值,我不能计算我究竟说了多少次“Funck S*ms*ng”,OEM 定制对 Android 开发人员引入了很多难题.。 Flutter,为了实现 120FPS 的目标,已决定开始在一个空的画布上,使用Skia2D 图形库绘制每个 widget,而不是重用 OEM widget,并实现大多数 Material Designa 和通用 layout。 这种方法,无法保证你的应用程序在多种设备上看起来是一样的,尽管并不是所有的 Flutter widget 都经过优化,可以在平板电脑上也可以在较小屏幕的设备上正常工作。 无论如何,这种方法也为 UI 设计的创新打开了新的大门,例如,以下的代码可以使得用户改变 Activity 的大小: class HeroScreen extends StatefulWidget { @override HeroState createState() => new HeroState(); }class HeroState extends State<HeroScreen> with SingleTickerProviderStateMixin { bool _scaling = false; Matrix4 _transformScale = new Matrix4.identity(); double scalePos = 1.0; set _scale(double value) => _transformScale = new Matrix4.identity()..scale(value, value, 1.0); @override Widget build(BuildContext context) { return new Scaffold( body: new GestureDetector( onScaleStart: (details) { setState(() { _scaling = true; }); }, onScaleUpdate: (details) { var n = details.scale; setState(() { scalePos = n; _scale = n; }); }, onScaleEnd: (details) { setState(() { _scaling = false; _scale = scalePos < 1 ? 1.0 : scalePos; }); }, child: new Container( color: Colors.black26, child: new Transform( transform: _transformScale, child: new Center( child: new Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new Text(_scaling ? "Scaling" : "Scale me up"), ])))))); } } 这让我们得到一个非常有趣的小部件,这样去写可重复使用的小部件就太容易了, 我可以将以上不太友好的代码转换成可重用且干净的小部件: class Scale extends StatefulWidget { final Widget child; final GestureScaleStartCallback onStart; final GestureScaleEndCallback onEnd; final GestureScaleUpdateCallback onUpdate; Scale({Key key, this.child, this.onStart, this.onUpdate, this.onEnd}) : super(); @override ScaleState createState() => new ScaleState(); }class ScaleState extends State<Scale> with SingleTickerProviderStateMixin { Matrix4 _transformScale = new Matrix4.identity(); double scalePos = 1.0; set _scale(double value) => _transformScale = new Matrix4.identity()..scale(value, value, 1.0); @override Widget build(BuildContext context) { return new GestureDetector( onScaleStart: widget.onStart, onScaleUpdate: (details) { widget.onUpdate(details); var n = details.scale; setState(() { scalePos = n; _scale = n; }); }, onScaleEnd: (details) { widget.onEnd(details); setState(() { _scale = scalePos < 1 ? 1.0 : scalePos; }); }, child: new Container( color: Colors.black26, child: new Transform( transform: _transformScale, child: widget.child))); } } 重构后的代码如下: class HeroScreen extends StatefulWidget { @override HeroState createState() => new HeroState(); }class HeroState extends State<HeroScreen> with SingleTickerProviderStateMixin { bool _scaling = false; @override Widget build(BuildContext context) { return new Scaffold( body: new Scale( onStart: (details) => setState(() => _scaling = true), onUpdate: (details) {}, onEnd: (details) => setState(() => _scaling = false), child: new Container( color: Colors.black26, child: new Center( child: new Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ new Text(_scaling ? "Scaling" : "Scale me up"), ]))), )); } } Etc 即时运行称为热重新加载,需要不到一秒钟 Flutter 使用 CPU 绘制图形、使用GPU 渲染, 但是一些关于 3D 的 API 是不可用的 不用 XML 来设计布局 布局只需一次性计算树的大小 即使有语法错误,您的 APP 也不会崩溃 Flutter 小部件可以嵌入到 Android Activity 中 您不能在 Flutter 小部件中嵌入 Android 库 至少需要 60 FPS,除非你的机器正在做别的事情 受到 React 的启发设计 了API,但不是像 React 那样运行的 不需要任何 POJO 原文发布时间为:2017-10-10 本文作者:佚名 本文来自云栖社区合作伙伴“51CTO”,了解相关信息可以关注。

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册