手机安全卫士10-设置向导之绑定SIM卡

最后更新于:2022-04-01 16:16:35

上回主要做了设置向导界面的界面设计,主要涉及到界面的布局和一些控件的使用。这次要做设置向导界面的功能具体实现。 首先,4个界面分别是(重复度很大,这里就不再贴到正文中了) 1. [/mobilesafe/res/layout/setup_wizard1.xml](https://github.com/hoxis/mobilesafe/blob/master/res/layout/setup_wizard1.xml) 1. [/mobilesafe/res/layout/setup_wizard2.xml](https://github.com/hoxis/mobilesafe/blob/master/res/layout/setup_wizard2.xml) 1. [/mobilesafe/res/layout/setup_wizard3.xml](https://github.com/hoxis/mobilesafe/blob/master/res/layout/setup_wizard3.xml) 1. [/mobilesafe/res/layout/setup_wizard4.xml](https://github.com/hoxis/mobilesafe/blob/master/res/layout/setup_wizard4.xml) ### Activity之间的切换动画效果 - public void **overridePendingTransition**(int enterAnim, int exitAnim) > 两个参数: > - **enterAnim**:进入新Activity的动画效果 > - **exitAnim**:退出当前Activity的动画效果 ### 创建动画效果: - /mobilesafe/res/anim/alpha_in.xml ~~~ <?xml version="1.0" encoding="utf-8"?> <alpha xmlns:android="http://schemas.android.com/apk/res/android" android:duration="300" android:fromAlpha="0.0" android:toAlpha="1.0" > </alpha> ~~~ - [/mobilesafe/res/anim/alpha_out.xml](https://github.com/hoxis/mobilesafe/blob/master/res/anim/alpha_out.xml) ### 为“下一步”按钮添加点击事件: - [/mobilesafe/src/com/liuhao/mobilesafe/ui/SetupWizard1Activity.java](https://github.com/hoxis/mobilesafe/blob/master/src/com/liuhao/mobilesafe/ui/SetupWizard1Activity.java) ~~~ package com.liuhao.mobilesafe.ui; import com.liuhao.mobilesafe.R; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class SetupWizard1Activity extends Activity implements OnClickListener { private Button button; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.setup_wizard1); button = (Button) this.findViewById(R.id.bt_next); button.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.bt_next: finish();// 用户点击“后退”时不会再看到这个界面 Intent intent = new Intent(this, SetupWizard2Activity.class); startActivity(intent); // 设置Activity切换时的动画效果 overridePendingTransition(R.anim.alpha_in, R.anim.alpha_out); break; } } } ~~~ ### 设置界向导面2 需求功能: 1. 绑定SIM卡 2. CheckBox状态的处理 3. 上一步、下一步 点击功能的实现 - **绑定SIM卡** 在用户点击“绑定SIM卡”时,触发相应的处理逻辑,获取当前SIM卡的串号,并将串号存取到SharePreference中。 要获取手机SIM卡串号,需要添加权限:**android.permission.READ_PHONE_STATE** ~~~ /** * 绑定SIM串号 * */ private void setSimInfo() { TelephonyManager manager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE); String simSerialNumber = manager.getSimSerialNumber(); Editor edit = sp.edit(); edit.putString("sim", simSerialNumber); edit.commit(); Toast.makeText(getApplicationContext(), "SIM卡已绑定", Toast.LENGTH_SHORT).show(); } ~~~ - **CheckBox状态的处理** ~~~ // 首先初始化chexkbox的状态 String sim = sp.getString("sim", null); if(sim != null){ cb_bind.setText("已绑定SIM卡"); cb_bind.setChecked(true); }else{ cb_bind.setText("未绑定SIM卡"); cb_bind.setChecked(false); } cb_bind.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if(isChecked){ setSimInfo(); cb_bind.setText("已绑定SIM卡"); }else{ cb_bind.setText("未绑定SIM卡"); } } }); ~~~ ### 异常处理 弄好,运行代码,绑定手机SIM卡串号,没有问题。 再次打开,进入向导2界面时,出错,程序崩溃。 ### 错误日志(摘取了主要部分) E/AndroidRuntime(26463): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.liuhao.mobilesafe/com.liuhao.mobilesafe.ui.SetupWizard2Activity}: java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Boolean ... E/AndroidRuntime(26463): at com.liuhao.mobilesafe.ui.SetupWizard2Activity.onCreate(SetupWizard2Activity.java:42) ### 原因: 由于之前判断SharePreference中是否存在SIM信息是根据下面的逻辑: ~~~ // 首先初始化chexkbox的状态 if(sp.getBoolean("sim", false)){ cb_bind.setText("已绑定SIM卡"); cb_bind.setChecked(true); }else{ cb_bind.setText("未绑定SIM卡"); cb_bind.setChecked(false); } ~~~ 而**boolean android.content.SharedPreferences.getBoolean(String key, boolean defValue)**方法, > Retrieve a boolean value from the preferences. > Parameters: key The name of the preference to retrieve. defValue Value to return if this preference does not exist. Returns: Returns the preference value if it exists, or defValue. Throws ClassCastException if there is a preference with this name that is not a boolean. Throws: ClassCastException 可以发现,若已存在值,而这个值不是Boolean类型时将会抛出ClassCastException。 ### 修改 ~~~ // 首先初始化chexkbox的状态 String sim = sp.getString("sim", null); if(sim != null){ cb_bind.setText("已绑定SIM卡"); cb_bind.setChecked(true); }else{ cb_bind.setText("未绑定SIM卡"); cb_bind.setChecked(false); } ~~~ ### 运行效果 ![](image/d41d8cd98f00b204e9800998ecf8427e.png) ![](image/d41d8cd98f00b204e9800998ecf8427e.png) #### 完整代码: - [/mobilesafe/src/com/liuhao/mobilesafe/ui/SetupWizard2Activity.java](https://github.com/hoxis/mobilesafe/blob/master/src/com/liuhao/mobilesafe/ui/SetupWizard2Activity.java)
';

手机安全卫士09-手机防盗界面设置向导1

最后更新于:2022-04-01 16:16:32

本次主要做手机防盗界面的设置向导功能界面的设计。 ### 需求: 当用户进入手机防盗界面时,判断用户是否已经进行过设置向导: - 如果用户已经设置过手机防盗,则不再提示用户进入手机向导 - 若还没有设置,则提示用户进入设置向导界面。 ### 具体实现: - 1.当用户输入“手机防盗”密码正确时,进行判断用户是否进行过设置向导 ~~~ /** * 判断用户是否进行过设置向导 * @return */ private boolean isSetup(){ return sp.getBoolean("isAlreadySetup", false); } ~~~ - 2.创建“设置向导”的Activity,并添加到AndroidManifest.xml清单文件中 SetupWizard1Activity.java ~~~ public class SetupWizard1Activity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.setup_wizard1); } } ~~~ - 3.添加xml布局文件/mobilesafe/res/layout/setup_wizard1.xml - 3.1.由于每个向导界面的标题文字样式都是统一的,因此可以将标题文字样式抽取出来: - /mobilesafe/res/values/style.xml ~~~ <style name="text_title_style"> <item name="android:layout_width">fill_parent</item> <item name="android:layout_height">wrap_content</item> <item name="android:textSize">28sp</item> <item name="android:textColor">#ff00ff66</item> </style> ~~~ - 3.2.标题下面的分割线 因为后面要经常使用,此处也是将分割线抽象出来。 ~~~ <style name="devide_line_style"> <item name="android:layout_width">fill_parent</item> <item name="android:layout_height">wrap_content</item> <item name="android:layout_marginTop">8dip</item> <item name="android:layout_marginBottom">8dip</item> <item name="android:background">@drawable/devide_line</item> </style> ~~~ - 3.3.正文内容样式 ~~~ <style name="text_content_style"> <item name="android:layout_width">fill_parent</item> <item name="android:layout_height">wrap_content</item> <item name="android:textSize">20sp</item> <item name="android:textColor">#ff00ff66</item> </style> ~~~ - 3.4.文字前的小星星 ~~~ <style name="image_star_style"> <item name="android:layout_width">wrap_content</item> <item name="android:layout_height">wrap_content</item> <item name="android:src">@android:drawable/btn_star_big_on</item> <item name="android:layout_marginLeft">8dip</item> </style> ~~~ 此处的图片资源使用了android自带的资源,使用@android下的图片资源的好处: 1. 减少程序的体积 2. 提高程序获取图片的速度 - 3.5.进度显示图标 ~~~ <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginTop="5dip" android:gravity="center_horizontal" android:orientation="horizontal" > <ImageView style="@style/image_status_on_style" /> <ImageView style="@style/image_status_off_style" /> <ImageView style="@style/image_status_off_style" /> <ImageView style="@style/image_status_off_style" /> </LinearLayout> ~~~ - 3.5.图标以及“下一步” ~~~ <RelativeLayout android:layout_width="fill_parent" android:layout_height="fill_parent"> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginTop="10dip" | center_horizontal" | android:orientation="vertical" > <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/setup1" /> </LinearLayout> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" android:drawableRight="@drawable/next" android:text="下一步" /> </RelativeLayout> ~~~ - 4./mobilesafe/res/layout/setup_wizard1.xml ~~~ <?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:background="@color/background" android:orientation="vertical" > <TextView style="@style/text_title_style" android:text="1. 欢迎使用手机防盗" /> <ImageView style="@style/devide_line_style" /> <TextView style="@style/text_content_style" android:text="您的手机防盗卫士可以:" /> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginTop="5dip" android:orientation="horizontal" > <ImageView style="@style/image_star_style" /> <TextView style="@style/text_content_style" android:paddingTop="5dip" android:text="sim卡变更报警" /> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginTop="5dip" android:orientation="horizontal" > <ImageView style="@style/image_star_style" /> <TextView style="@style/text_content_style" android:paddingTop="5dip" android:text="GPS追踪" /> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginTop="5dip" android:orientation="horizontal" > <ImageView style="@style/image_star_style" /> <TextView style="@style/text_content_style" android:paddingTop="5dip" android:text="远程销毁数据" /> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginTop="5dip" android:orientation="horizontal" > <ImageView style="@style/image_star_style" /> <TextView style="@style/text_content_style" android:paddingTop="5dip" android:text="远程锁屏" /> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginTop="5dip" android:gravity="center_horizontal" android:orientation="horizontal" > <ImageView style="@style/image_status_on_style" /> <ImageView style="@style/image_status_off_style" /> <ImageView style="@style/image_status_off_style" /> <ImageView style="@style/image_status_off_style" /> </LinearLayout> <RelativeLayout android:layout_width="fill_parent" android:layout_height="fill_parent" > <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginTop="10dip" android:gravity="center_vertical|center_horizontal" android:orientation="vertical" > <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/setup1" /> </LinearLayout> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" android:drawableRight="@drawable/next" android:text="下一步" /> </RelativeLayout> </LinearLayout> ~~~ - 5.界面跳转 ~~~ if(isSetup()){ Log.i(TAG, "加载手机防盗主界面"); }else{ Log.i(TAG, "激活设置向导界面"); finish(); Intent intent = new Intent(getApplicationContext(), SetupWizard1Activity.class); startActivity(intent); } ~~~ - 6.显示效果 ![](image/d41d8cd98f00b204e9800998ecf8427e.png)
';

手机安全卫士08-一些布局和显示的细节:State List

最后更新于:2022-04-01 16:16:30

我们注意到有些应用里的按钮在点击时的显示状态和普通状态是不一样的,比如: 普通状态下: ![](image/d41d8cd98f00b204e9800998ecf8427e.png) 选中状态下:![](image/d41d8cd98f00b204e9800998ecf8427e.png) 那这种效果是如何实现的呢?在Android系统中提供给我们一种方便与实现这种功能的方法即:state list drawable。 **StateListDrawable**是在XML中定义的drawable对象,我们可以通过设置不同item下的图片来显示不同状态,这取决于 drawable对象的状态。例如,一个Button控件可以有不同的状态(点击、聚焦等),通过一系列的drawable对象,可以为每个不同的状态提供不同的背景图片。 可以通过一个xml文件来描述这个状态序列,通过一个`<selector>`元素下的`<item>`元素来代表每个背景,其中每个`<item>`可以使用不同的属性值来描述某个状态。 当状态发生改变时,状态列表从上到下进行扫描,会使用第一个匹配当前状态的图片。这个选择不是基于最佳匹配,而是简单选择遇到的第一个满足条件的项目。 它的XML文件定义如下: ~~~ <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android" android:constantSize=["true" | "false"] android:dither=["true" | "false"] android:variablePadding=["true" | "false"] > <item android:drawable="@[package:]drawable/drawable_resource" android:state_pressed=["true" | "false"] android:state_focused=["true" | "false"] android:state_hovered=["true" | "false"] android:state_selected=["true" | "false"] android:state_checkable=["true" | "false"] android:state_checked=["true" | "false"] android:state_enabled=["true" | "false"] android:state_activated=["true" | "false"] android:state_window_focused=["true" | "false"] /> </selector> ~~~ - `**<selector>**` 必须的。为根节点,包含一个或多个节点。 - android:constantSize: boolean型,默认为false; - android:dither:boolean型,默认为true,当位图与屏幕的像素配置不一样时(例如,一个ARGB为8888的位图与RGB为555的屏幕)会自行递色(dither)。设置为false时不可递色; - android:variablePadding:boolean型,默认为false,当设置为true时,则drawable的padding值随当前选择的状态而改变。 - `**<item>**` - android:drawable:必须的参数,drawable资源; - android:state_pressed:boolean型,设置为true时表示当对象被按下时该item会显示或者说生效,为false时表示该item为默认状态非选中状态; - android:state_focused:boolean型,为true时表示该item生效为焦点在对象上时,false为非选中状态; - android:state_selected:boolean型,同上功能,该属性表示的时被选择状态; - android:state_checkable:boolean型,仅仅用在可以选择widget上,为true表示可选择,为false表示不可选; - android:state_checked:boolean型,为true时,表示当选中时该item生效,false为未选中时生效; - android:state_enabled:boolean型,当为true时,该item在对象可激活时生效,如该对象可以接受触摸或者点击事件时; - android:state_window_focused:boolean型,为true时,表示该item在当前窗口焦点为该应用程序窗口时生效也就是说该应用程序窗口为foreground,否则为false; 1. 在/res/drawable 目录下建立自己需要的.xml文件如button_selector.xml ~~~ <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android" > <item android:state_enabled="true" android:state_window_focused="false" android:drawable="@drawable/button_background"/> <item android:state_pressed="true" android:state_window_focused="true" android:drawable="@drawable/button_background_selected"/> <item android:state_focused="true" android:drawable="@drawable/button_background"/> <item android:drawable="@drawable/button_background"/> </selector> ~~~ 2.在layout xml文件中引用: ~~~ <Button android:id="@+id/bt_first_dialog_confirm" android:layout_width="140dip" android:layout_height="40dip" android:background="@drawable/button_selector" android:text="确定" android:textColor="#ffffffff" /> ~~~ 同时我们也可以设置GridView中子控件点击时的效果。这里就不再赘述了。
';

手机安全卫士07-手机防盗之进入限制

最后更新于:2022-04-01 16:16:28

上次写到在进入手机但·防盗界面时需要有密码限制,首先第一次进入时会弹出对话框提示用户设置密码;再次进入时会要求用户输入密码;这次来具体实现上述功能。 ### 首次登录,设置密码 首先,我们的密码是保存在SharePreference中的”password”字段里的,在登录时后台需要校验该字段是否已经设置了密码,若未设置则弹出对话框让用户设置,否则要用户输入密码进入手机防盗界面; - **校验是否设置了密码** ~~~ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); sp = getSharedPreferences("config", Context.MODE_PRIVATE); // 判读用户是否已经设置了密码 if (isPwdSetup()) { Log.i(TAG, "设置了密码,弹出输入密码的对话框"); } else { Log.i(TAG, "未设置密码,弹出设置密码对话框"); showFirstEntryDialog(); } } /** * 检查sharedpreference中是否有密码的设置 * * @return */ private boolean isPwdSetup() { String password = sp.getString("password", null); if (password == null) { return false; } else { if ("".equals(password)) { return false; } else { return true; } } } ~~~ - **showFirstEntryDialog(),弹出用户设置密码对话框** ~~~ /** * 第一次进入程序时弹出的设置密码的对话框 * 使用自定义对话框样式 */ private void showFirstEntryDialog() { dialog = new Dialog(this, R.style.MyDialog); // dialog.setContentView(R.layout.first_entry_dialog);// 设置要显示的内容 View view = View.inflate(this, R.layout.first_entry_dialog, null); et_pwd = (EditText) view.findViewById(R.id.et_first_entry_pwd); et_pwd_confirm = (EditText) view.findViewById(R.id.et_first_entry_pwd_confirm); Button bt_confirm = (Button) view.findViewById(R.id.bt_first_dialog_confirm); Button bt_cancel = (Button) view.findViewById(R.id.bt_first_dialog_cancel); // 设置按钮对应的点击事件 bt_confirm.setOnClickListener(this); bt_cancel.setOnClickListener(this); dialog.setContentView(view); dialog.setCanceledOnTouchOutside(false);// 设置dialog不可以点击其他地方时消失 dialog.setCancelable(false);// 设置dialog不可以点返回键时消失 dialog.show(); } ~~~ - **用户输入后,后台对用户的输入进行处理** ~~~ @Override public void onClick(View view) { switch(view.getId()){ // 点击取消 case R.id.bt_first_dialog_cancel: dialog.dismiss(); break; case R.id.bt_first_dialog_confirm: String pwd = et_pwd.getText().toString().trim(); String pwd_confirm = et_pwd_confirm.getText().toString().trim(); // 输入的密码中包好空值 if("".equals(pwd) || "".equals(pwd_confirm)){ Toast.makeText(getApplicationContext(), "输入不能为空!", Toast.LENGTH_LONG).show(); return; }else{ if(pwd.equals(pwd_confirm)){ Editor editor = sp.edit(); editor.putString("password", pwd); editor.commit(); } // 两次输入不一致 else{ Toast.makeText(getApplicationContext(), "两次输入密码不相同!", Toast.LENGTH_LONG).show(); return; } } dialog.dismiss(); break; } } ~~~ ### 效果如下: 初次进入手机防盗界面: ![](image/d41d8cd98f00b204e9800998ecf8427e.png) 未输入时点击确定: ![](image/d41d8cd98f00b204e9800998ecf8427e.png) 两次输入密码不相同: ![](image/d41d8cd98f00b204e9800998ecf8427e.png) ### 再次登录,输入密码 - **弹出对话框样式:normal_entry_dialog.xml** ~~~ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="300dip" android:layout_height="280dip" android:gravity="center_horizontal" android:orientation="vertical" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="登录" android:textColor="@color/textcolor" android:textSize="24sp" /> <LinearLayout android:layout_width="300dip" android:layout_height="wrap_content" android:background="#ffc8c8c8" android:orientation="vertical" > <EditText android:id="@+id/et_normal_entry_pwd" android:layout_width="300dip" android:layout_height="wrap_content" android:hint="请输入密码" android:password="true" /> </LinearLayout> <LinearLayout android:layout_width="300dip" android:layout_height="50dip" android:gravity="center" android:orientation="horizontal" > <Button android:id="@+id/bt_normal_dialog_confirm" android:layout_width="140dip" android:layout_height="40dip" android:background="@drawable/button_background" android:text="确定" android:textColor="#ffffffff" /> <Button android:id="@+id/bt_normal_dialog_cancel" android:layout_width="140dip" android:layout_height="40dip" android:layout_marginLeft="3dip" android:background="@drawable/button_background" android:text="取消" /> </LinearLayout> </LinearLayout> ~~~ - **showNormalEntryDialog方法** ~~~ /** * 正常登录的对话框 * */ private void showNormalEntryDialog() { dialog = new Dialog(this, R.style.MyDialog); View view = View.inflate(this, R.layout.normal_entry_dialog, null); et_pwd = (EditText) view.findViewById(R.id.et_normal_entry_pwd); Button bt_confirm = (Button) view.findViewById(R.id.bt_normal_dialog_confirm); Button bt_cancel = (Button) view.findViewById(R.id.bt_normal_dialog_cancel); // 设置按钮对应的点击事件 bt_confirm.setOnClickListener(this); bt_cancel.setOnClickListener(this); dialog.setContentView(view); dialog.setCanceledOnTouchOutside(false);// 设置dialog不可以点击其他地方时消失 // dialog.setCancelable(false);// 设置dialog不可以点返回键时消失 dialog.show(); } ~~~ - **按键处理:** ~~~ @Override public void onClick(View view) { switch(view.getId()){ case R.id.bt_normal_dialog_cancel: dialog.dismiss(); break; case R.id.bt_normal_dialog_confirm: String input_pwd = et_pwd.getText().toString(); if("".equals(input_pwd)){ Toast.makeText(getApplicationContext(), "输入不能为空!", Toast.LENGTH_LONG).show(); return; }else{ String password = sp.getString("password", ""); if(!password.equals(input_pwd)){ Toast.makeText(getApplicationContext(), "输入密码不正确,请重新输入!", Toast.LENGTH_LONG).show(); et_pwd.selectAll();// 用户输入错误后,对文本进行全选,方便用户进行删除重新输入 return; } } Log.i(TAG, "加载手机防盗主界面"); dialog.dismiss(); break; } } ~~~ ### 效果如下: ![](image/d41d8cd98f00b204e9800998ecf8427e.png) ![](image/d41d8cd98f00b204e9800998ecf8427e.png) ### 密码加密存储 目前我们的密码存储都是以明文存储在SharePreference中的,因此有点Android开发基础的人都可以获取到我们设置的密码。 考虑使用加密算法对密码加密后进行存储。 使用JavaSe提供的MessageDigest类进行加密。MessageDigest支持的加密算法包括:MD5、SHA-1、SHA-256。 ~~~ package com.liuhao.mobilesafe.util; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class MD5Encoder { public static String encode(String pwd) { try { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] bytes = md.digest(pwd.getBytes()); StringBuffer sb = new StringBuffer(); for (byte b : bytes) { String str = Integer.toHexString(0xff & b);// byte是八位字节存储的,转化为16进制数,直接与11111111相与 if (str.length() == 1) { sb.append("0" + str); } else { sb.append(str); } } return sb.toString(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); throw new RuntimeException("不存在加密算法"); } } } ~~~ 这样在存储密码时调用encode()方法即可对密码进行存储。在读取时也要用加密后的密文与已存储的进行对比。 ~~~ editor.putString("password", MD5Encoder.encode(pwd)); if(!password.equals(MD5Encoder.encode(input_pwd))){ Toast.makeText(getApplicationContext(), "输入密码不正确,请重新输入!", Toast.LENGTH_LONG).show(); et_pwd.selectAll();// 用户输入错误后,对文本进行全选,方便用户进行删除重新输入 return; } ~~~ 其实我们仅仅简单的一次加密也是很不保险的,虽说从算法实现上来说md5加密是不可逆的,但是有些“别有用心”的人,竟然将所有可预见的字符串对应的密文都算出来了,真是。。。 比如这个网站:[http://www.cmd5.com/](http://www.cmd5.com/) ![](image/d41d8cd98f00b204e9800998ecf8427e.png) 惊呆了,有木有! 所以,以后在重要的网站设置密码时一定要设的复杂一点!!!
';

手机安全卫士06-手机防盗之自定义对话框

最后更新于:2022-04-01 16:16:26

### 修改主界面的titleBar 可以在系统的AndroidManifest.xml文件中修改相应的配置来改变主界面的theme(设置为无titleBar样式) ### 当前主界面的样式为: ![](image/d41d8cd98f00b204e9800998ecf8427e.png) ~~~ <activity android:name="com.liuhao.mobilesafe.ui.MainActivity" android:theme="@android:style/Theme.NoTitleBar" android:label="@string/main_screen"> </activity> ~~~ ### 设置后样式为: ![](image/d41d8cd98f00b204e9800998ecf8427e.png) ### 添加自定义的title,直接在主界面布局的最上方添加一个,其中添加相应的文字,如下: ~~~ <LinearLayout android:layout_width="match_parent" android:layout_height="40dip" android:background="@drawable/title_background" android:gravity="center_horizontal|center_vertical" android:orientation="vertical" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/textcolor" android:textSize="22sp" android:text="山寨手机卫士" /> </LinearLayout> ~~~ ### 其中又添加了一个title_background的背景: ~~~ <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <!-- 边框 --> <stroke android:width="0.5dip" android:color="#ff505050" /> <!-- 指定边角 --> <corners android:radius="2dip" /> <!-- 渐变色 --> <gradient android:startColor="#ff505050" android:centerColor="#ff383030" android:endColor="#ff282828"/> </shape> ~~~ ### 添加之后效果: ![](image/d41d8cd98f00b204e9800998ecf8427e.png) ### 从主界面点击激活图切换到活手机防盗界面 ~~~ @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Log.i(TAG, "点击的位置" + position); switch(position){ case 0 : Log.i(TAG, "进入手机防盗"); Intent lostIntent = new Intent(MainActivity.this, LostProtectedActivity.class); startActivity(lostIntent); break; } } ~~~ ### 图标隐藏后,用户可以通过在拨号界面拨打某个号码进入手机防盗界面(知识点:broadcast) ### 要获取系统发送的广播 - CallPhoneReceiver:广播接收者中,接收后进行相应的处理 - 配置系统receiver AndroidManifest.xml: ~~~ <receiver android:name="com.liuhao.mobilesafe.receiver.CallPhoneReceiver" > <intent-filter android:priority="1000"> <action android:name="android.intent.action.NEW_OUTGOING_CALL"/> </intent-filter> </receiver> ~~~ - 配置权限:android.permission.PROCESS_OUTGOING_CALLS,重新设置外拨电话的路径 ~~~ <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/> ~~~ ### 异常处理: 未能激活目标Activity,程序异常终止。 > Unable to start receiver com.liuhao.mobilesafe.receiver.CallPhoneReceiver: android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want? 在一个Activity栈外调用startActivity()操作,必须为Intent显示的指定FLAG_ACTIVITY_NEW_TASK标志。 分析:我们是在一个广播接收者中激活一个Activity,Activity是运行在任务栈中的,而广播接收者则不在任务栈中。因此,若在一个广播接收者或者一个service中激活一个Activity必须指定FLAG_ACTIVITY_NEW_TASK标志,指定要激活的Activity在自己的任务栈中运行。 ~~~ <pre name="code" class="java">lostIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) ~~~ - CallPhoneReceiver.java 完整的 ~~~ package com.liuhao.mobilesafe.receiver; import com.liuhao.mobilesafe.ui.LostProtectedActivity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; public class CallPhoneReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String number = getResultData(); if("20122012".equals(number)){ Intent lostIntent = new Intent(context, LostProtectedActivity.class); // 指定要激活的Activity在自己的任务栈中运行 lostIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(lostIntent); // 终止这个拨号 // 不能通过abortBroadcast()终止 setResultData(null); } } } ~~~ ### 手机防盗界面 1. 第一次进入时弹出对话框,让用户设置密码 1. 设置完毕再次进入时弹出对话框,输入密码才能进入 ### 实现自定义对话框 - style.xml 其中实现了一个自定义对话框框架 ~~~ <?xml version="1.0" encoding="utf-8"?> <resources> <style name="MyDialog" parent="@android:style/Theme.Dialog"> <item name="android:windowBackground">@drawable/title_background</item> <item name="android:windowNoTitle">true</item> </style> </resources> ~~~ - first_entry_dialog.xml 自定义对话框中的布局内容 ~~~ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="300dip" android:layout_height="280dip" android:gravity="center_horizontal" android:orientation="vertical" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="设置密码" android:textSize="24sp" /> <LinearLayout android:layout_width="300dip" android:layout_height="180dip" android:background="#ffc8c8c8" android:orientation="vertical" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="设置手机防盗的密码" android:textColor="#ff000000" /> <EditText android:id="@+id/et_first_entry_pwd" android:layout_width="300dip" android:layout_height="wrap_content" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="再次输入密码" android:textColor="#ff000000" /> <EditText android:id="@+id/et_first_entry_pwd_confirm" android:layout_width="300dip" android:layout_height="wrap_content" /> </LinearLayout> <LinearLayout android:layout_width="300dip" android:layout_height="50dip" android:gravity="center" android:orientation="horizontal" > <Button android:layout_width="140dip" android:layout_height="40dip" android:background="@drawable/button_background" android:text="确定" android:textColor="#ffffffff" /> <Button android:layout_width="140dip" android:layout_height="40dip" android:layout_marginLeft="3dip" android:background="@drawable/button_background" android:text="取消" /> </LinearLayout> </LinearLayout> ~~~ - button_background.xml 按钮的背景 ~~~ <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <!-- 边框 --> <stroke android:width="0.5dip" android:color="#ff107048" /> <!-- 指定边角 --> <corners android:radius="2dip" /> <!-- 渐变色 --> <gradient android:centerColor="#ff107048" android:endColor="#ff085830" android:startColor="#ff109860" /> </shape> ~~~ - LostProtectedActivity.java ~~~ package com.liuhao.mobilesafe.ui; import com.liuhao.mobilesafe.R; import android.app.Activity; import android.app.Dialog; import android.content.Context; import android.content.SharedPreferences; import android.os.Bundle; import android.util.Log; public class LostProtectedActivity extends Activity { private static final String TAG = "LostProtectedActivity"; private SharedPreferences sp; private Dialog dialog; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); sp = getSharedPreferences("config", Context.MODE_PRIVATE); // 判读用户是否已经设置了密码 if (isPwdSetup()) { Log.i(TAG, "设置了密码,弹出输入密码的对话框"); } else { Log.i(TAG, "未设置密码,弹出设置密码对话框"); showFirstEntryDialog(); } } /** * 第一次进入程序时弹出的设置密码的对话框 自定义对话框样式 */ private void showFirstEntryDialog() { dialog = new Dialog(this, R.style.MyDialog); dialog.setContentView(R.layout.first_entry_dialog);// 设置要显示的内容 dialog.show(); } /** * 检查sharedpreference中是否有密码的设置 * * @return */ private boolean isPwdSetup() { String password = sp.getString("password", null); if (password == null) { return false; } else { if ("".equals(password)) { return false; } else { return true; } } } } ~~~ ### 最终效果: ![](image/d41d8cd98f00b204e9800998ecf8427e.png)
';

知识点:SharedPreferences

最后更新于:2022-04-01 16:16:23

### Ⅰ. 简介 很多时候我们开发的软件需要向用户提供软件参数设置功能,例如我们常用的 QQ,用户可以设置是否允许陌生人添加自己为好友。对于软件配置参数的保存,如果是window软件通常我们会采用ini文件进行保存,如果是j2se应用,我们会采用properties属性文件或者xml进行保存。如果是Android应用,我们最适合采用什么方式保存软件配置参数呢?Android 平台给我们提供了一个SharedPreferences类,它是一个轻量级的存储类,特别适合用于保存软件配置参数。SharedPreferences 类似过去Windows系统上的ini配置文件,但是它分为多种权限,可以全局共享访问,最终是以xml方式来保存,整体效率来看不是特别的高,对于常规的轻量级而言比SQLite要好不少,如果真的存储量不大可以考虑自己定义文件格式。xml 处理时Dalvik会通过自带底层的本地XML Parser解析,比如XMLpull方式,这样对于内存资源占用比较好。 使用 SharedPreferences保存数据,其背后是用xml文件存放数据,文件存放在/data/data/<package name>/shared_prefs目录下(需要用户的root权限) SharedPreferences的使用非常简单,能够轻松的存放数据和读取数据。SharedPreferences只能保存简单类型的数据,例如,String、int等。一般会将复杂类型的数据转换成Base64编码,然后将转换后的数据以字符串的形式保存在 XML文件中,再用SharedPreferences保存。这种方式应该是用起来最简单的Android读写外部数据的方法了。他的用法基本上和 J2SE(java.util.prefs.Preferences)中的用法一样,以一种简单、透明的方式来保存一些用户个性化设置的字体、颜色、位置等参数信息。一般的应用程序都会提供“设置”或者“首选项”的这样的界面,那么这些设置最后就可以 通过Preferences来保存,而程序员不需要知道它到底以什么形式保存的,保存在了什么地方。当然,如果你愿意保存其他的东西,也没有什么限制。只 是在性能上不知道会有什么问题。 ### Ⅱ. 使用 使用SharedPreferences保存key-value对的步骤如下: (1)使用Activity类的getSharedPreferences方法获得SharedPreferences对象,其中存储key-value的文件的名称由getSharedPreferences方法的第一个参数指定。 (2)使用SharedPreferences接口的edit获得SharedPreferences.Editor对象。 (3)通过SharedPreferences.Editor接口的putXxx方法保存key-value对。其中Xxx表示不同的数据类型。例如:字符串类型的value需要用putString方法。 (4)通过SharedPreferences.Editor接口的commit方法保存key-value对。commit方法相当于数据库事务中的提交(commit)操作。 具体代码的书写流程为: #### A、存放数据信息 ~~~ //1、打开Preferences,名称为setting,如果存在则打开它,否则创建新的Preferences SharedPreferences settings = getSharedPreferences(“setting”, 0); //2、让setting处于编辑状态 SharedPreferences.Editor editor = settings.edit(); //3、存放数据 editor.putString(“name”,”ATAAW”); editor.putString(“URL”,”ATAAW.COM”); //4、完成提交 editor.commit(); ~~~ 因为SharedPreferences背后是使用xml文件保存数据,getSharedPreferences(name,mode)方法的第一个参数用于指定该文件的名称,名称不用带后缀,后缀会由Android自动加上。方法的第二个参数指定文件的操作模式,共有四种操作模式。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a9565440c.png) - Activity.MODE_PRIVATE,//默认操作模式,代表该文件是私有数据,只能被应用本身访问,在该模式下,写入的内容会覆盖原文件的内容,如果想把新写入的内容追加到原文件中,可以使用Activity.MODE_APPEND  - Activity.MODE_WORLD_READABLE,//表示当前文件可以被其他应用读取,  - Activity.MODE_WORLD_WRITEABLE,//表示当前文件可以被其他应用写入;                                      //如果希望文件被其他应用读和写,可以传入:Activity.MODE_WORLD_READABLE+Activity.MODE_WORLD_WRITEABLE - Activity.MODE_APPEND//该模式会检查文件是否存在,存在就往文件追加内容,否则就创建新文件  如果希望SharedPreferences背后使用的xml文件能被其他应用读和写,可以指定Context.MODE_WORLD_READABLE和Context.MODE_WORLD_WRITEABLE权限。 另外Activity还提供了另一个getPreferences(mode)方法操作SharedPreferences,这个方法默认使用当前类不带包名的类名作为文件的名称。 #### B、读取数据信息 ~~~ //1、获取Preferences SharedPreferences settings = getSharedPreferences(“setting”, 0); //2、取出数据 String name = settings.getString(“name”,”默认值”);//getString()第二个参数为缺省值,如果preference中不存在该key,将返回缺省值 String url = setting.getString(“URL”,”default”); ~~~ //以上就是Android中SharedPreferences的使用方法,其中创建的Preferences文件存放位置可以在Eclipse中查看: DDMS->File Explorer /<package name>/shared_prefs/setting.xml ### 运行机制 #### #### Context内部实现 getSharedPreferences()方法本身是Context这个接口中定义的一个抽象方法,由ContextImpl类负责实现。 1 、调用getSharedPreferences()获取对应的的文件,该函数实现功能如下: ~~~ //Context类静态数据集合,以键值对保存了所有读取该xml文件后所形成的数据集合 private static final HashMap<File, SharedPreferencesImpl> sSharedPrefs = new HashMap<File, SharedPreferencesImpl>(); @Override public SharedPreferences getSharedPreferences(String name, int mode){ //其所对应的SharedPreferencesImpl对象 ,该对象已一个HashMap集合保存了我们对该文件序列化结果 SharedPreferencesImpl sp; File f = getSharedPrefsFile(name); //该包下是否存在对应的文件,不存在就新建一个 synchronized (sSharedPrefs) { //是否已经读取过该文件,是就直接返回该SharedPreferences对象 sp = sSharedPrefs.get(f); if (sp != null && !sp.hasFileChanged()) { //Log.i(TAG, "Returning existing prefs " + name + ": " + sp); return sp; } } //以下为序列化该xml文件,同时将数据写到map集合中 Map map = null; if (f.exists() && f.canRead()) { try { str = new FileInputStream(f); map = XmlUtils.readMapXml(str); str.close(); } ... } synchronized (sSharedPrefs) { if (sp != null) { //Log.i(TAG, "Updating existing prefs " + name + " " + sp + ": " + map); sp.replace(map); //更新数据集合 } else { sp = sSharedPrefs.get(f); if (sp == null) { //新建一个SharedPreferencesImpl对象,并且设置其相关属性 sp = new SharedPreferencesImpl(f, mode, map); sSharedPrefs.put(f, sp); } } return sp; } } ~~~ 2、 SharedPreferences 不过是个接口,它定义了一些操作xml文件的方法,其真正实现类为SharedPreferencesImpl ,该类是ContextIml的内部类,该类如下: ~~~ //这种形式我们在分析Context ContextIml时接触过 //SharedPreferences只是一种接口,其真正实现类是SharedPreferencesImpl类 private static final class SharedPreferencesImpl implements SharedPreferences{ private Map mMap; //保存了该文件序列化结果后的操作, 键值对形式 //通过key值获取对应的value值 public String getString(String key, String defValue) { synchronized (this) { String v = (String)mMap.get(key); return v != null ? v : defValue; } } ... //获得该SharedPreferencesImpl对象对应的Edito类,对数据进行操作 public final class EditorImpl implements Editor { private final Map<String, Object> mModified = Maps.newHashMap(); //保存了对键值变化的集合 } } ~~~ 参考: http://blog.csdn.net/wxyyxc1992/article/details/17222841
';

知识点:动态设置布局LayoutInflater

最后更新于:2022-04-01 16:16:21

### 一.作用: LayoutInflater作用是将layout的xml布局文件实例化为View类对象,LayoutInflater 的作用类似于 findViewById(),不同点是LayoutInflater是用来找layout文件夹下的xml布局文件,并且实例化!而 findViewById()是找具体某一个xml下的具体 widget控件(如:Button,TextView等)。 ### 二.获得 LayoutInflater 实例的三种方式 1.LayoutInflater inflater = getLayoutInflater(); //调用Activity的getLayoutInflater() 2.LayoutInflater inflater = LayoutInflater.from(this); 3.LayoutInflater inflater = (LayoutInflater)Context.getSystemService(LAYOUT_INFLATER_SERVICE); 其实,这三种方式本质是相同的,从源码中可以看出: **getLayoutInflater():** Activity 的 getLayoutInflater() 方法是调用 PhoneWindow 的getLayoutInflater()方法,看一下该源代码: ~~~ public PhoneWindow(Context context) { super(context); mLayoutInflater = LayoutInflater.from(context); } ~~~ 可以看出它其实是调用 LayoutInflater.from(context)。 LayoutInflater.from(context): ~~~ public static LayoutInflater from(Context context) { LayoutInflater LayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); if (LayoutInflater == null) { throw new AssertionError("LayoutInflater not found."); } return LayoutInflater; } ~~~ 可以看出它其实调用 context.getSystemService()。 结论:所以这三种方式最终本质是都是调用的Context.getSystemService()。 ### 三.实例化LayoutInflater之后,就要将layout的xml布局文件实例化为View类对象。 1. ~~~ LayoutInflater inflater = getLayoutInflater(); View view=inflater.inflate(R.layout.ID, null); ~~~ 2. ~~~ LayoutInflater inflater = LayoutInflater.from(this); View view=inflater.inflate(R.layout.ID, null); ~~~ 3. ~~~ LayoutInflater inflater = (LayoutInflater)Context.getSystemService(LAYOUT_INFLATER_SERVICE); View view=inflater.inflate(R.layout.ID, null); ~~~ ### 四.inflate 方法 通过 sdk 的 api 文档,可以知道该方法有以下几种过载形式,返回值均是 View 对象,如下: - public View inflate (int resource, ViewGroup root)  (常用) - inflate()方法一般接收两个参数,第一个参数就是要加载的布局id,第二个参数是指给该布局的外部再嵌套一层父布局,如果不需要就直接传null。这样就成功成功创建了一个布局的实例,之后再将它添加到指定的位置就可以显示出来了。 - public View inflate (XmlPullParser parser, ViewGroup root)  - public View inflate (XmlPullParser parser, ViewGroup root, boolean attachToRoot)  - public View inflate (int resource, ViewGroup root, boolean attachToRoot) 示意代码: ~~~ <pre name="code" class="java">LayoutInflater inflater = (LayoutInflater)getSystemService(LAYOUT_INFLATER_SERVICE); View view = inflater.inflate(R.layout.custom, (ViewGroup)findViewById(R.id.test)); //EditText editText = (EditText)findViewById(R.id.content);// error EditText editText = (EditText)view.findViewById(R.id.content); ~~~ 对于上面代码,指定了第二个参数 ViewGroup root,当然你也可以设置为 null 值。 ### 实例: 下面我们就通过一个非常简单的小例子,来更加直观地看一下LayoutInflater的用法。比如说当前有一个项目,其中MainActivity对应的布局文件叫做activity_main.xml,代码如下所示: ~~~ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/main_layout" android:layout_width="match_parent" android:layout_height="match_parent" > </LinearLayout> ~~~ 这个布局文件的内容非常简单,只有一个空的LinearLayout,里面什么控件都没有,因此界面上应该不会显示任何东西。 那么接下来我们再定义一个布局文件,给它取名为button_layout.xml,代码如下所示: ~~~ <Button xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" > </Button> ~~~ 这个布局文件也非常简单,只有一个Button按钮而已。 现在我们要想办法,如何通过LayoutInflater来将button_layout这个布局添加到主布局文件的LinearLayout中。根据刚刚介绍的用法,修改MainActivity中的代码,如下所示: ~~~ public class MainActivity extends Activity { private LinearLayout mainLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mainLayout = (LinearLayout) findViewById(R.id.main_layout); LayoutInflater layoutInflater = LayoutInflater.from(this); View buttonLayout = layoutInflater.inflate(R.layout.button_layout, null); mainLayout.addView(buttonLayout); } } ~~~ 可以看到,这里先是获取到了LayoutInflater的实例,然后调用它的inflate()方法来加载button_layout这个布局,最后调用LinearLayout的addView()方法将它添加到LinearLayout中。 现在可以运行一下程序,结果如下图所示: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a956452d4.jpg) Button 在界面上显示出来了!说明我们确实是借助LayoutInflater成功将button_layout这个布局添加到LinearLayout中了。 LayoutInflater技术广泛应用于需要动态添加View的时候,比如在ScrollView和ListView中,经常都可以看到 LayoutInflater的身影。 参考: http://www.cnblogs.com/merryjd/archive/2013/01/08/2851092.html http://blog.csdn.net/guolin_blog/article/details/12921889
';

手机安全卫士05_2:程序主界面,为每个条目添加事件

最后更新于:2022-04-01 16:16:19

### 为每个条目添加点击事件监听器 ~~~ gv_main.setOnItemClickListener(this); ~~~ 需要当前Activity实现OnItemClickListener接口,同时实现public void onItemClick(AdapterView<?> parent, View view, int position,long id)方法 ~~~ /** * 当gridview的条目被点击的时候对应的回调 * parent : gridView * view : 当前被点击条目的 LinearLayout * position : 点击条目对应的位置 * id : 代表的行号 */ @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Log.i(TAG, "点击的位置" + position); switch(position){ case 0 : Log.i(TAG, "进入手机防盗"); break; } } ~~~ ### 设置长按“手机防盗”时,弹出编辑窗口(知识点:SharedPreferences) ~~~ gv_main.setOnItemLongClickListener(new OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> parent, final View view, int position, long id) { if(position == 0){ Builder builder = new Builder(MainActivity.this); builder.setTitle("设置"); builder.setMessage("请输入要更改的内容"); final EditText et = new EditText(MainActivity.this); et.setHint("请输入内容,长度在0-8之间"); builder.setView(et); builder.setPositiveButton("确定", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { String name = et.getText().toString();//获取输入 if("".equals(name)){ Toast.makeText(getApplicationContext(), "内容不能为空", Toast.LENGTH_LONG).show(); return; }else if(name.length() > 8){ Toast.makeText(getApplicationContext(), "输入过长", Toast.LENGTH_LONG).show(); return; }else{ Editor editor = sp.edit(); editor.putString("lost_name", name); // 完成数据的提交 editor.commit(); TextView tv = (TextView) view.findViewById(R.id.tv_main_name); tv.setText(name); } } }); builder.setNegativeButton("取消", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }); builder.create().show(); } return false; } }); } ~~~ 长按后弹出界面效果及设置后的效果: [![Screenshot_2014-11-04-15-07-55](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a95623df9.jpg "Screenshot_2014-11-04-15-07-55")](http://img.blog.csdn.net/20141104152008738)    [![Screenshot_2014-11-04-15-08-05](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a95634fb8.jpg "Screenshot_2014-11-04-15-08-05")](http://img.blog.csdn.net/20141104152010127)
';

手机安全卫士05_1:程序主界面

最后更新于:2022-04-01 16:16:16

### 主界面布局(知识点:GridView) #### mainscreen.xml: ~~~ <?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:background="@color/background" android:orientation="vertical" > <GridView android:id="@+id/gv_main" android:layout_width="match_parent" android:layout_height="match_parent" android:horizontalSpacing="10dip" android:verticalSpacing="20dip" android:numColumns="3" > </GridView> </LinearLayout> ~~~ android:background="@color/background"--设置背景颜色,此处采用颜色资源文件(/mobilesafe/res/values/color.xml) #### color.xml: ~~~ <?xml version="1.0" encoding="utf-8"?> <resources> <color name="background">#ff404040</color> <color name="textcolor">#ffd0d0d0</color> </resources> ~~~ #### GridView : android:numColumns="3"-----列数设置为3列 android:horizontalSpacing="10dip"-----两列之间的边距 android:verticalSpacing="20dip"-----两行之间的边距 ### 数据适配MainUIAdapter(知识点:[LayoutInflater](http://blog.csdn.net/bruce_6/article/details/40785969),BaseAdapter ) MainUIAdapter : ~~~ package com.liuhao.mobilesafe.adapter; import com.liuhao.mobilesafe.R; import android.content.Context; import android.content.SharedPreferences; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; public class MainUIAdapter extends BaseAdapter { private static final String TAG = "MainUIAdapter"; private Context context;//用于接收传递过来的Context对象 private LayoutInflater inflater; private static ImageView iv_icon; private static TextView tv_name; private SharedPreferences sp; public MainUIAdapter(Context context) { this.context = context; inflater = LayoutInflater.from(context); sp = context.getSharedPreferences("config", Context.MODE_PRIVATE); } // 数据源,子条目的文本内容 private static String[] names = { "手机防盗", "通讯卫士", "软件管理", "任务管理", "上网管理", "手机杀毒", "系统优化", "高级工具", "设置中心" }; // 数据源,子条目的对应图片 private static int[] icons = { R.drawable.widget05, R.drawable.widget02, R.drawable.widget01, R.drawable.widget07, R.drawable.widget05, R.drawable.widget04, R.drawable.widget06, R.drawable.widget03, R.drawable.widget08 }; /** * How many items are in the data set represented by this Adapter. * 在此适配器中所代表的数据集中的条目数 */ @Override public int getCount() { return names.length; } /** * Get the data item associated with the specified position in the data set. * 获取数据集中与指定索引对应的数据项 */ @Override public Object getItem(int position) { return position; } /** * Get the row id associated with the specified position in the list. * 取在列表中与指定索引对应的行id */ @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { //getView()方法被调用了多少次? //9次? //GridView 控件bug // 使用静态变量引用来减少内存中申请的引用的个数 Log.i(TAG, "getView" + position); // 得到布局文件对应的View对象 // inflate()方法一般接收两个参数,第一个参数就是要加载的布局id, // 第二个参数是指给该布局的外部再嵌套一层父布局,如果不需要就直接传null。 View view = inflater.inflate(R.layout.mainscreen_item, null); iv_icon = (ImageView) view.findViewById(R.id.iv_main_icon); tv_name = (TextView) view.findViewById(R.id.tv_main_name); iv_icon.setImageResource(icons[position]); tv_name.setText(names[position]); if(position == 0){ // 从SharedPreferences获取条目的文本内容 String name = sp.getString("lost_name", null); if(!"".equals(name) && null != name){ tv_name.setText(name); } } return view; } } ~~~ ### 子条目的布局: mainscreen_item.xml: ~~~ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="100dip" android:layout_height="100dip" android:orientation="vertical" android:gravity="center_horizontal" android:background="@drawable/item_background" > <ImageView android:id="@+id/iv_main_icon" android:layout_width="60dip" android:layout_height="60dip" android:src="@drawable/ic_launcher" android:scaleType="fitXY" /> <TextView android:id="@+id/tv_main_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="18sp" android:textColor="@color/textcolor" android:text="程序功能" /> </LinearLayout> ~~~ ### 为每个条目添加背景图(知识点:android:shape) item_background.xml: ~~~ <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <!-- 边框 --> <stroke android:width="1dip" android:color="#ff202020" /> <!-- 指定边角 --> <corners android:radius="2dip" /> <!-- 指定固定颜色 --> <solid android:color="@color/background" /> <!-- 渐变色 --> <gradient /> </shape> ~~~ MainActivity中使用 ~~~ gv_main = (GridView) this.findViewById(R.id.gv_main); adapter = new MainUIAdapter(this); gv_main.setAdapter(adapter); ~~~ 效果: [![Screenshot_2014-11-04-14-46-21](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a9560ecdd.jpg "Screenshot_2014-11-04-14-46-21")](http://img.blog.csdn.net/20141104150011421) ### GridView中的getView()方法被调用了多少次?9次(9个条目)? 每个条目会多次调用getView()方法; 原因: GridView的item的layout中android:layout_height定义为wrap_content , 绘制item高度时系统并不知道item应该绘制多高,它会先取一条来试探以确定item绘制的具体高度,这样就导致多调用了一次getView方法。在滑动、长按等GridView需要重绘的情况下,getView的调用次数可能会不止多一次。 解决方法1: 由于多次调用getView()方法,每次都会重新生成一个iv_icon ,tv_name控件对象,从而产生多个对象实例,造成空间浪费。因此,一个简单的优化方法就是,将二者设置为类静态变量: private static ImageView iv_icon; private static TextView tv_name; 使用静态变量引用来减少内存中申请的引用的个数,这样可以在一定程度上提高效率。 后续还会有其他优化方法。
';

知识点:Adapter适配器

最后更新于:2022-04-01 16:16:14

### 1.概念 Adapter是连接后端数据和前端显示的适配器接口,是数据和UI(View)之间一个重要的纽带。在常见的 View(ListView,GridView)等地方都需要用到Adapter。如下图直观的表达了Data、Adapter、View三者的关系: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a95479fa9.jpg) Android中所有的Adapter一览: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a9548eda5.png) 由图可以看到在Android中与Adapter有关的所有接口、类的完整层级图。在我们使用过程中可以根据自己的需求实现接口或者继承类进行一定的扩展。比较常用的有 BaseAdapter,SimpleAdapter,ArrayAdapter,SimpleCursorAdapter等。 - BaseAdapter是一个抽象类,继承它需要实现较多的方法,所以也就具有较高的灵活性; - ArrayAdapter支持泛型操作,最为简单,只能展示一行字。 - SimpleAdapter有最好的扩充性,可以自定义出各种效果。 - SimpleCursorAdapter可以适用于简单的纯文字型ListView,它需要Cursor的字段和UI的id对应起来。如需要实现更复杂的UI也可以重写其他方法。可以认为是SimpleAdapter对数据库的简单结合,可以方便地把数据库的内容以列表的形式展示出来。 ### 2.应用案例 ### 1)ArrayAdapter 列表的显示需要三个元素: > a.ListVeiw 用来展示列表的View。 > b.适配器 用来把数据映射到ListView上的中介。 > c.数据    具体的将被映射的字符串,图片,或者基本组件。 #### 案例一 ~~~ public class ArrayAdapterActivity extends ListActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //列表项的数据 String[] strs = {"1","2","3","4","5"}; ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,android.R.layout.simple_expandable_list_item_1,strs); setListAdapter(adapter); } }  ~~~ #### 案例二 ~~~ public class MyListView extends Activity { private ListView listView; //private List<String> data = new ArrayList<String>(); @Override public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); listView = new ListView(this); listView.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_expandable_list_item_1,getData())); setContentView(listView); } private List<String> getData(){ List<String> data = new ArrayList<String>(); data.add("测试数据1"); data.add("测试数据2"); data.add("测试数据3"); data.add("测试数据4"); return data; } }  ~~~ 上面代码使用了ArrayAdapter(Context context, int textViewResourceId, List<T> objects)来装配数据,要装配这些数据就需要一个连接ListView视图对象和数组数据的适配器来两者的适配工作,ArrayAdapter的构造需要三个参数,依次为this,布局文件(注意这里的布局文件描述的是列表的每一行的布局,android.R.layout.simple_list_item_1是系统定义好的布局文件只显示一行文字,数据源(一个List集合)。同时用setAdapter()完成适配的最后工作。效果图如下: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a954a3132.png) ### 2)SimpleAdapter simpleAdapter的扩展性最好,可以定义各种各样的布局出来,可以放上ImageView(图片),还可以放上Button(按钮),CheckBox(复选框)等等。下面的代码都直接继承了ListActivity,ListActivity和普通的Activity没有太大的差别,不同就是对显示ListView做了许多优化,方面显示而已。 #### 案例一 simple.xml ~~~ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <ImageView android:id="@+id/img" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="5dp" /> <TextView android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#ffffff" android:textSize="20sp" /> </LinearLayout> ~~~ ~~~ public class SimpleAdapterActivity extends ListActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); SimpleAdapter adapter = new SimpleAdapter(this, getData(), R.layout.simple, new String[] { "title", "img" }, new int[] { R.id.title, R.id.img }); setListAdapter(adapter); } private List<Map<String, Object>> getData() { //map.put(参数名字,参数值) List<Map<String, Object>> list = new ArrayList<Map<String, Object>>(); Map<String, Object> map = new HashMap<String, Object>(); map.put("title", "摩托罗拉"); map.put("img", R.drawable.icon); list.add(map); map = new HashMap<String, Object>(); map.put("title", "诺基亚"); map.put("img", R.drawable.icon); list.add(map); map = new HashMap<String, Object>(); map.put("title", "三星"); map.put("img", R.drawable.icon); list.add(map); return list; } } ~~~ #### 案例二 下面的程序是实现一个带有图片的类表。首先需要定义好一个用来显示每一个列内容的xml, vlist.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="horizontal" > <ImageView android:id="@+id/img" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="5px" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" > <TextView android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#FFFFFFFF" android:textSize="22px" /> <TextView android:id="@+id/info" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#FFFFFFFF" android:textSize="13px" /> </LinearLayout> </LinearLayout> ~~~ ~~~ public class MyListView3 extends ListActivity { // private List<String> data = new ArrayList<String>(); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); SimpleAdapter adapter = new SimpleAdapter(this,getData(),R.layout.vlist, new String[]{"title","info","img"}, new int[]{R.id.title,R.id.info,R.id.img}); setListAdapter(adapter); } private List<Map<String, Object>> getData() { List<Map<String, Object>> list = new ArrayList<Map<String, Object>>(); Map<String, Object> map = new HashMap<String, Object>(); map.put("title", "G1"); map.put("info", "google 1"); map.put("img", R.drawable.i1); list.add(map); map = new HashMap<String, Object>(); map.put("title", "G2"); map.put("info", "google 2"); map.put("img", R.drawable.i2); list.add(map); map = new HashMap<String, Object>(); map.put("title", "G3"); map.put("info", "google 3"); map.put("img", R.drawable.i3); list.add(map); return list; } } ~~~ **使用simpleAdapter的数据用一般都是HashMap构成的List,list的每一节对应ListView的每一行。**HashMap 的每个键值数据映射到布局文件中对应id的组件上。因为系统没有对应的布局文件可用,我们可以自己定义一个布局vlist.xml。下面做适配,new一个SimpleAdapter参数一次是:this,布局文件(vlist.xml),HashMap的 title 和 info,img。布局文件的组件id,title,info,img。布局文件的各组件分别映射到HashMap的各元素上,完成适配。 运行效果如下图: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a954b5916.png) ###3)SimpleCursorAdapter ~~~ public class SimpleCursorAdapterActivity extends ListActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //获得一个指向系统通讯录数据库的Cursor对象获得数据来源 Cursor cur = getContentResolver().query(People.CONTENT_URI, null, null, null, null); startManagingCursor(cur); //实例化列表适配器 ListAdapter adapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_1, cur, new String[] {People.NAME}, new int[] {android.R.id.text1}); setListAdapter(adapter); } } ~~~ **一定要以数据库作为数据源的时候,才能使用SimpleCursorAdapter,**这里特别需要注意的一点是:不要忘了在AndroidManifest.xml文件中加入权限 ~~~ <uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission> ~~~ 效果如下: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a954c7288.jpg) ###4)BaseAdapter 有时候,列表不光会用来做显示用,我们同样可以在在上面添加按钮。添加按钮首先要写一个有按钮的xml文件,然后自然会想到用上面的方法定义一个适配器,然后将数据映射到布局文件上。但是事实并非这样,因为按钮是无法映射的,即使你成功的用布局文件显示出了按钮也无法添加按钮的响应,这时就要研究一下ListView是如何现实的了,而且必须要重写一个类继承BaseAdapter。下面的示例将显示一个按钮和一个图片,两行字如果单击按钮将删除此按钮的所在行。并告诉你ListView究竟是如何工作的。 vlist2.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="horizontal" > <ImageView android:id="@+id/img" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="5px" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" > <TextView android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#FFFFFFFF" android:textSize="22px" /> <TextView android:id="@+id/info" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#FFFFFFFF" android:textSize="13px" /> </LinearLayout> <Button android:id="@+id/view_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|right" android:text="@string/s_view_btn" /> </LinearLayout> ~~~ ~~~ public class MyListView4 extends ListActivity { private List<Map<String, Object>> mData; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mData = getData(); MyAdapter adapter = new MyAdapter(this); setListAdapter(adapter); } private List<Map<String, Object>> getData() { List<Map<String, Object>> list = new ArrayList<Map<String, Object>>(); Map<String, Object> map = new HashMap<String, Object>(); map.put("title", "G1"); map.put("info", "google 1"); map.put("img", R.drawable.i1); list.add(map); map = new HashMap<String, Object>(); map.put("title", "G2"); map.put("info", "google 2"); map.put("img", R.drawable.i2); list.add(map); map = new HashMap<String, Object>(); map.put("title", "G3"); map.put("info", "google 3"); map.put("img", R.drawable.i3); list.add(map); return list; } // ListView 中某项被选中后的逻辑 @Override protected void onListItemClick(ListView l, View v, int position, long id) { Log.v("MyListView4-click", (String)mData.get(position).get("title")); } /** * listview中点击按键弹出对话框 */ public void showInfo(){ new AlertDialog.Builder(this) .setTitle("我的listview") .setMessage("介绍...") .setPositiveButton("确定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }) .show(); } ~~~ ~~~ public final class ViewHolder{ public ImageView img; public TextView title; public TextView info; public Button viewBtn; } ~~~ ~~~ public class MyAdapter extends BaseAdapter{ private LayoutInflater mInflater; public MyAdapter(Context context){ this.mInflater = LayoutInflater.from(context); } @Override public int getCount() { // TODO Auto-generated method stub return mData.size(); } @Override public Object getItem(int arg0) { // TODO Auto-generated method stub return null; } @Override public long getItemId(int arg0) { // TODO Auto-generated method stub return 0; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null; if (convertView == null) { holder=new ViewHolder(); convertView = mInflater.inflate(R.layout.vlist2, null); holder.img = (ImageView)convertView.findViewById(R.id.img); holder.title = (TextView)convertView.findViewById(R.id.title); holder.info = (TextView)convertView.findViewById(R.id.info); holder.viewBtn = (Button)convertView.findViewById(R.id.view_btn); convertView.setTag(holder); }else { holder = (ViewHolder)convertView.getTag(); } holder.img.setBackgroundResource((Integer)mData.get(position).get("img")); holder.title.setText((String)mData.get(position).get("title")); holder.info.setText((String)mData.get(position).get("info")); holder.viewBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { showInfo(); } }); return convertView; } } } ~~~ 下面将对上述代码,做详细的解释,listView在开始绘制的时候,系统首先调用getCount()函数,根据他的返回值得到listView 的长度(这也是为什么在开始的第一张图特别的标出列表长度),然后根据这个长度,调用getView()逐一绘制每一行。如果你的getCount()返回值是0的话,列表将不显示同样return 1,就只显示一行。 系统显示列表时,首先实例化一个适配器(这里将实例化自定义的适配器)。当手动完成适配时,必须手动映射数据,这需要重写getView()方法。系统在绘制列表的每一行的时候将调用此方法。getView()有三个参数,position表示将显示的是第几行,covertView是从布局文件中inflate来的布局。我们用LayoutInflater的方法将定义好的vlist2.xml文件提取成View实例用来显示。然后将xml文件中的各个组件实例化(简单的findViewById()方法)。这样便可以将数据对应到各个组件上了。但是按钮为了响应点击事件,需要为它添加点击监听器,这样就能捕获点击事件。至此一个自定义的listView就完成了,现在让我们回过头从新审视这个过程。系统要绘制ListView了,他首先获得要绘制的这个列表的长度,然后开始绘制第一行,怎么绘制呢?调用getView()函数。在这个函数里面首先获得一个View(实际上是一个 ViewGroup),然后再实例并设置各个组件,显示之。好了,绘制完这一行了。那再绘制下一行,直到绘完为止。在实际的运行过程中会发现 listView的每一行没有焦点了,这是因为Button抢夺了listView的焦点,只要布局文件中将Button设置为没有焦点就OK了。 效果如下: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a955ddd94.png) ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a955ef67f.png) 参考: http://www.cnblogs.com/devinzhang/archive/2012/01/20/2328334.html
';

知识点:Intent

最后更新于:2022-04-01 16:16:12

### 一. Intent的介绍 - Android中提供了Intent机制来协助应用间的交互与通讯,或者采用更准确的说法是,Intent不仅可用于应用程序之间,也可用于应用程序内部的activity, service和broadcast receiver之间的交互。 - Intent是一种运行时绑定(runtime binding)机制,它能在程序运行的过程中连接两个不同的组件。通过Intent,你的程序可以向Android表达某种请求或者意愿,Android会根据意愿的内容选择适当的组件来响应。 Intent的中文意思是“意图,意向”,在Android中提供了Intent机制来协助应用间的交互与通讯,Intent负责对应用中一次操作的动作、动作涉及数据、附加数据进行描述,Android则根据此Intent的描述,负责找到对应的组件,将 Intent传递给调用的组件,并完成组件的调用。Intent不仅可用于应用程序之间,也可用于应用程序内部的Activity/Service之间的交互。因此,可以将Intent理解为不同组件之间通信的“媒介”专门提供组件互相调用的相关信息,起着一个媒体中介的作用,专门提供组件互相调用的相关信息,实现调用者与被调用者之间的解耦。 Intent 是一个将要执行的动作的抽象的描述,一般来说是作为参数来使用,由Intent来协助完成android各个组件之间的通讯。比如说调用 startActivity()来启动一个activity,或者由broadcaseIntent()来传递给所有感兴趣的 BroadcaseReceiver, 再或者由startService()/bindservice()来启动一个后台的service。所以可以看出来,intent主要是用来启动其他的 activity 或者service,所以可以将intent理解成activity之间的粘合剂。 ### 主要用途 Intent是一种消息传递机制,它可以在应用程序内使用,也可以在应用程序间使用,主要用途分为: 1.使用类名显示的启动一个特定的Activity或Service 2.启动Activity或Service来执行一个动作的Intent,通常需要使用特定的数据,或者对特定的数据执行动作 3.广播某个事件已经发生 activity、service和broadcast receiver之间是通过Intent进行通信的,而另外一个组件Content Provider本身就是一种通信机制,不需要通过Intent。我们来看下面这个图就知道了: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a953f3470.png) 如果Activity1需要和Activity2进行联系,二者不需要直接联系,而是通过Intent作为桥梁。通俗来讲,Intnet类似于中介、媒婆的角色。 Intent最常见的一个用法是显示的(通过指定要装载的类)或隐式的(通过请求对一条数据执行某个动作)启动新的activity,在后一种情况下,动作不一定由调用应用程序中的Activty执行。 Intent也可以在系统范围内发送广播消息。应用程序可以注册一个Broadcast Receiver来监听相应这些广播的Intent。Android就是通过广播Intent来公布系统事件,比如网络连接状态或者电池电量的改变。 ### 二. Inten启动组件的方法 Intent可以启动一个Activity,也可以启动一个Service,还可以发起一个广播Broadcasts。具体方法如下: #### 对于向这三种组件发送intent有不同的机制: - 使用Context.startActivity() 或 Activity.startActivityForResult(),传入一个intent来启动一个activity。使用 Activity.setResult(),传入一个intent来从activity中返回结果。 - 将intent对象传给Context.startService()来启动一个service或者传消息给一个运行的service。将intent对象传给 Context.bindService()来绑定一个service。 - 将intent对象传给 Context.sendBroadcast(),Context.sendOrderedBroadcast(),或者Context.sendStickyBroadcast()等广播方法,则它们被传给 broadcast receiver。 <table align="center" width="400" border="1" cellpadding="2" cellspacing="0"><tbody><tr><td valign="top" width="194"><p align="center">组件名称</p></td><td valign="top" width="204"><p align="center">方法名称</p></td></tr><tr><td valign="top" width="191">Activity</td><td valign="top" width="207"><p>startActvity( ) </p><p>startActivityForResult( )</p></td></tr><tr><td valign="top" width="189">Service</td><td valign="top" width="209"><p>startService( ) </p><p>bindService( )</p></td></tr><tr><td valign="top" width="188">Broadcasts</td><td valign="top" width="211"><p>sendBroadcasts( ) </p><p>sendOrderedBroadcasts( ) </p><p>sendStickyBroadcasts( )</p></td></tr></tbody></table> ### 三. Intent的构成 Intent由以下各个组成部分: - component(组件):目的组件 - action(动作):用来表现意图的行动 - category(类别):用来表现动作的类别 - data(数据):表示与动作要操纵的数据 - type(数据类型):对于data范例的描写 - extras(扩展信息):扩展信息 - Flags(标志位):期望这个意图的运行模式 #### 1、component(组件):目的组件 指定Intent的的目标组件的类名称。通常 Android会根据Intent 中包含的其它属性的信息,比如action、data/type、category进行查找,最终找到一个与之匹配的目标组件。但是,如果 component这个属性有指定的话,将直接使用它指定的组件,而不再执行上述查找过程。指定了这个属性以后,Intent的其它所有属性都是可选的。 例如,启动第二个Activity时,我们可以这样来写: ~~~ button1.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { //创建一个意图对象 Intent intent = new Intent(); //创建组件,通过组件来响应 ComponentName component = new ComponentName(MainActivity.this, SecondActivity.class); intent.setComponent(component); startActivity(intent); } }); ~~~ 如果写的简单一点,监听事件onClick()方法里可以这样写: ~~~ Intent intent = new Intent(); //setClass函数的第一个参数是一个Context对象 //Context是一个类,Activity是Context类的子类,也就是说,所有的Activity对象,都可以向上转型为Context对象 //setClass函数的第二个参数是一个Class对象,在当前场景下,应该传入需要被启动的Activity类的class对象 intent.setClass(MainActivity.this, SecondActivity.class); startActivity(intent); ~~~ 再简单一点,可以这样写:(当然,也是最常见的写法) ~~~ Intent intent = new Intent(MainActivity.this,SecondActivity.class); startActivity(intent); ~~~ #### 2、Action(动作):用来表现意图的行动 当日常生活中,描述一个意愿或愿望的时候,总是有一个动词在其中。比如:我想“做”三个俯卧撑;我要“写”一封情书,等等。在Intent中,Action就是描述做、写等动作的,当你指明了一个Action,执行者就会依照这个动作的指示,接受相关输入,表现对应行为,产生符合的输出。在Intent类中,定义了一批量的动作,比如ACTION_VIEW,ACTION_PICK等, 基本涵盖了常用动作。加的动作越多,越精确。 #### 标准的Activity Actions ACTION_MAIN                             作为一个主要的进入口,而并不期望去接受数据 ACTION_VIEW                             向用户去显示数据 ACTION_ATTACH_DATA               用于指定一些数据应该附属于一些其他的地方,例如,图片数据应该附属于联系人 ACTION_EDIT                              访问已给的数据,提供明确的可编辑 ACTION_PICK                              从数据中选择一个子项目,并返回你所选中的项目 ACTION_CHOOSER                      显示一个activity选择器,允许用户在进程之前选择他们想要的 ACTION_GET_CONTENT               允许用户选择特殊种类的数据,并返回(特殊种类的数据:照一张相片或录一段音) ACTION_DIAL                               拨打一个指定的号码,显示一个带有号码的用户界面,允许用户去启动呼叫 ACTION_CALL                              根据指定的数据执行一次呼叫 (ACTION_CALL在应用中启动一次呼叫有缺陷,多数应用ACTION_DIAL,ACTION_CALL不能用在紧急呼叫上,紧急呼叫可以用ACTION_DIAL来实现) ACTION_SEND                             传递数据,被传送的数据没有指定,接收的action请求用户发数据 ACTION_SENDTO                         发送一个信息到指定的某人 ACTION_ANSWER                        处理一个打进电话呼叫 ACTION_INSERT                          插入一条空项目到已给的容器 ACTION_DELETE                          从容器中删除已给的数据 ACTION_RUN                               运行数据,无论怎么 ACTION_SYNC                             同步执行一个数据 ACTION_PICK_ACTIVITY              为已知的Intent选择一个Activity,返回别选中的类 ACTION_SEARCH                         执行一次搜索 ACTION_WEB_SEARCH                执行一次web搜索 ACTION_FACTORY_TEST              工场测试的主要进入点, #### 标准的广播Actions ACTION_TIME_TICK                   当前时间改变,每分钟都发送,不能通过组件声明来接收,只有通过Context.registerReceiver()方法来注册 ACTION_TIME_CHANGED            时间被设置 ACTION_TIMEZONE_CHANGED   时间区改变 ACTION_BOOT_COMPLETED       系统完成启动后,一次广播 ACTION_PACKAGE_ADDED         一个新应用包已经安装在设备上,数据包括包名(最新安装的包程序不能接收到这个广播) ACTION_PACKAGE_CHANGED    一个已存在的应用程序包已经改变,包括包名 ACTION_PACKAGE_REMOVED   一个已存在的应用程序包已经从设备上移除,包括包名(正在被安装的包程序不能接收到这个广播) ACTION_PACKAGE_RESTARTED 用户重新开始一个包,包的所有进程将被杀死,所有与其联系的运行时间状态应该被移除,包括包名(重新开始包程序不能接收到这个广播) ACTION_PACKAGE_DATA_CLEARED 用户已经清楚一个包的数据,包括包名(清除包程序不能接收到这个广播) ACTION_BATTERY_CHANGED 电池的充电状态、电荷级别改变,不能通过组建声明接收这个广播,只有通过Context.registerReceiver()注册 ACTION_UID_REMOVED 一个用户ID已经从系统中移除 #### 3、category(类别):用来表现动作的类别 Intent中的Category属性是一个执行动作Action的附加信息。比如:CATEGORY_HOME则表示放回到Home界面,ALTERNATIVE_CATEGORY表示当前的Intent是一系列的可选动作中的一个。 指定Action范围,这个选项指定了将要执行的这个action的其他一些额外的约束。有时通过Action,配合Data 或Type,很多时候可以准确的表达出一个完整的意图了,但也会需要加一些约束在里面才能够更精准。比如,如果你虽然很喜欢做俯卧撑,但一次做三个还只是在特殊的时候才会发生,那么你可能表达说:每次吃撑了的时候 ,我都想做三个俯卧撑。吃撑了,这就对应着Intent的Category的范畴,它给所发生的意图附加一个约束。在Android中,一个实例是:所有应用的主Activity(单独启动时候,第一个运行的那个Activity...),都需要一个Category为 CATEGORY_LAUNCHER,Action为ACTION_MAIN的Intent。 在自定义动作时,使用activity组件时,必须添加一个默认的类别 具体的实现为: ~~~ <intent-filter> <action android:name="com.example.action.MY_ACTION"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> ~~~ 如果有多个组件被匹配成功,就会以对话框列表的方式让用户进行选择。 每个Intent中只能指定一个action,但却能指定多个category;类别越多,动作越具体,意图越明确(类似于相亲时,给对方提了很多要求)。 自定义类别: 在Intent添加类别可以添加多个类别,那就要求被匹配的组件必须同时满足这多个类别,才能匹配成功。操作Activity的时候,如果没有类别,须加上默认类别 | Constant | Meaning | |-----|-----| | `CATEGORY_BROWSABLE` | The target activity can be safely invoked by the browser to display data referenced by a link — for example, an image or an e-mail message. | | `CATEGORY_GADGET` | The activity can be embedded inside of another activity that hosts gadgets. | | `CATEGORY_HOME` | The activity displays the home screen, the first screen the user sees when the device is turned on or when the HOME key is pressed. | | `CATEGORY_LAUNCHER` | The activity can be the initial activity of a task and is listed in the top-level application launcher. | | `CATEGORY_PREFERENCE` | The target activity is a preference panel. | #### 4、data(数据):表示与动作要操纵的数据 = - Data属性是Android要访问的数据,和action和Category声明方式相同,也是在<intent-filter>中。 - 多个组件匹配成功显示优先级高的; 相同显示列表。 Data是用一个uri对象来表示的,uri代表数据的地址,属于一种标识符。通常情况下,我们使用action+data属性的组合来描述一个意图:做什么 使用隐式Intent,我们不仅可以启动自己程序内的活动,还可以启动其他程序的活动,这使得Android多个应用程序之间的功能共享成为了可能。比如应用程序中需要展示一个网页,没有必要自己去实现一个浏览器(事实上也不太可能),而是只需要条用系统的浏览器来打开这个网页就行了。 - 当Intent匹配成功的组件有多个时,显示优先级高的组件,如果优先级相同,显示列表让用户自己选择 - 优先级从-1000至1000,并且其中一个必须为负的才有效 注:系统默认的浏览器并没有做出优先级声明,其优先级默认为正数。 Data属性的声明中要指定访问数据的Uri和MIME类型。可以在<data>元素中通过一些属性来设置: android:scheme、android:path、 android:port、android:mimeType、android:host等,通过这些属性来对应一个典型的Uri格式 scheme://host:port/path。例如:http://www.google.com。 #### 5、type(数据类型):对于data范例的描写 如果Intent对象中既包含Uri又包含Type,那么,在<intent-filter>中也必须二者都包含才能通过测试。 Type属性用于明确指定Data属性的数据类型或MIME类型,但是通常来说,当Intent不指定Data属性时,Type属性才会起作用,否则Android系统将会根据Data属性值来分析数据的类型,所以无需指定Type属性。 data和type属性一般只需要一个,通过setData方法会把type属性设置为null,相反设置setType方法会把data设置为null,如果想要两个属性同时设置,要使用Intent.setDataAndType()方法。 #### 6、extras(扩展信息):扩展信息 是其它所有附加信息的集合。使用extras可以为组件提供扩展信息,比如,如果要执行“发送电子邮件”这个动作,可以将电子邮件的标题、正文等保存在extras里,传给电子邮件发送组件。 #### 7、Flags(标志位):期望这个意图的运行模式 一个程序启动后系统会为这个程序分配一个task供其使用,另外同一个task里面可以拥有不同应用程序的activity。那么,同一个程序能不能拥有多个task?这就涉及到加载activity的启动模式,这个需要单独讲一下。能识别,有输入,整个Intent基本就完整了,但还有一些附件的指令,需要放在Flags中带过去。顾名思义,Flags是一个整形数,有一些列的标志 位构成,这些标志,是用来指明运行模式的。比如,你期望这个意图的执行者,和你运行在两个完全不同的任务中(或说进程也无妨吧...),就需要设置**FLAG_ACTIVITY_NEW_TASK**的标志位。 注:android中一组逻辑上在一起的activity被叫做task,自己认为可以理解成一个activity堆栈。 ### Intent 的解析机制 理解Intent的关键之一是理解清楚Intent的两种基本用法:一种是**显式的Intent**,即在构造Intent对象时就指定接收者;另一种是**隐式的Intent**,即Intent的发送者在构造Intent对象时,并不知道也不关心接收者是谁,有利于降低发送者和接收者之间的耦合。官方建议使用隐式Intent。上述属性中,component属性为直接类型,其他均为间接类型。 对于显式Intent,Android不需要去做解析,因为目标组件已经很明确,Android需要解析的是那些隐式Intent,通过解析,将 Intent映射给可以处理此Intent的Activity、IntentReceiver或Service。 相比与显式Intent,隐式Intnet则含蓄了许多,它并不明确指出我们想要启动哪一个活动,而是指定一系列更为抽象的action和category等信息,然后交由系统去分析这个Intent,并帮我们找出合适的活动去启动。 Activity 中 Intent Filter 的匹配过程 : ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a954193f0.png) Intent 解析机制主要是通过查找已注册在AndroidManifest.xml中的所有IntentFilter及其中定义的Intent,最终找到匹配的 Intent。在这个解析过程中,Android是通过Intent的action、type、category这三个属性来进行判断的,判断方法如下: - 如果Intent指明定了action,则目标组件的IntentFilter的action列表中就必须包含有这个action,否则不能匹配; - 如果Intent没有提供type,系统将从data中得到数据类型。和action一样,目标组件的数据类型列表中必须包含Intent的数据类型,否则不能匹配。 - 如果Intent中的数据不是content: 类型的URI,而且Intent也没有明确指定它的type,将根据Intent中数据的scheme (比如 http: 或者[mailto](http://www.androidcn.net/wiki/index.php/Reference/android/content/mailto):) 进行匹配。同上,Intent 的scheme必须出现在目标组件的scheme列表中。 - 如果Intent指定了一个或多个category,这些类别必须**全部**出现在组建的类别列表中。比如Intent中包含了两个类别:LAUNCHER_CATEGORY 和 ALTERNATIVE_CATEGORY,解析得到的目标组件必须至少包含这两个类别。 **Intent-Filter的定义** 一些属性设置的例子: ~~~ <action android:name="com.example.project.SHOW_CURRENT" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="video/mpeg" android:scheme="http" . . . /> <data android:mimeType="image/*" /> <data android:scheme="http" android:type="video/*" /> ~~~ 完整的实例 ~~~ <activity android:name="NotesList" android:label="@string/title_notes_list"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <intent-filter> <action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.EDIT" /> <action android:name="android.intent.action.PICK" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="vnd.android.cursor.dir/vnd.google.note" /> </intent-filter> <intent-filter> <action android:name="android.intent.action.GET_CONTENT" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="vnd.android.cursor.item/vnd.google.note" /> </intent-filter> </activity> ~~~ ### Intent的构造函数 公共构造函数: 1、Intent() 空构造函数 2、Intent(Intent o) 拷贝构造函数 3、Intent(String action) 指定action类型的构造函数 4、Intent(String action, Uri uri) 指定Action类型和Uri的构造函数,URI主要是结合程序之间的数据共享ContentProvider 5、Intent(Context packageContext, Class<?> cls) 传入组件的构造函数,也就是上文提到的 6、Intent(String action, Uri uri, Context packageContext, Class<?> cls) 前两种结合体 Intent有六种构造函数,3、4、5是最常用的,并不是其他没用! Intent(String action, Uri uri) 的action就是对应在AndroidMainfest.xml中的action节点的name属性值。在Intent类中定义了很多的Action和Category常量。 **示例代码二:** ~~~ Intent intent = new Intent(Intent.ACTION_EDIT, null); startActivity(intent); ~~~ 示例代码二是用了第四种构造函数,只是uri参数为null。执行此代码的时候,系统就会在程序主配置文件AndroidMainfest.xml中寻找 <action android:name="android.intent.action.EDIT" />对应的Activity,如果对应为多个activity具有<action android:name="android.intent.action.EDIT" />此时就会弹出一个dailog选择Activity。 如果是用示例代码一那种方式进行发送则不会有这种情况。 **利用Intent在Activity之间传递数据** 在Main中执行如下代码: ~~~ Bundle bundle = new Bundle(); bundle.putStringArray("NAMEARR", nameArr); Intent intent = new Intent(Main.this, CountList.class); intent.putExtras(bundle); startActivity(intent); ~~~ 在CountList中,代码如下: ~~~ Bundle bundle = this.getIntent().getExtras(); String[] arrName = bundle.getStringArray("NAMEARR"); ~~~ 以上代码就实现了Activity之间的数据传递! ###总结说明 这篇文章是我刚开始学习Android时看到的,当时理解的不是很深入,现在再回头看这篇文章总结的很详细,在这里与大家分享。 1,调用web浏览器 ~~~ <pre name="code" class="java">Uri myBlogUri = Uri.parse("http://kuikui.javaeye.com"); returnIt = new Intent(Intent.ACTION_VIEW, myBlogUri); ~~~ ~~~ ~~~ 2,地图 ~~~ Uri mapUri = Uri.parse("geo:38.899533,-77.036476"); returnIt = new Intent(Intent.ACTION_VIEW, mapUri); ~~~ 3,调拨打电话界面 ~~~ Uri telUri = Uri.parse("tel:100861"); returnIt = new Intent(Intent.ACTION_DIAL, telUri); ~~~ 4,直接拨打电话 ~~~ Uri callUri = Uri.parse("tel:100861"); returnIt = new Intent(Intent.ACTION_CALL, callUri); ~~~ 5,卸载 ~~~ Uri uninstallUri = Uri.fromParts("package", "xxx", null); returnIt = new Intent(Intent.ACTION_DELETE, uninstallUri); ~~~ 6,安装 ~~~ Uri installUri = Uri.fromParts("package", "xxx", null); returnIt = new Intent(Intent.ACTION_PACKAGE_ADDED, installUri); ~~~ 7,播放 ~~~ Uri playUri = Uri.parse("file:///sdcard/download/everything.mp3"); returnIt = new Intent(Intent.ACTION_VIEW, playUri); ~~~ 8,调用发邮件 ~~~ Uri emailUri = Uri.parse("mailto:shenrenkui@gmail.com"); returnIt = new Intent(Intent.ACTION_SENDTO, emailUri); ~~~ 9,发邮件 ~~~ returnIt = new Intent(Intent.ACTION_SEND); String[] tos = { "shenrenkui@gmail.com" }; String[] ccs = { "shenrenkui@gmail.com" }; returnIt.putExtra(Intent.EXTRA_EMAIL, tos); returnIt.putExtra(Intent.EXTRA_CC, ccs); returnIt.putExtra(Intent.EXTRA_TEXT, "body"); returnIt.putExtra(Intent.EXTRA_SUBJECT, "subject"); returnIt.setType("message/rfc882"); Intent.createChooser(returnIt, "Choose Email Client"); ~~~ 10,发短信 ~~~ Uri smsUri = Uri.parse("tel:100861"); returnIt = new Intent(Intent.ACTION_VIEW, smsUri); returnIt.putExtra("sms_body", "shenrenkui"); returnIt.setType("vnd.android-dir/mms-sms"); ~~~ 11,直接发邮件 ~~~ Uri smsToUri = Uri.parse("smsto://100861"); returnIt = new Intent(Intent.ACTION_SENDTO, smsToUri); returnIt.putExtra("sms_body", "shenrenkui"); ~~~ 12,发彩信 ~~~ Uri mmsUri = Uri.parse("content://media/external/images/media/23"); returnIt = new Intent(Intent.ACTION_SEND); returnIt.putExtra("sms_body", "shenrenkui"); returnIt.putExtra(Intent.EXTRA_STREAM, mmsUri); returnIt.setType("image/png"); ~~~ 用获取到的Intent直接调用startActivity(returnIt)就ok了。 ###Intent用法实例 #### 1、打开指定网页:(直接复制的上面的代码) MainActivity.java中,监听器部分的核心代码如下: ~~~ button1.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse("http://www.baidu.com")); startActivity(intent); } }); ~~~ 第4行代码:指定了Intent的action是 **Intent.ACTION_VIEW**,表示查看的意思,这是一个Android系统内置的动作; 第5行代码:通过Uri.parse()方法,将一个网址字符串解析成一个Uri对象,再调用intent的setData()方法将这个Uri对象传递进去。 #### 2、打电话: 【方式一】打开拨打电话的界面: ~~~ Intent intent = new Intent(Intent.ACTION_DIAL); intent.setData(Uri.parse("tel:10086")); startActivity(intent); ~~~ 运行程序后,点击按钮,显示如下界面: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a95434437.png) 【方式二】直接拨打电话: ~~~ Intent intent = new Intent(Intent.ACTION_CALL); intent.setData(Uri.parse("tel:10086")); startActivity(intent); ~~~ 要使用这个功能必须在配置文件中加入权限:(加一行代码) ~~~ <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="16" /> <uses-permission android:name="android.permission.CALL_PHONE"/> ~~~ #### 3、发送短信: 【方式一】打开发送短信的界面:action+type ~~~ Intent intent = new Intent(Intent.ACTION_VIEW); intent.setType("vnd.android-dir/mms-sms"); intent.putExtra("sms_body", "具体短信内容"); //"sms_body"为固定内容 startActivity(intent); ~~~ 【方式二】打开发短信的界面(同时指定电话号码):action+data ~~~ Intent intent = new Intent(Intent.ACTION_SENDTO); intent.setData(Uri.parse("smsto:18780260012")); intent.putExtra("sms_body", "具体短信内容"); //"sms_body"为固定内容 startActivity(intent); ~~~ #### 4、播放指定路径音乐:action+data+type ~~~ Intent intent = new Intent(Intent.ACTION_VIEW); Uri uri = Uri.parse("file:///storage/sdcard0/平凡之路.mp3"); ////路径也可以写成:"/storage/sdcard0/平凡之路.mp3" intent.setDataAndType(uri, "audio/mp3"); //方法:Intent android.content.Intent.setDataAndType(Uri data, String type) startActivity(intent); ~~~ ####5、卸载程序:action+data(例如点击按钮,卸载某个应用程序,根据包名来识别) 注:无论是安装还是卸载,应用程序是根据包名package来识别的。 ~~~ Intent intent = new Intent(Intent.ACTION_DELETE); Uri data = Uri.parse("package:com.example.smyh006intent01"); intent.setData(data); startActivity(intent); ~~~ #### 6、安装程序:action+data+type ~~~ Intent intent = new Intent(Intent.ACTION_VIEW); Uri data = Uri.fromFile(new File("/storage/sdcard0/AndroidTest/smyh006_Intent01.apk")); //路径不能写成:"file:///storage/sdcard0/···" intent.setDataAndType(data, "application/vnd.android.package-archive"); //Type的字符串为固定内容 startActivity(intent); ~~~ 注:第2行的路径不能写成:"file:///storage/sdcard0/···",不然报错如下: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a9544b499.png) 综上所述,完整版代码如下: ~~~ <LinearLayout 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" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:orientation="vertical" tools:context=".MainActivity" > <Button android:id="@+id/button1_browsePage" android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="browsePageClick" android:text="打开指定网页"/> <Button android:id="@+id/button2_openDialPage" android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="openDialPageClick" android:text="打开拨号面板"/> <Button android:id="@+id/button3_dialPhone" android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="dialPhoneClick" android:text="直接拨打指定号码"/> <Button android:id="@+id/button4_openMsgPage" android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="openMsgPageClick" android:text="打开发短信的界面"/> <Button android:id="@+id/button5_sendMsg" android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="sendMsgClick" android:text="给指定的人发短信"/> <Button android:id="@+id/button6_playMusic" android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="playMusicClick" android:text="播放指定路径音乐"/> <Button android:id="@+id/button7_uninstall" android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="uninstallClick" android:text="卸载程序"/> <Button android:id="@+id/button8_install" android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="installClick" android:text="安装程序"/> </LinearLayout> ~~~ MainActivity.java代码如下: ~~~ package com.example.m06intent01; import java.io.File; import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.view.Menu; import android.view.View; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } //打开指定网页 public void browsePageClick(View view){ Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse("http://www.baidu.com/")); startActivity(intent); } //打开拨号面板 public void openDialPageClick(View view){ Intent intent = new Intent(Intent.ACTION_DIAL); intent.setData(Uri.parse("tel:10086")); startActivity(intent); } //直接拨打指定号码 public void dialPhoneClick(View view){ Intent intent = new Intent(Intent.ACTION_CALL); intent.setData(Uri.parse("tel:10086")); startActivity(intent); } //打开发短信的界面:action+type public void openMsgPageClick(View view){ Intent intent = new Intent(Intent.ACTION_VIEW); intent.setType("vnd.android-dir/mms-sms"); intent.putExtra("sms_body", "具体短信内容"); //"sms_body"为固定内容 startActivity(intent); } //打开发短信的界面(指定电话号码):action+data public void sendMsgClick(View view){ Intent intent = new Intent(Intent.ACTION_SENDTO); intent.setData(Uri.parse("smsto:18780260012")); intent.putExtra("sms_body", "具体短信内容"); //"sms_body"为固定内容 startActivity(intent); } //播放指定路径音乐 public void playMusicClick(View view){ Intent intent = new Intent(Intent.ACTION_VIEW); Uri uri = Uri.parse("file:///storage/sdcard0/平凡之路.mp3"); //路径也可以写成:"/storage/sdcard0/平凡之路.mp3" intent.setDataAndType(uri, "audio/mp3"); //方法:Intent android.content.Intent.setDataAndType(Uri data, String type) startActivity(intent); } //卸载某个应用程序,根据包名来识别 public void uninstallClick(View view){ Intent intent = new Intent(Intent.ACTION_DELETE); Uri data = Uri.parse("package:com.example.smyh006intent01"); intent.setData(data); startActivity(intent); } //安装某个应用程序,根据apk的文件名来识别 public void installClick(View view){ Intent intent = new Intent(Intent.ACTION_VIEW); Uri data = Uri.fromFile(new File("/storage/sdcard0/AndroidTest/smyh006_Intent01.apk")); //路径不能写成:"file:///storage/sdcard0/···" intent.setDataAndType(data, "application/vnd.android.package-archive"); //Type的字符串为固定内容 startActivity(intent); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } } ~~~ **1.无参数Activity跳转** ~~~ Intent it = new Intent(Activity.Main.this, Activity2.class); startActivity(it); ~~~ **2.向下一个Activity传递数据(使用Bundle和Intent.putExtras)** ~~~ Intent it = new Intent(Activity.Main.this, Activity2.class); Bundle bundle=new Bundle(); bundle.putString("name", "This is from MainActivity!"); it.putExtras(bundle); // it.putExtra(“test”, "shuju”); startActivity(it); // startActivityForResult(it,REQUEST_CODE); ~~~ 对于数据的获取可以采用: ~~~ Bundle bundle=getIntent().getExtras(); String name=bundle.getString("name"); ~~~ **3.向上一个Activity返回结果(使用setResult,针对startActivityForResult(it,REQUEST_CODE)启动的Activity)** ~~~ Intent intent=getIntent(); Bundle bundle2=new Bundle(); bundle2.putString("name", "This is from ShowMsg!"); intent.putExtras(bundle2); setResult(RESULT_OK, intent); ~~~ **4.回调上一个Activity的结果处理函数(onActivityResult)** ~~~ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { // TODO Auto-generated method stub super.onActivityResult(requestCode, resultCode, data); if (requestCode==REQUEST_CODE){ if(resultCode==RESULT_CANCELED) setTitle("cancle"); else if (resultCode==RESULT_OK) { String temp=null; Bundle bundle=data.getExtras(); if(bundle!=null) temp=bundle.getString("name"); setTitle(temp); } } } ~~~ 运行后,主界面如下: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a9545ba57.png) **下面是转载来的其他的一些Intent用法实例(转自javaeye)** ###显示网页 ~~~ Uri uri = Uri.parse("http://google.com"); Intent it = new Intent(Intent.ACTION_VIEW, uri); startActivity(it); ~~~ ### 显示地图 ~~~ Uri uri = Uri.parse("geo:38.899533,-77.036476"); Intent it = new Intent(Intent.ACTION_VIEW, uri); startActivity(it); //其他 geo URI 範例 //geo:latitude,longitude //geo:latitude,longitude?z=zoom //geo:0,0?q=my+street+address //geo:0,0?q=business+near+city //google.streetview:cbll=lat,lng&cbp=1,yaw,,pitch,zoom&mz=mapZoom ~~~ ### 路径规划 ~~~ Uri uri = Uri.parse("http://maps.google.com/maps?f=d&saddr=startLat%20startLng&daddr=endLat%20endLng&hl=en"); Intent it = new Intent(Intent.ACTION_VIEW, uri); startActivity(it); //where startLat, startLng, endLat, endLng are a long with 6 decimals like: 50.123456 ~~~ ### 打电话 ~~~ //叫出拨号程序 Uri uri = Uri.parse("tel:0800000123"); Intent it = new Intent(Intent.ACTION_DIAL, uri); startActivity(it); //直接打电话出去 Uri uri = Uri.parse("tel:0800000123"); Intent it = new Intent(Intent.ACTION_CALL, uri); startActivity(it); //用這個,要在 AndroidManifest.xml 中,加上 //<uses-permission id="android.permission.CALL_PHONE" /> ~~~ ### 传送SMS/MMS ~~~ //调用短信程序 Intent it = new Intent(Intent.ACTION_VIEW, uri); it.putExtra("sms_body", "The SMS text"); it.setType("vnd.android-dir/mms-sms"); startActivity(it); //传送消息 Uri uri = Uri.parse("smsto://0800000123"); Intent it = new Intent(Intent.ACTION_SENDTO, uri); it.putExtra("sms_body", "The SMS text"); startActivity(it); //传送 MMS Uri uri = Uri.parse("content://media/external/images/media/23"); Intent it = new Intent(Intent.ACTION_SEND); it.putExtra("sms_body", "some text"); it.putExtra(Intent.EXTRA_STREAM, uri); it.setType("image/png"); startActivity(it); ~~~ ### 传送 Email ~~~ Uri uri = Uri.parse("mailto:xxx@abc.com"); Intent it = new Intent(Intent.ACTION_SENDTO, uri); startActivity(it); Intent it = new Intent(Intent.ACTION_SEND); it.putExtra(Intent.EXTRA_EMAIL, "me@abc.com"); it.putExtra(Intent.EXTRA_TEXT, "The email body text"); it.setType("text/plain"); startActivity(Intent.createChooser(it, "Choose Email Client")); Intent it=new Intent(Intent.ACTION_SEND); String[] tos={"me@abc.com"}; String[] ccs={"you@abc.com"}; it.putExtra(Intent.EXTRA_EMAIL, tos); it.putExtra(Intent.EXTRA_CC, ccs); it.putExtra(Intent.EXTRA_TEXT, "The email body text"); it.putExtra(Intent.EXTRA_SUBJECT, "The email subject text"); it.setType("message/rfc822"); startActivity(Intent.createChooser(it, "Choose Email Client")); //传送附件 Intent it = new Intent(Intent.ACTION_SEND); it.putExtra(Intent.EXTRA_SUBJECT, "The email subject text"); it.putExtra(Intent.EXTRA_STREAM, "file:///sdcard/mysong.mp3"); sendIntent.setType("audio/mp3"); startActivity(Intent.createChooser(it, "Choose Email Client")); ~~~ ###播放多媒体 ~~~ Uri uri = Uri.parse("file:///sdcard/song.mp3"); Intent it = new Intent(Intent.ACTION_VIEW, uri); it.setType("audio/mp3"); startActivity(it); Uri uri = Uri.withAppendedPath(MediaStore.Audio.Media.INTERNAL_CONTENT_URI, "1"); Intent it = new Intent(Intent.ACTION_VIEW, uri); startActivity(it); ~~~ ###Market 相关 ~~~ //寻找某个应用 Uri uri = Uri.parse("market://search?q=pname:pkg_name"); Intent it = new Intent(Intent.ACTION_VIEW, uri); startActivity(it); //where pkg_name is the full package path for an application //显示某个应用的相关信息 Uri uri = Uri.parse("market://details?id=app_id"); Intent it = new Intent(Intent.ACTION_VIEW, uri); startActivity(it); //where app_id is the application ID, find the ID //by clicking on your application on Market home //page, and notice the ID from the address bar ~~~ ###Uninstall 应用程序 ~~~ Uri uri = Uri.fromParts("package", strPackageName, null); Intent it = new Intent(Intent.ACTION_DELETE, uri); startActivity(it); ~~~ 参考: [http://www.cnblogs.com/smyhvae/p/3959204.html](http://www.cnblogs.com/smyhvae/p/3959204.html "http://www.cnblogs.com/smyhvae/p/3959204.html") [http://blog.csdn.net/yulei_qq/article/details/21233901](http://blog.csdn.net/yulei_qq/article/details/21233901 "http://blog.csdn.net/yulei_qq/article/details/21233901") [http://zy77612.iteye.com/blog/764699](http://zy77612.iteye.com/blog/764699 "http://zy77612.iteye.com/blog/764699")
';

手机安全卫士04_02:从服务器下载并安装新版本安装包

最后更新于:2022-04-01 16:16:09

### 文件下载 ### 1. 下载文件业务类 下载文件的操作也属于业务方法,所以在com.liuhao.mobilesafe.engine中创建一个DownloadFileTask下载文件的类。 其中的getFile方法,用于从服务器文件路径上下载文件至本地文件目录。 ~~~ package com.liuhao.mobilesafe.engine; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; public class DownloadFileTask { /** * @param path * 服务器文件路径 * @param filepath * 本地文件路径 * @return 本节文件对象 * @throws Exception */ public static File getFile(String path, String filepath) throws Exception { URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setReadTimeout(5000); // 读取数据没有异常 if (conn.getResponseCode() == 200) { InputStream is = conn.getInputStream();// 获取文件输入流 File file = new File(filepath);// 本地文件对象 FileOutputStream fos = new FileOutputStream(file);//本地文件输出流 byte[] buffer = new byte[1024]; int length = 0; while ((length = is.read(buffer)) != -1) { fos.write(buffer, 0, length); } fos.flush(); fos.close(); is.close(); return file; } return null; } } ~~~ ### 2.使用下载文件类 在用户点击“确定”后,会进行下载。 其中定义了一个进度条,用来显示下载过程: ~~~ private ProgressDialog pd;// 进度条 pd = new ProgressDialog(this); pd.setMessage("正在下载,请耐心等待。o(∩_∩)o");// 设置进度条显示的内容 builder.setPositiveButton("确定", new OnClickListener() { // 设置用户选择确定时的按键操作 @Override public void onClick(DialogInterface dialog, int which) { Log.i(TAG, "下载pak文件:" + info.getApkurl()); // 判断sd卡是否可用 if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { // 调用子线程进行下载 DownloadFileThreadTask task = new DownloadFileThreadTask( info.getApkurl(), Environment.getExternalStorageDirectory().getPath() + "/aanew.apk"); pd.show();// 显示下载进度条 new Thread(task).start();// 启动子线程 } else { Toast.makeText(getApplicationContext(), "sd卡不可用", Toast.LENGTH_LONG).show(); loadMainUI(); } } }); // 子线程,用于下载文件,因为下载文件比较耗时 private class DownloadFileThreadTask implements Runnable { private String path;// 服务器路径 private String filepath;// 本地文件路径 public DownloadFileThreadTask(String path, String filepath) { this.path = path; this.filepath = filepath; } @Override public void run() { try { File file = DownloadFileTask.getFile(path, filepath); Log.i(TAG, "下载更新apk成功"); pd.dismiss(); } catch (Exception e) { e.printStackTrace(); Toast.makeText(getApplicationContext(), "下载文件失败", Toast.LENGTH_LONG).show(); pd.dismiss(); loadMainUI(); } } } ~~~ ### 3.添加权限 由于下载文件需要对sd卡进行读写,因袭需要sd卡的权限:<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> 写外部存储设备的权限:<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> ### 4.配置服务器端的apk文件(高版本的) 将当前版本号改为2.0,生成一个apk安装包,放到之前指定的目录(%TOMCAT_HOME%\webapps\ROOT),然后再改回来。 [![image](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a9537bce3.jpg "image")](http://img.blog.csdn.net/20141030113722327) ### 异常处理: ERROR/AndroidRuntime(1540): Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application 我们在ActivityGroup或者TabActivity中的子Activity创建Dialog若使用以下的代码 ~~~ progressDialog = new ProgressDialog(XXX.this) ~~~ 创建就会出现如下Exception: ERROR/AndroidRuntime(6362): android.view.WindowManager$BadTokenException: Unable to add window -- token android.app.LocalActivityManager$LocalActivityRecord@43e5b158 is not valid; is your activity running?  而该使用: ~~~ progressDialog = new ProgressDialog(getParent()) ~~~ 原因分析: 因为new对话框的时候,参数content 指定成了this,即指向当前子Activity的content。但子Activity是动态创建的,不能保证一直存在。其父Activity的content是稳定存在的,所以有下面的解决办法。 若ActivityGroup中嵌套ActivityGroup,嵌套多少就该使用多少个getParent()。 为什么要使用getParent我们可以从柯元旦的《Android内核剖析》中第十章”Ams内部原理“中的ActivityGroup的内部机制来理解: TabActivity的父类是ActivityGroup,而ActivityGroup的父类是Activity。因此从Ams的角度来看,ActivityGroup与普通的Activity没有什么区别,其生命周期包括标准的start,stop,resume,destroy等,而且系统中只允许同时允许一个ActivityGroup.但ActivityGroup内部有一个重要成员变量,其类型为LocalActivityManager,该类的最大特点在于它可以访问应用进程的主类,即ActivityThread类。Ams要启动某个Activity或者赞同某个Activity都是通过ActivityThread类执行的,而LocalActivityManager类就意味着可以通过它来装载不同的Activity,并控制Activity的不同的状态。注意,这里是装载,而不是启动,这点很重要。所谓的启动,一般是指会创建一个进程(如果所在的应用经常还不存在)运行该Activity,而装载仅仅是指把该Activity作为一个普通类进行加载,并创建一个该类的对象而已,而该类的任何函数都没有被运行。装载Activity对象的过程对AmS来讲是完全不可见的,那些嵌入的Activity仅仅贡献了自己所包含的Window窗口而已。而子Activity的不同状态是通过moveToState来处理的。 所以子Activity不是像普通的Activity一样,它只是提供Window而已,所以在创建Dialog时就应该使用getParent获取ActivityGroup真正的Activity,才可以加Dialog加入Activity中。 参考: [http://aijiawang-126-com.iteye.com/blog/1717368](http://aijiawang-126-com.iteye.com/blog/1717368 "http://aijiawang-126-com.iteye.com/blog/1717368") 下载部署完毕后,运行效果 [![Screenshot_2014-10-29-17-13-43](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a95398644.jpg "Screenshot_2014-10-29-17-13-43")](http://img.blog.csdn.net/20141030113723747) [![Screenshot_2014-10-29-17-14-03](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a953bedc1.jpg "Screenshot_2014-10-29-17-14-03")](http://img.blog.csdn.net/20141030113724995) ### 文件安装(下载完成后自动安装)(知识点:[Intent](http://blog.csdn.net/bruce_6/article/details/40619213)) ~~~ /** * 安装apk文件 * @param file */ private void install(File file){ Intent intent = new Intent(); intent.setAction(Intent.ACTION_VIEW); intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive"); finish();// 终结当前Activity startActivity(intent);// 激活新的Activity } ~~~ [![Screenshot_2014-10-30-15-50-15](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a953cea90.jpg "Screenshot_2014-10-30-15-50-15")](http://img.blog.csdn.net/20141030162312654) ### 让当前Activity延时2秒再判断是否需要更新(知识点:Handler) ~~~ private String versiontext; private Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (isNeedUpdate(versiontext)) { Log.i(TAG, "弹出升级对话框"); showUpdateDialog(); } } }; ~~~ onCreate方法中: ~~~ // 让当前Activity延时两秒钟,再去检查更新 new Thread(){ public void run() { try { sleep(2000); handler.sendEmptyMessage(0);// 向主线程发送一条空消息 } catch (InterruptedException e) { e.printStackTrace(); } }; }.start(); ~~~ ### 设置下载进度条显示下载进度(知识点:[ProgressDialog](http://blog.csdn.net/bruce_6/article/details/40614999)) 在下载任务类DownloadFileTask的getFile()方法中添加一个ProgressDialog作为参数,在下载过程中对其进行设置。 ~~~ /** * @param path * 服务器文件路径 * @param filepath * 本地文件路径 * @param pd * 进度条,用以显示下载进度 * @return 本地文件对象 * @throws Exception */ public static File getFile(String path, String filepath, ProgressDialog pd) throws Exception { URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setReadTimeout(5000); // 读取数据没有异常 if (conn.getResponseCode() == 200) { int total = conn.getContentLength();// 获取内容的总长度 pd.setMax(total); InputStream is = conn.getInputStream();// 获取文件输入流 File file = new File(filepath);// 本地文件对象 FileOutputStream fos = new FileOutputStream(file);//本地文件输出流 byte[] buffer = new byte[1024]; int length = 0; int process = 0;// 当前进度 while ((length = is.read(buffer)) != -1) { fos.write(buffer, 0, length); process += length; pd.setProgress(process);// 设置当前进度 Thread.sleep(50); } fos.flush(); fos.close(); is.close(); return file; } return null; } ~~~ 由于默认的ProgressDialog是不显示下载进度的,因此需要进行设置。 pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);// 默认情况下不显示进度,这个设置用于显示进度 效果: [![Screenshot_2014-10-30-15-49-56](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a953dca5b.jpg "Screenshot_2014-10-30-15-49-56")](http://img.blog.csdn.net/20141030162313762)
';

知识点:Android控件系列之ProgressDialog与ProgressBar

最后更新于:2022-04-01 16:16:07

### ProgressDialog ProgressDialog与ProgressBar在UI中动态显示一个加载图标显示程序运行状态。 ProgressDialog是继承自Android.app.AlertDialog所设计的互动对话窗口,使用时,必须新建ProgressDialog对象,在运行时会弹出“对话框”作为提醒,此时应用程序后台失去焦点(即此时无法对UI组件进行操作),直到进程结束后,才会将控制权交给应用程序,如果在Activity当中不希望后台失焦,又希望提示User有某后台程序正处于忙碌阶段,那么ProgressBar就会派上用场了。 ~~~ ProgressDialog pd= new ProgressDialog(TestProgerssDialogActivity.this); // 设置pd风格 pd.setProgress(ProgressDialog.STYLE_SPINNER);//圆形 pd.setProgress(ProgressDialog.STYLE_HORIZONTAL);//水平 // 设置pd标题 pd.setTitle("提示"); // 设置pd提示 pd.setMessage("这是一个圆形进度条对话框"); // 设置pd进度条的图标 pd.setIcon(R.drawable.flag); // 设置pd的进度条是否不明确 //不滚动时,当前值在最小和最大值之间移动,一般在进行一些无法确定操作时间的任务时作为提示,明确时就是根据你的进度可以设置现在的进度值 pd.setIndeterminate(false); //pd.setProgress(m_count++); // 是否可以按回退键取消 pd.setCancelable(true); // 设置pd的一个Button pd.setButton("确定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } }); // 显示pd.show(); ~~~ 当有ProgressDialog时,点击back时,会首先取消ProgressDialog ,以上代码可以监听进度条被取消事件(也就是点击Back键取消ProgressDialog),此时可以在这里作一些取消后台操作的处理。 ### ProgressBar XML重要属性 ~~~ android:progressBarStyle:默认进度条样式 android:progressBarStyleHorizontal:水平样式 ~~~ 重要方法 getMax():返回这个进度条的范围的上限 getProgress():返回进度 getSecondaryProgress():返回次要进度 incrementProgressBy(int diff):指定增加的进度 isIndeterminate():指示进度条是否在不确定模式下 setIndeterminate(boolean indeterminate):设置不确定模式下 setVisibility(int v):设置该进度条是否可视 重要事件 onSizeChanged(int w, int h, int oldw, int oldh):当进度值改变时引发此事件 ~~~ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Welcome to blog" /> <ProgressBar android:id="@+id/rectangleProgressBar" style="?android:attr/progressBarStyleHorizontal" mce_style="?android:attr/progressBarStyleHorizontal" android:layout_width="fill_parent" android:layout_height="wrap_content" android:visibility="gone" /> <ProgressBar android:id="@+id/circleProgressBar" style="?android:attr/progressBarStyleLarge" mce_style="?android:attr/progressBarStyleLarge" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="gone" /> <Button android:id="@+id/button" android:text="Show ProgressBar" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> ~~~ 参考: [http://www.jb51.net/article/38532.htm](http://www.jb51.net/article/38532.htm "http://www.jb51.net/article/38532.htm")
';

手机安全卫士04_01:界面(Activity)之间的切换,Activity和任务栈

最后更新于:2022-04-01 16:16:05

上一回说到,用户选择是否升级,若用户选择不升级,那么就要进入程序的主界面。下面要做的是从splash界面跳转到main界面。 ### MainActivity创建 1.首先新建MainActivity: ~~~ package com.liuhao.mobilesafe.ui; import com.liuhao.mobilesafe.R; import android.app.Activity; import android.os.Bundle; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //Set the activity content from a layout resource. The resource will be inflated, adding all top-level views to the activity setContentView(R.layout.main); } } ~~~ 2.MainActivity对应的布局文件:main.xml ~~~ <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" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".SplashActivity" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/hello_world" /> </RelativeLayout> ~~~ 3.AndroidManifest.xml中配置MainActivity ~~~ <activity android:name="com.liuhao.mobilesafe.ui.MainActivity" android:label="@string/main_screen"> </activity> ~~~ 至此便完成了MainActivity的创建,下面就是SplashActivity到MainActivity的切换。 ### SplashActivity到MainActivity的切换 在SplashActivity新建loadMainUI()方法 ~~~ private void loadMainUI(){ Intent intent = new Intent(this, MainActivity.class); startActivity(intent); finish(); //将当前Activity从任务栈中移除 } ~~~ 然后在之前需要进入主界面的地方调用此方法即可: ① ~~~ if(versiontext.equals(version)){ Log.i(TAG, "版本号相同,无需升级,进入到主界面"); loadMainUI(); return false; } ~~~ ②        ~~~ catch (Exception e) { e.printStackTrace(); /** * Toast使用场景 * 1、需要提示用户,但又不需要用户点击“确定”或者“取消”按钮。 * 2、不影响现有Activity运行的简单提示。 */ Toast.makeText(this, "获取更新信息异常", 2).show();//弹出文本,并保持2秒 Log.i(TAG, "获取更新信息异常,进入到主界面"); loadMainUI(); return false; } ~~~ ③       ~~~ builder.setNegativeButton("取消", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Log.i(TAG, "用户取消升级,进入程序主界面"); loadMainUI(); } }); ~~~ ### 知识点 #### 任务栈TaskStack和Activity的管理 上面的这段代码就是在一个activity里通过Intent启动另一个activity的实例。就像前面提到的,一个activity可以启动另一个,包括那些定义在不同应用程序中的。 一个activity就是一个用户界面,可以连续启动很多activity以提高用户体验。当然这个数量是有限的,这要根据手机的硬件配置来决定。上一篇文章中已经介绍,所有打开的activity都是存储在栈中的,所以如果打开的activity过多,会消占去很多的内存和处理器资源,严重时会导致当前应用无法启动或者系统崩溃。 activity是和任务紧密联系的。因为在android系统中,为了保持用户体验,用户想做某件事情是以任务的结构作为逻辑,以应用的形式来表现的。而一个应用又可以包含很多的activity,所以就涉及到activity和task的关系问题。 #### activity简单解释 Android中,Activity是所有程序的根本,所有程序的流程都运行在Activity之中。简单的的说,任务就是用户所体验到的“应用程序”。它是一组相关的activity,分配到 一个栈中。栈中的根activity,是任务的开始——一般来说,它是用户组应用程序加载器中选择的activity。在栈顶的activity正是当前 正在运行的——集中处理用户动作的那个。当一个activity启动了另外一个,这个新的activity将压入栈中,它将成为正在运行中的 activity。前一个activity保留在栈中。当用户按下后退按键,当前的这个activity将中栈中弹出,而前面的那个activity恢复 成运行中状态。 #### activity的生命周期: Activity具有自己的生命周期,由系统控制生命周期,程序无法改变,但可以用**onSaveInstanceState**保存其状态。对于Activity,关键是其**生命周期**的把握(如下图),其次就是状态的保存和恢复(onSaveInstanceState onRestoreInstanceState),以及Activity之间的跳转和数据传输(**intent**)。 **onCreate()**------->**onStart**()-------->**onResume**()--------->**onSaveInstanceState**()----->**onPause**()------->**onStop()**--------->**onDestroy**(). ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a95330ed8.png) Activity中常用的函数有**SetContentView()   findViewById()    finish()   startActivity(),**其生命周期涉及的函数有: > **`void onCreate(Bundle *savedInstanceState*)` `void onStart()` `void onRestart()` `void onResume()` `void onPause()` `void onStop()` `void onDestroy()`** `注意的是,Activity的使用需要在Manifest文件中添加相应的<Activity>,并设置其属性和intent-filter。` #### activity的各种状态的转换: 当启动一个activity时,首先是在onCreate处进行初始化界面所需要的信息,如:界面的views,buttons,分配的引用变量等;初始化完成后就会调用onStart,此时就可以看到界面了;当用户与界面进行交互时就会调用onResume函数;当此activity因为被其他activity没有完全覆盖而进入pause状态时就调用 onPause,当被其他activity完全覆盖时就调用onStop函数。在后面的这两种情况下都会调用onSaveInstanceState 方法来暂时保存被覆盖的activity的状态,在这些被覆盖的activity重新回到界面上的时候会恢复这些状态;当调用finish方法使,这个 activity就被destroy了,也就从栈中移除了。 一个手机应用程序通常包含多个Activities。每个Activity的设计,都是为了完成某种明确的功能及跳转到其他应用程序的Activity。比如,一个邮件收发应用程序,有一个Title的列表Activity,当点击列表标题时,跳转到另外一个Activity去显示邮件内容。 一个Activity中,也可以去打开另外一个在同一设备上的其他应用程序的Activity。比如,当你发送邮件时,你发出一个发送邮件的意图Intent,这个Intent包含了邮件地址,邮件消息。另外一个发送邮件应用程序定义了接收这个意图Intent的处理方式,那么就可以打开发送邮件的应用程序进行邮件发送了。如果多个应用程序支持处理这个意图Intent,那么系统会提示选择其中一个执行。当你发送完成之后,就会回到你自己的应用程序,这样看起来就好比邮件发送应用程序是你自己应用程序中的一部分。不管怎么样,毕竟是不同应用程序的Activities,是Android系统的Task让用户感知完全不一样了。 Task是一个Activities的收集器,专门收集用户操作交互所打开的Activity。这些Activities都被安排在一个回收栈back stack中,安排的顺序和它们打开的顺序一致。即先打开的安排在最底部,最后一个打开的安排在顶部。 设备的主屏幕就是为了放置更多的任务。当用户点击某个应用程序图标打开一个应用时,那么这个任务就处于前端。如果这个应用程序之前未被打开过,就会创建一个新的任务Task。 当当前的Activity打开其他Activities时,新打开的Activity处于back stack的最顶端并处于用户获取焦点状态,当前的Activity被保存置于stack中,处于stopped状态。当用户点击BACK键时,stack最顶部的Activity被销毁,前一个Activity被恢复。Back stack的操作遵循“后进先出”的原则。 ![Android 开发指南(一) 任务Task及回收栈back stack介绍 - Android 鱼 - Android 鱼](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a9534dd16.jpg) 如果用户继续按返回,那么在栈中所有的activity都会被弹出,直到用户返回到主屏幕(或者到该任务开始的地方)。当所有的activity都从栈中移除后,任务就不复存在了。 一个Task任务是一个完整的单元,它可以运行于后台。当用户开启一个新的任务Task,或者通过HOME键跳到主屏幕时,这个任务就处于后台运行,不过这个Task包含所有的Activities都处于Stopped状态。但back stack维护者这个任务的完整数据,只是简单的被其他task替换焦点而已。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-01_565da699b8109.jpg) 图2.两个任务:任务B到了前台,任务A于是被打入后台,伺机恢复. 举个例子,有三个activity在当前任务(任务A)的栈中--其中两个在当前activity的下面。这时,用户按下Home键回到主屏幕,然后启动了一个新的应用。当显示主屏幕时,任务A进入后台。当新应用启动时,系统为该应用启动了一个新任务(任务B)。当用户与该应用交互完毕之后,重新回到主界面并且选择任务A的应用。这时,任务A回到前台--栈中的三个activity都原封未动并且恢复在栈顶的activity。在这个时候,用户依然可以Home键返回主屏幕,选择任务B的应用图标来切换到任务B(也可以通过最近使用应用列表启动)。这就是android多任务的一个例子。 注:在后台可以同时存在多个任务。但是,如果用户在运行多个后台任务,系统可能会销毁后台activity来回收内存,导致activity的状态丢失。关于这方面内容后面小节讲述。 因为在回退栈中的activity从来不会被重排,如果你的应用允许用户从多个activity启动一个特定的activity,那么会新创建该activity的一个实例并且把它放到放到栈顶。因此,在你的应用中一个activity可能被实例化多次,如下图所示。因此,用户使用回退键返回,那么每个activity的实例会按照被打开的反向顺序被显示。但是,如果你不想把一个activity实例化多次,你可以修改这种行为。关于如何修改,我们稍后会在“任务管理”一节中讨论。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-12-01_565da69aa2484.jpg) 图3.一个activity被实例化多次. 让我们来总结一下activity和任务的默认行为: - 当Activity A启动Activity B,Activity A会停止,但是系统会保存Activity A的状态(例如滚动条位置,编辑框中的文字等)。如果在Activity B时,玩家按返回键,会使用保存的状态恢复Activity A。 - 当用户按下Home键离开一个任务,当前的Activity会被停止并且当前任务会进入后台。系统保存该任务中所有Activity的状态。如果用户通过启动图标再次启动该任务,该任务会回到前台并且恢复栈顶端的Activity。 - 如果用户按下回退键,当前的Activity会从栈中弹出并且销毁。栈中的前一个Activity被恢复。当一个Activity被销毁时,系统不会保存该Activity的状态。 - Activity会被实例化多次,即使是由其他任务启动的。 #### 保存Activity的状态 如前一节所述,系统默认下会在activity停止的时候保存其状态。如此一来,当用户导航到前一个activity时,其用户介面显示得跟离开时一样。然后,你可以—并且应该—提前使用你的activity的回调方法们保持它的状态,因为activity可能会被销毁然后被重新创建。当系统停止了你的一个activitie(比如当新的activity启动或任务被移到后台),系统可能为了释放内存会完全销毁那个activity。当这种情况发生时,activity的状态就会丢失。如果真发生了这种现象,系统依然知道那个activity在后退栈中占有一个位置,但是当activity被弄到前台时,系统必须重新创建它(而不是仅仅恢复它)。为了避免丢掉用户的工作,你应该通过实现activity的onSaveInstanceState() 来提前保存状态. #### 如何进行任务管理 刚才说明的那些行为,是activity和任务的默认行为。Android管理任务和后退栈的方式,如前面文章所述是通过把所有接连启动的 activity放在同一个任务中并且是同一个后进先出的栈中完成的。你不必要去担心你的Activites如何与Task任务进行交互,它们是如何存在于back stack中。但是,你也许想改变这种正常的管理方式。比如,你希望你的某个Activity在一个新的任务中进行管理,或者你只想对某个Activity实例化,又或者你想在用户离开任务时清理Task中的所有Activities,除了根Activity。 要完成这些特殊的管理方式,activity和任务的关联,activity在任务中的行为,受控于启动activity的行为对象的标志位和清单文件中的[<activity>](http://developer.android.com/guide/topics/manifest/activity-element.html) 元素的属性的互相作用。 1. 在manifest文件中,对接收请求(即Intent对象)的activity组件设置一些属性。 1. 在发送的请求(即Intent对象)中设置一些标记。 所以在请求者和接收者中都可以进行控制。 Activity在manifest可配置的属性有: - taskAffinity - launchMode - allowTaskReparenting - clearTaskOnLaunch - alwaysRetainTaskState - finishOnTaskLaunch Intents 标识有: - FLAG_ACTIVITY_NEW_TASK - FLAG_ACTIVITY_CLEAR_TOP - FLAG_ACTIVITY_SINGLE_TOP 注意:大多数应用不应改变activity和任务的默认行为。如果你确定必须要改变默认行为,你必需小心并且保证测试了activity在启动时和后退导航到它时的可用性。确保测试了与用户习惯相冲突的导航行为。 #### 定义启动模式 你可以通过定义运行模式来定义Activities如何与Task进行交互。定义的两种方式如下: 1.使用manifest文件 当你在你的manifest文件中声明一个activity时,你可以指定activity在启动时如何与任务相关联. 2.使用Intent的flag 当你调用startActivity()时,你可以在Intent中包含指明新的activity如何(或是否)与当前栈关联的flag. 例子:Activity A 启动 Activity B。如果B在manifest中定义了运行模式,并且A在启动B时,也在Intent中指定了B的运行模式,那么A在Intent的定义将覆盖B在manifest中的定义。 注:有些运行模式在manifest中定义有效未必在Intent中也有效,同样,在Intent定义有效的运行模式在manifest中未必生效。 ##### 如何使用manifest配置管理 在manifest配置文件中,你可以通过<activity>元素的的**launchMode**属性来指定4中不同的运行模式,指定activity如何与一个任务关联。launchMode属性指明了activity如何启动到一个任务中去。有四种不同的启动模式你可以用于指定给launchMode属性: 1. Standard:标准默认模式 > 在这种默认模式下,Activity可以被多次实例化,也可以运行在多个Task中,一个Task可以拥有多个Activity实例。activity可以被多次实例化,每个实例可以属于不同的任务,也可以属于同一个任务. 2. singleTop > 在这种模式下,如果一个Activity实例已经存在于当前Task的最顶部,那么系统将调用onNewIntent()方法路由到这个实例,而不是创建一个新的Activity实例。 > 一个Activity可以被实例化多次,且可以从属于不同的Task任务,并且一个任务中可以存在多个Activity实例(这情况仅仅存在于Activity实例不在Task任务的顶端)。 > 假设一个任务的后退栈中有根ActivityA-B-C-D,D位于顶端:再开启D,back stack中的情形: > 在标准模式下,一个新的类的实例被启动并且栈变为 A-B-C-D-D > 在singleTop模式,那么这个已存在的ActivityD就通过onNewIntent()接收到intent,因为它在栈的顶端—栈于是依然保持为A-B-C-D > 如果开启B > 则在singleTop模式下为 A-B-C-D-B > 注:当一个新的activity的实例被创建,用户可以按下后退键回到上一个activity。但当一个已存在的activity实例处理了一个新intent,用户就不能按下后退键回到当前的activity在intent来之前的状态. 3. singTask > 这种模式下,系统创建一个新的Task,并在Task的底部实例化Activities。然而,当一个activity的实例存在于另一个独立的Task时,系统不是去创建一个新的实例,而是调用onNewIntent()路由到其他任务的实例。在同一时间,只存在一个Activity实例。 > 注:尽管activity在一个新任务中启动,后退键依然可以返回到上一个activity. 4. singInstance > 于singTask相似,唯独一点不同的是,这个实例只能在一个单独的Task中使用。activity永远是任务的唯一;任何由这个activity启动的其它activity都在另一个任务中打开. 接上文,关于后退栈,先举个例子: Android浏览器应用声明网页浏览activity必须在它自己的任务中打开—通过在<activity>元素中指定 singleTask启动模式。这表示如果你的应用发出一个intent来打开Android浏览器,它的activity不会放到你的应用所在的任务中。代替的是,可能一个新的任务为浏览器启动,或者,如果浏览器已经运行于后台,它所在的任务就被弄到前台并接受这个intent。 不论一个从一个新任务启动activity还是在一个已存在这种activity的任务中启动,后退键总是能后退到前一个activity。然而,如果你在任务A中启动一个声明为singleTask模式的activity,而这个activity可能在后台已有一个属于一个任务(任务B)的实例。任务B于是被弄到前台并处理这个新的intent。那么此时后退键会先一层层返回任务BActivity,然后再返回到任务A的顶端activity。图 4演示了这种情形. ![Android Task和Back Stack详解 - philn - IT基础知识](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a953686c7.gif) 图4.演示一个"singleTask"启动模式的acitvity如何被添加到一个后退栈中.如果这个activity已经是一个后台任务(任务B)自己的栈的一部分,那么整个后退栈被弄到前台,位于当前任务 (任务A)的上面. 注:你使用launchMode属性的指定的actvitiy的行为可以被intent的flag覆盖. ##### 如何使用Intent配置管理 在启动Activity时,你可以通过传递一个Intent入参给startActivity()方法,来实现与manifest配置类似功能,改变Activity在task中的行为。 1.FLAG_ACTIVITY_NEW_TASK 在新的任务中启动activity-即不在本任务中启动。如果一个包含这个activity的任务已经在运行,那个任务就被弄到前台并恢复其UI状态,然后这个已存在的activity在onNewIntent()中接收新的intent。 这个标志产生与"singleTask"相同的行为。 2.FLAG_ACTIVITY_SINGLE_TOP 如果正启动的activity就是当前的activity(位于后退栈的顶端),那么这个已存在的实例就接收到onNewIntent()的调用,而不是创建一个新的实例。 这产生与"singleTop"模式相同的行为. 3.FLAG_ACTIVITY_CLEAR_TOP 如果要启动的activity已经在当前任务中运行,那么在它之上的所有其它的activity都被销毁掉,然后这个activity被恢复,而且通过onNewIntent(),initent被发送到这个activity(现在位于顶部了) 没有launchMode属性值对应这种行为。 FLAG_ACTIVITY_CLEAR_TOP多数时候与FLAG_ACTIVITY_NEW_TASK联用。可以达到这样的效果:找到在其他Task中存在的Activity,并将它放置到一个可以相应Intent的地方。如果是standard模式,那么它将从stack移除,并新建一个Activity去相应Intent,因为这种模式下,总是新建Activity。 注:如果Activity的启动模式是"standard",FLAG_ACTIVITY_CLEAR_TOP会导致已存在的activity被从栈中移除然后在这个位置创建一个新的实例来处理到来的intent.这是因为"standard"模式会导致总是为新的intent创建新的实例. #### 启动模式的区别 哪个任务存放着activity,用来对行为进行响应。**对“standard ”和“singleTop ”模式来说,这个任务是产生行为(并且调用startActivity() )的那个——除非行为对象包含了 FLAG_ACTIVITY_NEW_TASK 标记。在这种情况下,像前面那节Affinities and new tasks  描述的一样,将会选择一个不同的任务。 - **它们是否可以有多个实例。**"standard "和“singleTop ”类型的activity可以被实例化多次。它们可以属于多个任务,一个特定的任务也可以拥有同一个activity的多个实例。 作为比较"singleTask "和"singleInstance "类型的activity只限定有一个实例。因为这些activity是任务的根。这个限制意味着,在设备上不能同时有超过一个任务的实例。 - **是否能有其他的activity在它所在的任务中。"singleInstance "**类型的 activity是它所在任务中唯一的activity。如果它启动了其他的activity,不管那个activity的启动模式如何,它都会加载到一个不同的任务中——好像行为对象中的FLAG_ACTIVITY_NEW_TASK 标记。在其他的方面,"singleInstance " 和"singleTask "模式是相同的。 其他三种模式运行任务中有多个activity。"singleTask "总是任务中的根activity,但是它可以启动其他的activity并分配到它所在的任务中。"standard "和"singleTop "类型的activity可以出现在任务中的任何地方。 - **是否启动一个新的实例来处理一个新的行为。**对默认的"standard "模式来说,对于每一个行为都会创建一个新的实例来响应。每个实例只处理一个行为。对于"singleTop "模式,如果一个已经存在的实例位于目标任务activity栈的栈顶,那么他将被重用来处理这个行为。如果它不在栈顶,它将不会被重用,而是为行为创建一个新的实例,并压入栈中。 #### 处理任务亲和关系 默认情况下,一个应用程序中的activity组件彼此之间是亲属关系,也就是说它们属于同一个任务栈。但是我们可以通过设置某个<activity>标签的taskAffinity属性来为这个activity组件设置亲属关系。在不同的应用程序中定义的 activity组件可以共用同一个亲属关系,或者在同一个的应用程序中定义的activity组件可以使用不同的亲属关系。 亲属关系会在两种情况下发挥作用: 1)负责激活activity组件的Intent对象中包含了FLAG_ACTIVITY_NEW_TASK标志。 在默认情况下,新建一个Activity,会调用startActivity()方法进入Task当中。它放置到和启动它的Activity相同的back stack。但是,如果启动的Intent包含了FLAG_ACTIVITY_NEW_TASK标识,系统将为这个Activity寻找一个不同的Task。通常是新建一个新的Task。但是也未必全是这样,如果存在一与之相同taskAffinity定义的Task,那么这个Activity将运行在那里,否则新建Task。 2)被激活的activity组件的allowTaskReparenting属性被设置为“true”。 若一个activity组件的allowTaskReparenting被置为“true”,则当与这个activity有相同的亲属关系的任务栈被切换到前台的时候,这个activity会从当前存在的任务栈中移动到与其有相同的亲属关系的任务栈中。 举个例子说明这个属性的作用: 假设一个选择城市查看天气的Activity是一个旅游应用程序的一部分。这个Activity与这个应用中的其他Activity有相同affinity,并且设置了这个属性为true。当你的其他Activity启动这个查看天气的Activity时,它和你的Activity属于同一个Task。但是,当你的旅游应用程序再次展现在前端时,这个查看天气的Activity会重新分配到旅游应用程序的Task中,并显示天气情况。 若从用户的角度来看,一个.apk文件包含了一个以上的“应用程序”,那你可能要为那些activity组件指定不同的亲属关系。 #### 如何清理后退栈back stack 如果一个用户离开一个Task很长时间,系统会清理这个Task,当然除了根Activity。当用户回来时,只剩下这个根Activity,其他的都被销毁了。系统之所以这样做,是因为经过一大段时间之后,用户很可能已抛弃掉他们已经做的并且回到任务开始做一些新的事情。 以下属性可以设置并改变这种行为方式: - alwaysRetainTaskState 如果在Task的跟Activity中设置这个属性为true,默认的行为将不再发生,所有Activity的状态数据将永久保存。 - clearTaskOnLaunch 如果在Task的跟Activity中设置这个属性为true,当用户离开或者回到这个Task时,都会清理除了根Activity之外的Activity。换句话说,它是与alwaysRetainTaskState反着来的.用户回到任务时永远见到的是初始状态,即使只离开了一小会。 - finishOnTaskLaunch 类似clearTaskOnLaunch ,但只在一个单独的Activity中生效,而不是整个Task,它可以清理任何的Activity,包括跟Activity。你为当期会话保存Activity,当用户回来或者离去,都不将再恢复呈现。 #### 启动一个task 通过将一个activity类型的intent-filter的动作设置为“android.intent.action.MAIN”,类别设置为 “android.intent.category.LAUNCHER”可以使这个activity实例称为一个任务栈的入口。拥有这种类型的 intent-filter的activity类型的图表和名字也会显示在application launcher中。 例如: ~~~ <activity... > <intent-filter... > <actionandroid:name="android.intent.action.MAIN" /> <categoryandroid:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> ~~~ 一个intent类型的过滤器导致activity的一个图标和标签被显示于应用启动界面上,使得用户可以启动这个activity并且再次回到这个任务。第二个能力是很重要:用户必须能够使一个任务栈切换到后台,也可以随时将其切换到前台。出于这个原因,使activity在启动时新开任务栈的启动模式(即 “singleTask”和“singleInstance”模式)只应该被利用在拥有拥有“android.intent.action.MAIN”动作和“android.intent.category.LAUNCHER”类别的intent-filter的activity类型上。 想像一下如果没有这两个过滤器会发生什么:一个行为启动了"singleTask"模式的activity,启动了一个新的任务并且用户花了一些时间在这个任务上。然后用户按下了HOME键,这个任务被隐藏到了后台。因为没有在应用程序加载器上显示它,所以就没有办法返回到这个任务。 一个类似的麻烦事 FLAG_ACTIVITY_NEW_TASK 标志。如果这个标志导致activity启动了一个新任务,并且用户按下HOME键离开了它,必须有一些方法将用户引导回它。一些实体(像是通知管理器) 总是在一个外部的任务中启动activity,而不作为它们的一部分,所以他们总是将带有FLAG_ACTIVITY_NEW_TASK 标记的行为对象传递到startActivity() 。如果你有一个可以被外部实体使用这个标签调用的activity,要注意用户应该有办法返回到启动的任务。 但遇到那些你不希望用户能够回到一个activity的情况时怎么办呢?有办法:设置<activity>元素的finishOnTaskLaunch属性为"true"! 参考: [http://philn.blog.163.com/blog/static/10401475320135144639141/](http://philn.blog.163.com/blog/static/10401475320135144639141/ "http://philn.blog.163.com/blog/static/10401475320135144639141/") [http://blog.csdn.net/wbw1985/article/details/4916909](http://blog.csdn.net/wbw1985/article/details/4916909 "http://blog.csdn.net/wbw1985/article/details/4916909") [http://blog.csdn.net/oracleot/article/details/19036909](http://blog.csdn.net/oracleot/article/details/19036909 "http://blog.csdn.net/oracleot/article/details/19036909") [http://blog.163.com/cazwxy_12/blog/static/898763720122992149143/](http://blog.163.com/cazwxy_12/blog/static/898763720122992149143/ "http://blog.163.com/cazwxy_12/blog/static/898763720122992149143/")
';

知识点:Android控件系列之对话框AlertDialog.Builder

最后更新于:2022-04-01 16:16:03

我们在平时做开发的时候,免不了会用到各种各样的对话框,相信有过其他平台开发经验的朋友都会知道,大部分的平台都只提供了几个最简单的实现,如果我们想实现自己特定需求的对话框,大家可能首先会想到,通过继承等方式,重写我们自己的对话框。当然,这也是不失为一个不错的解决方式,但是一般的情况却是这样,我们重写的对话框,也许只在一个特定的地方会用到,为了这一次的使用,而去创建一个新类,往往有点杀鸡用牛刀的感觉,甚至会对我们的程序增加不必要的复杂性,对于这种情形的对话框有没有更优雅的解决方案呢? 幸运的是,android提供了这种问题的解决方案,刚开始接触android的时候,我在做一个自定义对话框的时候,也是通过继承的方式来实现,后来随着对文档了解的深入,发现了android起始已经提供了相应的接口Dialog Builder ,下面我就吧相关的内容在这里分享一下,也能让更多的初学者少走弯路。 ### 弹出一个消息框 首先是一个最简单的应用,就是弹出一个消息框,在android中可以这样实现: ~~~ new AlertDialog.Builder(self) .setTitle("标题") .setMessage("简单消息框") .setPositiveButton("确定", null) .show(); ~~~ 效果如下: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a9525fc23.png "Android详细的对话框AlertDialog.Builder使用方法") 上面的代码中我们新建了一个AlertDialog,并用Builder方法形成了一个对象链,通过一系列的设置方法,构造出我们需要的对话框,然 后调用show方法显示出来,注意到Builder方法的参数 self,这个其实是Activity对象的引用,根据你所处的上下文来传入相应的引用就可以了。例如在onCreate方法中调用,只需传入this即 可。 ### 带确认和取消按钮的对话框 ~~~ new AlertDialog.Builder(self) .setTitle("确认") .setMessage("确定吗?") .setPositiveButton("是", null) .setNegativeButton("否", null) .show(); ~~~ ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a95272de6.png "Android详细的对话框AlertDialog.Builder使用方法") 注意到,这里有两个null参数,这里要放的其实是这两个按钮点击的监听程序,由于我们这里不需要监听这些动作,所以传入null值简单忽略掉,但是实际开发的时候一般都是需要传入监听器的,用来响应用户的操作。 ### 可以输入文本的对话框 ~~~ new AlertDialog.Builder(self) .setTitle("请输入") .setIcon(android.R.drawable.ic_dialog_info) .setView(new EditText(self)) .setPositiveButton("确定", null) .setNegativeButton("取消", null) .show(); ~~~ #### ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a95282cbd.png "Android详细的对话框AlertDialog.Builder使用方法") 如上代码,我们用setView方法,为我们的对话框传入了一个文本编辑框,当然,你可以传入任何的视图对象,比如图片框,WebView等。。尽情发挥你的想象力吧~ ### 单选框 ~~~ new AlertDialog.Builder(self) .setTitle("请选择") .setIcon(android.R.drawable.ic_dialog_info) .setSingleChoiceItems(new String[] {"选项1","选项2","选项3","选项4"}, 0, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } } ) .setNegativeButton("取消", null) .show(); ~~~ `` ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a9529218c.png "Android详细的对话框AlertDialog.Builder使用方法") ### 多选框 ~~~ new AlertDialog.Builder(self) .setTitle("多选框") .setMultiChoiceItems(new String[] {"选项1","选项2","选项3","选项4"}, null, null) .setPositiveButton("确定", null) .setNegativeButton("取消", null) .show(); ~~~ ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a952b72a5.png "Android详细的对话框AlertDialog.Builder使用方法") 单选和多选对话框应该是我们平时用的非常多的,代码应该很好理解 ### 列表对话框: ~~~ new AlertDialog.Builder(self) .setTitle("列表框") .setItems(new String[] {"列表项1","列表项2","列表项3"}, null) .setNegativeButton("确定", null) .show(); ~~~ #### ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a95309fba.png "Android详细的对话框AlertDialog.Builder使用方法") ### 在对话框中显示图片 ~~~ ImageView img = new ImageView(self); img.setImageResource(R.drawable.icon); new AlertDialog.Builder(self) .setTitle("图片框") .setView(img) .setPositiveButton("确定", null) .show(); ~~~ ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a9531ed3a.png "Android详细的对话框AlertDialog.Builder使用方法") 我们传入了一个ImageView来显示图片,这里显示了一个经典的android小绿人图标~ ~,当然这里还可以放上网络图片,具体的实现方法就不介绍了,留给大家来练习吧~ 最后总结一下,android平台为我们开发提供了极大的便利,DialogBuilder能做的不止这些,这里给大家展示的只是冰山一角,我们可以尽情的发挥想象,创造我们自己的对话框。 参考: [http://www.cnblogs.com/Gaojiecai/archive/2011/12/10/2283156.html](http://www.cnblogs.com/Gaojiecai/archive/2011/12/10/2283156.html "http://www.cnblogs.com/Gaojiecai/archive/2011/12/10/2283156.html")
';

异常处理:android.os.NetworkOnMainThreadException–多线程问题

最后更新于:2022-04-01 16:16:00

一切搞定,以为高枕无忧了,结果还是有问题! log开始报错了,获取更新信息异常!!!debug一下,发现Exception:android.os.NetworkOnMainThreadException 这个异常大概意思是在主线程访问网络时出的异常。 Android在4.0之前的版本 支持在主线程中访问网络,但是在4.0以后对这部分程序进行了优化,也就是说访问网络的代码不能写在主线程中了。 查看网上的解决方法,在Android中实现异步任务机制有两种方式,Handler和AsyncTask。 > Handler模式需要为每一个任务创建一个新的线程,任务完成后通过Handler实例向UI线程发送消息,完成界面的更新,这种方式对于整个过程的控制比较精细,但也是有缺点的,例如代码相对臃肿,在多个任务同时执行时,不易对线程进行精确的控制。 为了简化操作,Android1.5提供了工具类android.os.AsyncTask,它使创建异步任务变得更加简单,不再需要编写任务线程和Handler实例即可完成相同的任务。 这里我们采用AsyncTask的处理方式。 ### 先来看看AsyncTask的定义: public abstract class AsyncTask<Params, Progress, Result> { 三种泛型类型分别代表“启动任务执行的输入参数”、“后台任务执行的进度”、“后台计算结果的类型”。在特定场合下,并不是所有类型都被使用,如果没有被使用,可以用java.lang.Void类型代替。 ### AsyncTask的执行分为四个步骤 每一步都对应一个回调方法,这些方法不应该由应用程序调用,开发者需要做的就是实现这些方法。 1) 子类化AsyncTask 2) 实现AsyncTask中定义的下面一个或几个方法 onPreExecute(), 该方法将在执行实际的后台操作前被UI thread调用。可以在该方法中做一些准备工作,如在界面上显示一个进度条。 doInBackground(Params...), 将在onPreExecute 方法执行后马上执行,该方法运行在后台线程中。这里将主要负责执行那些很耗时的后台计算工作。可以调用 publishProgress方法来更新实时的任务进度。该方法是抽象方法,子类必须实现。 onProgressUpdate(Progress...),在publishProgress方法被调用后,UI thread将调用这个方法从而在界面上展示任务的进展情况,例如通过一个进度条进行展示。 onPostExecute(Result), 在doInBackground 执行完成后,onPostExecute 方法将被UI thread调用,后台的计算结果将通过该方法传递到UI thread. ### 为了正确的使用AsyncTask类,以下是几条必须遵守的准则: 1) Task的实例必须在UI thread中创建 2) execute方法必须在UI thread中调用 3) 不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params...), onProgressUpdate(Progress...)这几个方法 4) 该task只能被执行一次,否则多次调用时将会出现异常 doInBackground方法和onPostExecute的参数必须对应,这两个参数在AsyncTask声明的泛型参数列表中指定,第一个为 doInBackground接受的参数,第二个为显示进度的参数,第第三个为doInBackground返回和onPostExecute传入的参数。 ### 下面是项目中具体的处理方法: ~~~ package com.liuhao.mobilesafe.engine; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import android.content.Context; import android.os.AsyncTask; import com.liuhao.mobilesafe.domain.UpdateInfo; public class UpdateInfoService { private Context context; //应用程序环境的上下文信息 public UpdateInfoService(Context context) { this.context = context; } // 将与网络通信的过程封装在ServiceInBackGround的doInBackground方法中 private class ServiceInBackGround extends AsyncTask<Integer, Void, UpdateInfo>{ @Override protected UpdateInfo doInBackground(Integer... params) { String path = context.getResources().getString(params[0]); //根据urlId获取资源文件中对应的内容 UpdateInfo info = new UpdateInfo(); URL url; try { url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(20000); conn.setRequestMethod("GET"); InputStream is = conn.getInputStream(); //得到url对应的文件流,应该是xml文件流,需要对其进行解析 info = UpdateInfoParser.getUpdateInfo(is); //对xml文件流进行解析,获取到更新信息实体 } catch (Exception e) { e.printStackTrace(); } return info; } @Override protected void onPostExecute(UpdateInfo result) { super.onPostExecute(result); } } /** * @param urlId * 服务器资源路径对应的id * @return 更新信息 * @throws Exception */ public UpdateInfo getUpdateInfo(int urlId) throws Exception { // new serviceInBackGround().execute(urlId).get(); return new ServiceInBackGround().execute(urlId).get(); } } ~~~ 最后需要说明AsyncTask不能完全取代线程,在一些逻辑较为复杂或者需要在后台反复执行的逻辑就可能需要线程来实现了。 异常都解决后,再次运行程序: 点击进入程序,会弹出升级对话框: [![Screenshot_2014-09-28-15-24-56](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a951ececb.jpg "Screenshot_2014-09-28-15-24-56")](http://img.blog.csdn.net/20140928161730904) 查看日志: [![image](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a9520baff.jpg "image")](http://img.blog.csdn.net/20140928161552828) 用户点击确定: [![image](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a9522329e.jpg "image")](http://img.blog.csdn.net/20140928161553718) 点击取消: [![image](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a952366fe.jpg "image")](http://img.blog.csdn.net/20140928161733680) 参考: [http://www.cnblogs.com/dawei/archive/2011/04/18/2019903.html](http://www.cnblogs.com/dawei/archive/2011/04/18/2019903.html "http://www.cnblogs.com/dawei/archive/2011/04/18/2019903.html") [http://daoshud1.iteye.com/blog/1843655](http://daoshud1.iteye.com/blog/1843655 "http://daoshud1.iteye.com/blog/1843655")
';

手机安全卫士03:获取更新的服务器配置,显示更新对话框

最后更新于:2022-04-01 16:15:58

### 配置应用程序在手机桌面显示的名称和图标-AndroidManifest.xml: ~~~ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.liuhao.mobilesafe" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="19" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher"《在程序管理列表中显示的图标》 ① android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:icon="@drawable/icon5"《在桌面显示为自己配置的icon5图标》 ② android:name="com.liuhao.mobilesafe.ui.SplashActivity" android:label="@string/app_name" >《名称为自己配置的app_name》 ② <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> ~~~ 配置后,显示如图: ① [![image](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a95037b7d.jpg "image")](http://img.blog.csdn.net/20140926150151180) [![image](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a9504848c.jpg "image")](http://img.blog.csdn.net/20140926150010078)   ②[![image](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a9505c2d2.jpg "image")](http://img.blog.csdn.net/20140926150201164) ### 获取更新的服务器配置流程: [![手机安全卫士](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a950a1ae9.jpg "手机安全卫士")](http://img.blog.csdn.net/20140926150020046) ### 服务器配置: 以tomcat作为服务器,在TOMCAT_HOME/ROOT目录下新建update.xml文件,在其中添加新版本的相关信息; ~~~ <?xml version="1.0" encoding="utf-8"?> <info> <version>2.0</version> <description>亲,最新的版本,速度来下载!</description> <apkurl>http://localhost:18081/newapk.apk</apkurl> </info> ~~~ 在浏览器访问:[http://localhost:18081/update.xml](http://localhost:18081/update.xml "http://localhost:18081/update.xml") [![image](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a950bc9cc.jpg "image")](http://img.blog.csdn.net/20140926150204066) ### xml配置文件的获取和解析 那么我们的应用程序启动时就要尝试到上述地址获取新版本的信息,同时对xml配置文件进行解析。 #### 那么应用程序如何获取到上述的版本信息的地址呢?一般在资源文件中以配置文件的方式存储。 ~~~ config.xml <?xml version="1.0" encoding="utf-8"?> <resources> <string name="updateurl">http://192.168.1.123:18081/update.xml</string> </resources> ~~~ #### 下面要建立update.xml文件对应的实体类-UpdateInfo.java: ~~~ package com.liuhao.mobilesafe.domain; /** * @author liuhao * 升级信息 */ public class UpdateInfo { String version; String description; String apkurl; public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getApkurl() { return apkurl; } public void setApkurl(String apkurl) { this.apkurl = apkurl; } } ~~~ #### 如何获取这个config.xml里url对应的文件内容(即[http://192.168.1.123:18081/update.xml](http://localhost:18081/update.xml "http://localhost:18081/update.xml"))? 新建更新信息服务类:UpdateInfoService.java: ~~~ package com.liuhao.mobilesafe.engine; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import android.content.Context; import com.liuhao.mobilesafe.domain.UpdateInfo; public class UpdateInfoService { private Context context; // 应用程序环境的上下文信息 public UpdateInfoService(Context context) { this.context = context; } /** * @param urlId * 服务器资源路径对应的id * @return 更新信息 * @throws Exception */ public UpdateInfo getUpdateInfo(int urlId) throws Exception { String path = context.getResources().getString(urlId);// 根据urlId获取资源文件中对应的内容 URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setReadTimeout(2000); conn.setRequestMethod("GET"); InputStream is = conn.getInputStream(); //得到url对应的文件流,应该是xml文件流,需要对其进行解析 return UpdateInfoParser.getUpdateInfo(is); } } ~~~ - 知识点:为什么在业务类中不对异常进行捕获,而是直接抛出了? 向外传播给更高层处理,以便异常的错误原因不丢失,便于排查错误或进行捕获处理。对于异常处理,应该从设计、需要、维护等多个角度综合考虑,有一个通用准则:千万别捕获了异常什么事情都不干,这样一旦出现异常了,你没法依据异常信息来排错。 见:[J2EE系统异常的处理准则](http://blog.csdn.net/bruce_6/article/details/39579243) #### 解析xml文件: 获取到xml文件流后,要对其进行解析,使用XmlPullParser: XmlPullParser将xml分解成不同的事件类型(EventType) 常用的有: XmlPullParser.END_DOCUMENT:文档的结束 XmlPullParser.START_DOCUMENT:文档的开始 XmlPullParser.START_TAG:标签的开始 XmlPullParser.END_TAG:标签的结束 XmlPullParser.TEXT :内容 并且该类中的方法主要是用于获取EventType的内容,以及在EventType之间进行跳转。 创建解析更新信息的工具服务类UpdateInfoParser: ~~~ package com.liuhao.mobilesafe.engine; import java.io.InputStream; import org.xmlpull.v1.XmlPullParser; import android.util.Xml; import com.liuhao.mobilesafe.domain.UpdateInfo; public class UpdateInfoParser { /** * @param is xml格式的文件输入流 * @return 解析好的UpdateInfo */ public static UpdateInfo getUpdateInfo(InputStream is) throws Exception{ XmlPullParser parser = Xml.newPullParser(); UpdateInfo info = new UpdateInfo(); // 初始化parser解析器,设置准备对哪个输入流进行解析 // 这个方法会对parser进行重置,同时会将事件类型(event type)定位到文档初始位置(START_DOCUMENT) parser.setInput(is, "utf-8"); int type = parser.getEventType(); //获取当前的EventType while(type != XmlPullParser.END_DOCUMENT){ switch (type) { // 对其中的标签类型进行处理 case XmlPullParser.START_TAG: if("version".equals(parser.getName())){ String version = parser.nextText(); info.setVersion(version); } else if("description".equals(parser.getName())){ String description = parser.nextText(); info.setDescription(description); } else if("apkurl".equals(parser.getName())){ String apkurl = parser.nextText(); info.setApkurl(apkurl); } break; } type = parser.next(); } return info; } } ~~~ ### 测试 #### 不知道Android如何测试? 1、新建一个Android Test Project,将我们的项目放在测试项目中。 [![image](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a950d0f4a.jpg "image")](http://img.blog.csdn.net/20140926163408847)[![image](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a950eef2f.jpg "image")](http://img.blog.csdn.net/20140926163227828) 2、将test项目中AndroidManifest.xml的<uses-library android:name="android.test.runner" />内容和<instrumentation>节点下的内容拷贝到项目的AndroidManifest.xml中,注意节点的对应。 [![image](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a95111481.jpg "image")](http://img.blog.csdn.net/20140926163411327) 之后,test项目便可以暂时不用了。 3、创建测试类 [![image](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a9512b83f.jpg "image")](http://img.blog.csdn.net/20140926163412388) ~~~ package com.liuhao.mobilesafe.test; import junit.framework.Assert; import com.liuhao.mobilesafe.R; import com.liuhao.mobilesafe.domain.UpdateInfo; import com.liuhao.mobilesafe.engine.UpdateInfoService; import android.test.AndroidTestCase; public class TestGetUpdateInfo extends AndroidTestCase { public void testGetInfo() throws Exception{ UpdateInfoService service = new UpdateInfoService(getContext()); UpdateInfo info = service.getUpdateInfo(R.string.updateurl); Assert.assertEquals("2.0", info.getVersion()); } } ~~~ 4、从服务器上获取更新信息的配置文件,需要程序有访问Internet的权限: [![image](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a9514b590.jpg "image")](http://img.blog.csdn.net/20140926163433015) [![image](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a95170790.jpg "image")](http://img.blog.csdn.net/20140928130533450) [![image](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a95185550.jpg "image")](http://img.blog.csdn.net/20140928130534605) 保存,即可。 5、运行测试代码: [![image](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a951af84d.jpg "image")](http://img.blog.csdn.net/20140928130535665) 出现异常!!!connect failed: ECONNREFUSED (Connection refused) #### 异常处理:java.net.ConnectException android 从tomcat读取文件时出现以下异常: 08-10 14:53:09.118: W/System.err(12527): java.net.ConnectException: failed to connect to localhost/127.0.0.1 (port 8080): connect failed: ECONNREFUSED (Connection refused) 解决方法: String url = "[http://localhost:18081/update.xml](http://localhost:18081/update.xml "http://localhost:18081/update.xml")";  修改成 String url = "[http://192.168.1.123:18081/update.xml](http://localhost:18081/update.xml "http://localhost:18081/update.xml")"; 主机ip不能使用localhost或者127.0.0.1,使用本机真实ip地址即可。使用ipconfig命令就可以查看到: [![image](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a951c926e.jpg "image")](http://img.blog.csdn.net/20140928130357812) 异常处理后,运行成功! ### 在activity使用业务 所有的业务代码已经完成,回到splash的activity使用业务! ~~~ package com.liuhao.mobilesafe.ui; import com.liuhao.mobilesafe.R; import com.liuhao.mobilesafe.domain.UpdateInfo; import com.liuhao.mobilesafe.engine.UpdateInfoService; import android.os.Bundle; import android.app.Activity; import android.app.AlertDialog; import android.app.AlertDialog.Builder; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.util.Log; import android.view.Menu; import android.view.Window; import android.view.WindowManager; import android.view.animation.AlphaAnimation; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; public class SplashActivity extends Activity { private static final String TAG = "SplashActivity"; private TextView tv_splash_version; private LinearLayout ll_splash_main; private UpdateInfo info; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //取消标题栏 requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.splash); tv_splash_version = (TextView) this.findViewById(R.id.tv_splash_version); ll_splash_main = (LinearLayout) this.findViewById(R.id.ll_splash_main); String versiontext = getVersion(); tv_splash_version.setText(versiontext); if(isNeedUpdate(versiontext)){ Log.i(TAG, "弹出升级对话框"); showUpdateDialog(); } /* AlphaAnimation类:透明度变化动画类 * AlphaAnimation类是Android系统中的透明度变化动画类,用于控制View对象的透明度变化,该类继承于Animation类。 * AlphaAnimation类中的很多方法都与Animation类一致,该类中最常用的方法便是AlphaAnimation构造方法。 * * public AlphaAnimation (float fromAlpha, float toAlpha) 参数说明 fromAlpha:开始时刻的透明度,取值范围0~1。 toAlpha:结束时刻的透明度,取值范围0~1。 */ AlphaAnimation aa = new AlphaAnimation(0.0f, 1.0f); aa.setDuration(2000); //Animation类的方法,设置持续时间 ll_splash_main.startAnimation(aa); //设置动画 //完成窗体的全屏显示 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); } private void <span style="color:#FF6666;">showUpdateDialog</span>() { //弹出一个消息框 <span style="color:#FF0000;">AlertDialog.Builder builder = new Builder(this);</span> builder.setIcon(R.drawable.icon5); //设置消息框的标题图标 builder.setTitle("升级提醒"); //设置消息框的标题 builder.setMessage(info.getDescription()); //设置要显示的内容 builder.setCancelable(false); //让用户不能按后退键取消 builder.<span style="color:#FF6666;">setPositiveButton</span>("确定", new OnClickListener() { //设置用户选择确定时的按键操作 @Override public void onClick(DialogInterface dialog, int which) { Log.i(TAG, "下载pak文件:" + info.getApkurl()); } }); builder.setNegativeButton("取消", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Log.i(TAG, "用户取消升级,进入程序主界面"); } }); builder.create().show(); } /** * * @param versiontext 当前客户端的版本信息 * @return 是否需要更新 */ private boolean isNeedUpdate(String versiontext) { UpdateInfoService service = new UpdateInfoService(this); try { info = service.getUpdateInfo(R.string.updateurl); String version = info.getVersion(); if(versiontext.equals(version)){ Log.i(TAG, "版本号相同,无需升级,进入到主界面"); return false; } else{ Log.i(TAG, "版本号不同,需要升级"); return true; } } catch (Exception e) { e.printStackTrace(); /** * Toast使用场景 * 1、需要提示用户,但又不需要用户点击“确定”或者“取消”按钮。 * 2、不影响现有Activity运行的简单提示。 */ Toast.makeText(this, "获取更新信息异常", 2).show();//弹出文本,并保持2秒 Log.i(TAG, "获取更新信息异常,进入到主界面"); return false; } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.splash, menu); return true; } /** * 获取当前程序的版本号 * @return */ private String getVersion(){ // 获取一个PackageManager的实例,从而可以获取全局包信息 PackageManager manager = getPackageManager(); try { // Retrieve overall information about an application package that is installed on the system. PackageInfo info = manager.getPackageInfo(getPackageName(), 0); // The version name of this package, as specified by the <manifest> tag's versionName attribute. return info.versionName; } catch (Exception e) { e.printStackTrace(); return "版本号未知"; } } } ~~~ - isNeedUpdate()方法:调用UpdateInfoService 的getUpdateInfo()方法,来获取更新信息。同时将服务器端的版本号和当前客户端的版本号进行对比,并做出是否让用户升级的操作。若发现两个版本号不一致,那么就要提醒用户进行升级: - 这里调用了showUpdateDialog()方法,在这个方法中,设置界面弹出一个消息框,其中有两个按钮:“确定”“取消”,用户点击不同的按钮则对应不同的操作。 #### 异常处理android.os.NetworkOnMainThreadException--多线程问题 一切搞定,以为高枕无忧了,结果还是有问题! log开始报错了,获取更新信息异常!!!debug一下,发现Exception:android.os.NetworkOnMainThreadException 这个异常大概意思是在主线程访问网络时出的异常。 Android在4.0之前的版本 支持在主线程中访问网络,但是在4.0以后对这部分程序进行了优化,也就是说访问网络的代码不能写在主线程中了。 处理方法:[http://blog.csdn.net/bruce_6/article/details/39640587](http://blog.csdn.net/bruce_6/article/details/39640587)
';

知识点:Android控件系列之Toast

最后更新于:2022-04-01 16:15:56

简介:Toast英文含义是吐司,在Android中,它就像烘烤机里做好的吐司弹出来,并持续一小段时间后慢慢消失。 Toast也是一个容器,可以包含各种View,并承载着它们显示。 使用场景: 1、需要提示用户,但又不需要用户点击“确定”或者“取消”按钮。 2、不影响现有Activity运行的简单提示。 用法: 1、可以通过构造函数初始化: ~~~ //初始化Toast Toast toast = new Toast(this); //设置显示时间,可以选择Toast.LENGTH_LONG或者Toast.LENGTH_SHORT toast.setDuration(Toast.LENGTH_LONG); //承载一个TextView,用来显示文字 TextView view = new TextView(this); //设置TextView的值 view.setText("这是一个Toast提示"); //设置TextView的布局 view.setLayoutParams(new LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,LinearLayout.LayoutParams.WRAP_CONTENT)); //Toast承载该 TextViewtoast.setView(view); //显示Toast toast.show(); ~~~ 2、上述的方法过于复杂,一般只适用于构造特殊界面的Toast,如果只想单纯的进行文字提示,可以用工厂方法,它会自动构建一个带边框和文字的Toast: ~~~ //利用工厂方法构造一个简单的Toast,并链式结构的直接进行提示 Toast.makeText(this, "这是一个Toast提示", Toast.LENGTH_LONG).show(); ~~~ 总结:Toast可以说是最常用也是最简单的Android控件之一,其自动关闭的功能大大简化了代码量,不失为用户提示的最佳选择。 **1. 默认的显示方式** ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a94fc0c64.jpg) ~~~ // 第一个参数:当前的上下文环境。可用getApplicationContext()或this // 第二个参数:要显示的字符串。也可是R.string中字符串ID // 第三个参数:显示的时间长短。Toast默认的有两个LENGTH_LONG(长)和LENGTH_SHORT(短),也可以使用毫秒如2000ms Toast toast=Toast.makeText(getApplicationContext(), "默认的Toast", Toast.LENGTH_SHORT); //显示toast信息 toast.show(); ~~~ **2. 自定义显示位置** ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a94fd5ac4.jpg) ~~~ Toast toast=Toast.makeText(getApplicationContext(), "自定义显示位置的Toast", Toast.LENGTH_SHORT); //第一个参数:设置toast在屏幕中显示的位置。我现在的设置是居中靠顶 //第二个参数:相对于第一个参数设置toast位置的横向X轴的偏移量,正数向右偏移,负数向左偏移 //第三个参数:同的第二个参数道理一样 //如果你设置的偏移量超过了屏幕的范围,toast将在屏幕内靠近超出的那个边界显示 toast.setGravity(Gravity.TOP|Gravity.CENTER, -50, 100); //屏幕居中显示,X轴和Y轴偏移量都是0 //toast.setGravity(Gravity.CENTER, 0, 0); toast.show(); ~~~ **3. 带图片的** ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a94fe6a4e.jpg) ~~~ Toast toast=Toast.makeText(getApplicationContext(), "显示带图片的toast", 3000); toast.setGravity(Gravity.CENTER, 0, 0); //创建图片视图对象 ImageView imageView= new ImageView(getApplicationContext()); //设置图片 imageView.setImageResource(R.drawable.ic_launcher); //获得toast的布局 LinearLayout toastView = (LinearLayout) toast.getView(); //设置此布局为横向的 toastView.setOrientation(LinearLayout.HORIZONTAL); //将ImageView在加入到此布局中的第一个位置 toastView.addView(imageView, 0); toast.show(); ~~~ **4. 完全自定义显示方式** ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a95007f8a.jpg) ~~~ //Inflater意思是充气 //LayoutInflater这个类用来实例化XML文件到其相应的视图对象的布局 LayoutInflater inflater = getLayoutInflater(); //通过制定XML文件及布局ID来填充一个视图对象 View layout = inflater.inflate(R.layout.custom2,(ViewGroup)findViewById(R.id.llToast)); ImageView image = (ImageView) layout.findViewById(R.id.tvImageToast); //设置布局中图片视图中图片 image.setImageResource(R.drawable.ic_launcher); TextView title = (TextView) layout.findViewById(R.id.tvTitleToast); //设置标题 title.setText("标题栏"); TextView text = (TextView) layout.findViewById(R.id.tvTextToast); //设置内容 text.setText("完全自定义Toast"); Toast toast= new Toast(getApplicationContext()); toast.setGravity(Gravity.CENTER , 0, 0); toast.setDuration(Toast.LENGTH_LONG); toast.setView(layout); toast.show(); ~~~ **5. 其他线程通过 Handler 的调用** ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a9501c5ff.jpg) ~~~ //调用方法1 //Thread th=new Thread(this); //th.start(); //调用方法2 handler.post(new Runnable() { @Override public void run() { showToast(); } });   ~~~ ~~~ public void showToast(){ Toast toast=Toast.makeText(getApplicationContext(), "Toast在其他线程中调用显示", Toast.LENGTH_SHORT); toast.show(); } ~~~ ~~~ Handler handler=new Handler(){ @Override public void handleMessage(Message msg) { int what=msg.what; switch (what) { case 1: showToast(); break; default: break; } super.handleMessage(msg); } }; ~~~ ~~~ @Override public void run() { handler.sendEmptyMessage(1); } ~~~ 参考: [http://www.cnblogs.com/wt616/archive/2011/06/20/Android_Toast.html](http://www.cnblogs.com/wt616/archive/2011/06/20/Android_Toast.html "http://www.cnblogs.com/wt616/archive/2011/06/20/Android_Toast.html") [http://daikainan.iteye.com/blog/1405575](http://daikainan.iteye.com/blog/1405575 "http://daikainan.iteye.com/blog/1405575")
';

手机安全卫士02:splash界面ui

最后更新于:2022-04-01 16:15:53

手机安全卫士项目是跟着黑马的视频做的。 splash是飞洒、飞溅的意思,主要是用于完成一个产品logo显示,期间可以: 1. 后台完成数据库初始化的操作 1. 联网访问服务器,获取服务器最新信息(升级提示) 1. 不同的日期显示出来不同logo,判断当前系统时间,素材一般从服务器上下载下来. 1. 判断时间,根据不同时间显示不同的加载页面 ### 布局文件:splash.xml ~~~ <?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:background="@drawable/logo2" android:gravity="center_horizontal" android:orientation="vertical" android:id="@+id/ll_splash_main" > <TextView android:layout_marginTop="320dip" android:id="@+id/tv_splash_version" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#FFff5f00" android:textSize="20sp" android:text="版本号" /> <ProgressBar android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="20dip" /> </LinearLayout> ~~~ ### activity:SplashActivity ~~~ package com.liuhao.mobilesafe.ui; import com.liuhao.mobilesafe.R; import android.os.Bundle; import android.app.Activity; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.view.Menu; import android.view.Window; import android.view.WindowManager; import android.view.animation.AlphaAnimation; import android.widget.LinearLayout; import android.widget.TextView; public class SplashActivity extends Activity { private TextView tv_splash_version; private LinearLayout ll_splash_main; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //取消标题栏 requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.splash); tv_splash_version = (TextView) this.findViewById(R.id.tv_splash_version); ll_splash_main = (LinearLayout) this.findViewById(R.id.ll_splash_main); String versiontext = getVersion(); tv_splash_version.setText(versiontext); /* AlphaAnimation类:透明度变化动画类 * AlphaAnimation类是Android系统中的透明度变化动画类,用于控制View对象的透明度变化,该类继承于Animation类。 * AlphaAnimation类中的很多方法都与Animation类一致,该类中最常用的方法便是AlphaAnimation构造方法。 * * public AlphaAnimation (float fromAlpha, float toAlpha) 参数说明 fromAlpha:开始时刻的透明度,取值范围0~1。 toAlpha:结束时刻的透明度,取值范围0~1。 */ AlphaAnimation aa = new AlphaAnimation(0.0f, 1.0f); aa.setDuration(2000); //Animation类的方法,设置持续时间 ll_splash_main.startAnimation(aa); //设置动画 //完成窗体的全屏显示 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.splash, menu); return true; } /** * 获取当前程序的版本号 * @return */ private String getVersion(){ // 获取一个PackageManager的实例,从而可以获取全局包信息 PackageManager manager = getPackageManager(); try { // Retrieve overall information about an application package that is installed on the system. PackageInfo info = manager.getPackageInfo(getPackageName(), 0); // The version name of this package, as specified by the <manifest> tag's versionName attribute. return info.versionName; } catch (Exception e) { e.printStackTrace(); return "版本号未知"; } } } ~~~ ### 相关知识点: 在开发程序是经常会需要软件全屏显示、自定义标题(使用按钮等控件)和其他的需求,今天这一讲就是如何控制Android应用程序的窗体显示. 首先介绍一个重要方法那就是requestWindowFeature(featrueId),它的功能是启用窗体的扩展特性。参数是Window类中定义的常量。 一、枚举常量 > 1.DEFAULT_FEATURES:系统默认状态,一般不需要指定 > 2.FEATURE_CONTEXT_MENU:启用ContextMenu,默认该项已启用,一般无需指定 > 3.FEATURE_CUSTOM_TITLE:自定义标题。当需要自定义标题时必须指定。如:标题是一个按钮时 > 4.FEATURE_INDETERMINATE_PROGRESS:不确定的进度 > 5.FEATURE_LEFT_ICON:标题栏左侧的图标 > 6.FEATURE_NO_TITLE:无标题 > 7.FEATURE_OPTIONS_PANEL:启用“选项面板”功能,默认已启用。 > 8.FEATURE_PROGRESS:进度指示器功能 > 9.FEATURE_RIGHT_ICON:标题栏右侧的图标 **效果图: **default: DEFAULT_FEATURES ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a94ee3419.png) progress:FEATURE_PROGRESS:进度指示器功能 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a94f09a4f.png) no title:FEATURE_NO_TITLE:无标题 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a94f24101.png) lefticon:FEATURE_LEFT_ICON:标题栏左侧的图标 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a94f322d8.png) fullscreen: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a94f43bdb.png) indeterminate_progress: FEATURE_INDETERMINATE_PROGRESS:不确定的进度 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a94f57116.png) customtitle:FEATURE_CUSTOM_TITLE:自定义标题。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a94f66de6.png) AlphaAnimation类是Android系统中的透明度变化动画类,用于控制View对象的透明度变化,该类继承于Animation类。AlphaAnimation类中的很多方法都与Animation类一致,该类中最常用的方法便是AlphaAnimation构造方法。 【基本语法】public AlphaAnimation (float fromAlpha, float toAlpha) 参数说明 fromAlpha:开始时刻的透明度,取值范围0~1。 toAlpha:结束时刻的透明度,取值范围0~1。 ### 运行 [![image](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a94f7b244.jpg "image")](http://img.blog.csdn.net/20140925143019612) [![image](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a94f8ba54.jpg "image")](http://img.blog.csdn.net/20140925143020673) 运行效果: [![Screenshot_2014-09-25-14-32-55](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a94fabeb7.jpg "Screenshot_2014-09-25-14-32-55")](http://img.blog.csdn.net/20140925143206534)
';

新建android项目gen目录下未生成R文件

最后更新于:2022-04-01 16:15:51

### 问题描述: 如图: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a94ecf713.jpg) ### 解决方法: 一。解决方案首选重启eclipse,如果还是未解决的话,可以尝试重新编译工程 project-->Clean 二。发现的另一种解决方案,前提是由编译环境改变后引起的R文件消失(jdk1.5更改为jdk1.7) 选中项目右键 Android Tools ---> fix project properties 即可解决 。 三。build Project改成了手动build项目,最新更改回来就Ok了,eclipse工具栏上有个Project里面有个自动build项目(build Automatically),如果没有选就是手动build项目 遇到的问题都解决了,终于可以正式的开始Android之旅了! 参考: http://blog.csdn.net/sjw890821sjw/article/details/8215291
';