首页 > 学院 > 开发设计 > 正文

day7安全卫士

2019-11-07 23:46:29
字体:
来源:转载
供稿:网友

Day07 笔记

自定义组合控件

(1)软件模块介绍

软件管理模块大致可以分为两个部分,第一部分是显示数据存储状态的一些信息,第二部分展示手机中的所有应用程序。如下图所示。

![image](http://olvukblvf.bkt.clouddn.com/2.png)

第一部分的数据存储状态分为内部存储和外部存储(SD卡存储),数据通过自定义组合控件展示出来。 第二部分是应用程序的展示,点击条目后会弹出对此软件的其他的一些操作,如卸载、分享、详情等

(2)软件管理页面布局的实现

创建软件管理activity

新建AppManagerActivity类,继承自activity。然后在清单文件中注册activity,代码如下。

<activity android:name=".activity.AppManagerActivity" />

在布局目录中新建activity_app_manager.xml布局文件,然后activity关联布局文件,实现代码如下。

public class AppManagerActivity extends Activity {@OverridePRotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //关联布局文件 setContentView(R.layout.activity_app_manager); }}

image

进行注册,不要忘记了

组合控件类的创建

image

在view包下,新建ProgressStateView类,继承自自定义组合控件的根布局,也就是布局文件中的LinearLayout。然后实现ProgressStateView两个构造函数,这两个构造函数的特点是确保一个参数的构造函数调用的是两个参数的构造函数,目的是所有的构造函数都走同一处代码。代码实现如下。

public class ProgressStateView extends LinearLayout { public ProgressStateView(Context context) { //调用第二个构造函数 this(context, null); } public ProgressStateView(Context context, AttributeSet attrs) { super(context, attrs); }}

类创建好以后,在两个参数的构造函数中挂载我们的布局文件,代码如下。

public ProgressStateView(Context context, AttributeSet attrs) {super(context, attrs);// 挂载布局文件View.inflate(context, R.layout.view_progress_state, this);

UI分析

软件管理页面大致可以分为三个部分,最上层为title部分;中间层有很多页面都有用到所以这里我们会抽取成自定义控件;最下方是一个listview。UI分析图如下。

image

定义组合控件

组合控件布局的实现

从组合控件的UI上分析,根布局可以用一个水平放置的LinearLayout,左侧放置一个textview,右侧方一个RelativeLayout。RelativeLayout中摆放两个textview和一个progressBar, UI分析图如下所示。

image 组合控件UI分析

在布局文件夹下新建activiyty_app_manager.xml布局文件,根布局采用LinearLayout,然后按照刚才的UI分析,写出相应的布局文件,布局代码如下。

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:heima="http://schemas.android.com/apk/res/com.itheima.mobilesafe" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView style="@style/TitleBarStyle" android:text="软件管理" /> <com.itheima.mobilesafe.view.ProgressStatusView android:id="@+id/psv_rom" android:layout_width="match_parent" heima:psvText="内存:" android:layout_height="wrap_content"/> <com.itheima.mobilesafe.view.ProgressStatusView android:layout_width="match_parent" heima:psvText="SD卡:" android:id="@+id/psv_sd" android:layout_height="wrap_content" /> <ListView android:id="@+id/lv_app_manager" android:layout_width="match_parent" android:layout_height="match_parent" /></LinearLayout>

组合控件类的创建

细节展示

image

在view包下,新建ProgressStateView类,继承自自定义组合控件的根布局,也就是布局文件中的LinearLayout。然后实现ProgressStateView两个构造函数,这两个构造函数的特点是确保一个参数的构造函数调用的是两个参数的构造函数,目的是所有的构造函数都走同一处代码。代码实现如下。

public class ProgressStateView extends LinearLayout {public ProgressStateView(Context context) { //调用第二个构造函数 this(context, null);}public ProgressStateView(Context context, AttributeSet attrs) { super(context, attrs);}}

类创建好以后,在两个参数的构造函数中挂载我们的布局文件,代码如下。

public ProgressStateView(Context context, AttributeSet attrs) { super(context, attrs); // 挂载布局文件 View.inflate(context, R.layout.view_progress_state, this);}

image

组合控件的使用

拷贝组合控件的全路径名,然后找到软件管理页面的布局文件,添加组合控件,代码如下。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:itheima="http://schemas.android.com/apk/res/org.itheima18.safe" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" >

总代码在 组合控件UI分析部分(略)

效果图

image

自定义属性的添加

组合控件的最左侧可以显示不同的文字,这个属性可以在布局文件中定义。

添加属性

自定义属性的第一步是到valus目录下的attrs文件中添加自定义的属性,这里我们要显示文字,所以自定义的类型是String或者是引用类型。 <declare-styleable name="ProgressStateView"> <!—title类型为引用或者String --> <attr name="psvTitle" format="reference|string" /> </declare-styleable>

image

属性的使用

第一步就是要把自己的命名空间添加到布局文件中。方法是把res后的Android改为自己的包名,包名可以在清单文件中获得,然后把命名空间的名称更改为任意一个名称即可,实现代码如下。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:itheima="http://schemas.android.com/apk/res/org.itheima18.safe" //添加部分 android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > ... </LinearLayout>

第二步就是在自定义组合控件中添加自定义属性,属性名称要和定义的属性名称一致,代码例如下。

<org.itheima18.safe.view.ProgressStateView android:id="@+id/am_psv_rom" android:layout_width="match_parent" android:layout_height="wrap_content" itheima:psvTitle="内存:" /> //自定义属性<!-- SD存储 --><org.itheima18.safe.view.ProgressStateView android:id="@+id/am_psv_sd" android:layout_width="match_parent" android:layout_height="wrap_content" itheima:psvTitle="SD卡:" /> //自定义属性

属性值的读取

在ProgressStateView类中要把布局文件中设置的自定义属性值读取出来。读取属性值要通过属性集合对象,obtainStyledAttributes 方法的第一个参数是构造函数中的attrs即属性集,第二个参数是在attrs文件中,我们添加的declare-styleable标签声明的属性集合名称。需要注意的是TypedArray需要回收,否则容易内存泄露.public ProgressStateView(Context context, AttributeSet attrs) {super(context, attrs);// 挂载xmlView.inflate(context, R.layout.view_progress_state, this);// 读取自定义的属性TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ProgressStateView);String title = ta.getString(R.styleable.ProgressStateView_psvTitle);//回收属性集ta.recycle();>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>注意回收}

给控件赋值

已经读取到属性值后,给相应的控件赋值即可,首先要找到相应控件,代码如下

public class ProgressStatusView extends RelativeLayout { private TextView mTvLeft; private TextView mTvRight; private ProgressBar mProgress; public ProgressStatusView(Context context, AttributeSet attrs) { super(context, attrs); //管理布局文件 View view = View.inflate(context, R.layout.progress_status_view, this); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ProgressStatusView); //读取属性 String text = ta.getString(R.styleable.ProgressStatusView_psvText); ta.recycle(); //设置属性值 TextView tvInstall = (TextView) view.findViewById(R.id.tv_app_install); mTvLeft = (TextView) view.findViewById(R.id.tv_left); mTvRight = (TextView) view.findViewById(R.id.tv_right); mProgress = (ProgressBar)view.findViewById(R.id.progress); tvInstall.setText(text); } public ProgressStatusView(Context context) { this(context, null);//保证只调用2个参数构造方法 } public void setRigthText(String text){ mTvRight.setText(text); } public void setLeftText(String text){ mTvLeft.setText(text); } public void setProgress(int progress){ mProgress.setProgress(progress); } }

自定义属性的添加

添加属性

自定义属性的第一步是到valus目录下的attrs文件中添加自定义的属性,这里我们要显示文字,所以自定义的类型是String或者是引用类型。

<!-- 自定义Progress状态View --> <declare-styleable name="ProgressStatusView"> <!-- 属性,文本 --> <attr name="psvText" format="reference|string" /></declare-styleable>

* 使用属性

要使用自己定义的属性,第一步就是要把自己的命名空间添加到布局文件中。方法是把res后的Android改为自己的包名,包名可以在清单文件中获得,然后把命名空间的名称更改为任意一个名称即可,实现代码如下。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:itheima="http://schemas.android.com/apk/res/ com.itheima.mobilesafe" //添加部分android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical" >...</LinearLayout>

image

添加属性

第二步就是在自定义组合控件中添加自定义属性,属性名称要和定义的属性名称一致,代码如下。

<org.itheima18.safe.view.ProgressStateView android:id="@+id/am_psv_rom" android:layout_width="match_parent" android:layout_height="wrap_content" itheima:psvTitle="内存:" /> //自定义属性 <!-- SD存储 --> <org.itheima18.safe.view.ProgressStateView android:id="@+id/am_psv_sd" android:layout_width="match_parent" android:layout_height="wrap_content" itheima:psvTitle="SD卡:" /> //自定义属性读取属性值

在ProgressStatusView类中要把布局文件中设置的自定义属性值读取出来。读取属性值要通过属性集合对象,obtainStyledAttributes 方法的第一个参数是构造函数中的attrs即属性集,第二个参数是在attrs文件中,我们添加的declare-styleable标签声明的属性集合名称。需要注意的是TypedArray需要回收,否则容易内存泄露。

public ProgressStatusView(Context context, AttributeSet attrs) { super(context, attrs); //管理布局文件 挂載XML View view = View.inflate(context, R.layout.progress_status_view, this); TypedArray ta = context.obtainStyledAttributes(attrs(第一個參數), R.styleable.ProgressStatusView(第二個參數)); //读取属性 String text = ta.getString(R.styleable.ProgressStatusView_psvText); //回收 ta.recycle();

image

給控件賦值

已经读取到属性值后,给相应的控件赋值即可,首先要找到相应控件,代码如下。

public class ProgressStatuView extends LinearLayout {private ProgressBar mProgressBar;private TextView mTvTitle;private TextView mTvLeft;private TextView mTvRight;...public ProgressStatuView(Context context, AttributeSet attrs) { super(context, attrs); ... // 读取自定义的属性 ... // 初始化view 設置屬性值 TextView tvInstall = (TextView) view.findViewById(R.id.tv_app_install); mTvLeft = (TextView) view.findViewById(R.id.tv_left); mTvRight = (TextView) view.findViewById(R.id.tv_right); mProgress = (ProgressBar)view.findViewById(R.id.progress); // 赋值 tvInstall.setText(text);}}

自定义控件设置方法的添加

设置的方法需要四个,其中两个是左侧和右侧文本的设置,另外两个方法是分别设置progressBar的进度和最大值,方法比较简单,代码如下。

public ProgressStatusView(Context context) { this(context, null);//保证只调用2个参数构造方法} //设置右侧文本public void setRigthText(String text){ mTvRight.setText(text);} //设置左侧文本public void setLeftText(String text){ mTvLeft.setText(text);} //设置ProgressBar的进度public void setProgress(int progress){ mProgress.setProgress(progress);} //设置ProgressBar的最大值public void setMax(int max) {mProgressBar.setMax(max);}

进度条样式的改变

ProgressBar 有个下列属性用来决定它的样式。

android:progressDrawable="@drawable/progress_horizontal"

引用的文件有固定的格式,文件代码如下。其中第一个条目用来决定背景的颜色;第二个条目用来决定第二层进度的颜色,我们一般只使用第一层进度;第三个条目用来显示第一层进度的颜色 * 樣式一

<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android" > <!--背景的颜色 --> <item android:id="@android:id/background"> <color android:color="@color/progress_bg" /> </item> <!-- 第二层进度的颜色 --> <item android:id="@android:id/secondaryProgress"> <clip> <color android:color="@color/progress_sencodary" /> </clip> </item> <!-- 进度的颜色 --> <item android:id="@android:id/progress"> <clip android:drawable="@color/progress_progress" /> </item> </layer-list>

这里面drawable中引用的颜色要在values目录下的colors文件中定义。

文件大小数据的展示

呃与

初始化数据

内部存储指的是data文件下的存储,首先我们要获得data文件夹,data文件夹可以通过环境的getDataDirectory方法获得。得到文件后可以通过file的getFreeSpace和getTotalSpace来获得剩余控件和总空间,总空间减去剩余控件就得出了已使用空间,实现代码如下。

// 内部存储:/data文件夹File romDir = Environment.getDataDirectory();// data// 剩余空间long romFreeSpace = romDir.getFreeSpace();// 总空间long romTotalSpace = romDir.getTotalSpace();//已使用的空间long romUsedSpace = romTotalSpace - romFreeSpace;

获得的空间大小类型都是long类型,在安卓中可以通过Formatter进行数据的转化,转化后设置相应控件的数据即可。设置进度大小时,进度条最大值默认是100f,所以需要把进度进行转换,在设置转换后的值。

//已用空间大小 mPsvRom.setTextLeft(Formatter.formatFileSize(this, romUsedSpace) + "已用"); //可用空间大小 mPsvRom.setTextRight(Formatter.formatFileSize(this, romFreeSpace)+ "可用"); //设置进度大小 int romProgress = (int) (romUsedSpace * 100f / romTotalSpace + 0.5f); mPsvRom.setProgress(romProgress);

外部存储的展示 外部存储是指SD卡下的mnt文件下的存储。可通过环境的getExternalStorageDirectory获得文件,文件获得后的实现方法就和内部存储实现方法一样了,代码实现如下。

// SD:/mntFile sdDir = Environment.getExternalStorageDirectory();//外部存储剩余空间long sdFreeSpace = sdDir.getFreeSpace();//外部存储总控件long sdTotalSpace = sdDir.getTotalSpace();//外部存储已使用空间long sdUsedSpace = sdTotalSpace - sdFreeSpace;//给控价赋值mPsvSD.setTextLeft(Formatter.formatFileSize(this, sdUsedSpace) + "已用");mPsvSD.setTextRight(Formatter.formatFileSize(this, sdFreeSpace) + "可用");int sdProgress = (int) (sdUsedSpace * 100f / sdTotalSpace + 0.5f);mPsvSD.setProgress(sdProgress);

运行代码后的效果如下图所示。

image

软件管理adapter的完成

从UI上可以看出,软件管理页面最下方是一个listview,UI图如下。

image

所以在布局文件activity_app_manager.xml中添加相应的控件,实现代码如下。

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:heima="http://schemas.android.com/apk/res/com.itheima.mobilesafe"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical" > ... <ListView android:id="@+id/lv_app_manager" android:layout_width="match_parent" android:layout_height="match_parent" > </ListView></LinearLayout>

(其实就是添加了一个ListView)

初始化ListView

在AppManagerActivity类初始化视图的时候找到相应的listview,代码如下。 private ListView mListView;

private void initView() { ... //初始化listview mListView = (ListView) findViewById(R.id.lv_app_manager);}

Adapter的完成

Adapter依赖数据集合,数据集合是由javabean构成,所以我们要新建bean。其次adapter的创建,还需要用到条目的布局和对应的viewholder,所以这些都要依次准备好。

Bean的建立

Bean由条目的UI决定,UI图如下所示。

image

从UI图可以看出,每一个条目是由一个图片和三个textview组成。由于图片还不确定具体的类型,所以此处先用int值代替,确定后再更改回来;名称用string类型;存储只分为两种,内部和外部,所以这里我们用布尔类型;大小我们直接用long类型。Bean代码如下。

/** * 软件管家数据模型 * @author developer * */ public class AppBean { public Drawable icon;//图标 public String name; public boolean isInstallSD;//是否安装在SD卡 public String space;//应用大小 public boolean isSystem;//是否为系统应用 }

条目布局的创建

条目的根布局可以采用RelativeLayout,Relative内部再包裹一个RelativeLayout,右侧包裹一个竖直摆放的LinearLayout。UI分析图如下。

image

根据上面的UI分析,在布局文件夹下新建item_app.xml布局文件,实现代码如下。

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:padding="8dp" ><ImageView android:id="@+id/iv_app_icon" android:layout_width="46dp" android:layout_height="46dp" android:layout_centerVertical="true" android:src="@drawable/ic_launcher" /><RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="8dp" android:layout_centerVertical="true" android:layout_toRightOf="@id/iv_app_icon" > <TextView android:id="@+id/tv_app_name" style="@style/TextNormal" android:layout_width="wrap_content" android:text="应用名称" /> <TextView android:id="@+id/tv_app_install" style="@style/TextNormal" android:layout_width="wrap_content" android:layout_below="@id/tv_app_name" android:text="安装位置" android:textSize="14sp" /> <TextView android:id="@+id/tv_app_space" style="@style/TextNormal" android:layout_width="wrap_content" android:layout_alignBaseline="@id/tv_app_install" android:layout_alignParentRight="true" android:text="150 KB" android:textSize="14sp" /></RelativeLayout></RelativeLayout>]

ViewHolder的创建(ViewHolder就是一个持有者的类,它一般没有方法,只有属性,作用就是被当作一个临时存储器,把你getView方法每次返回的View 进行存储,下次在用,这样做的好处就ushi不必每次到布局文件去拿你的View 提高了效率)

Viewholder的创建也要根据条目的UI长相来,从UI中可以看到,它是由三个textview和一个imageview组成,所以内部类Viewholder的代码如下。 private class ViewHolder { ImageView ivIcon; TextView tvName; TextView tvInstall; TextView tvSize; }

Adapter的创建 (改编者)

因为adapter需要数据集合,这里我们现在成员变量中声明处一个空集合,如下

private List<AppBean> mDatas;

新建内部类AppAdapter,继承自BaseAdapter,然后实现未实现的方法,前三个方法比较简单,这里不再赘述,getview方法后面会单独实现。代码如下。

private class AppAdapter extends BaseAdapter {@Overridepublic int getCount() { if (mDatas != null) { return mDatas.size(); } return 0;}@Overridepublic Object getItem(int position) { if (mDatas != null) { return mDatas.get(position); } return null;}@Overridepublic long getItemId(int position) { return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) { return convertView;}

getView方法的实现

整个方法可以分为两大部分,第一部分是获得holder对象,第二部分是给holder中对象赋值。对于第一部分,如果没有可以用的视图,就新建视图,然后把holder设置为标记;如果有可用的视图,直接从可复用视图中取出标记holder即可。第二部分中由于应用图标不知道是什么类型,这里先不设定,实现代码如下。

@Override public View getView(int position, View convertView, ViewGroup parent) { //1.声明ViewHolder ViewHolder holder = null; if(convertView == null){ //没有复用 //1.加载布局 convertView = View.inflate(AppManagerActivity.this, R.layout.item_app_managerr, null); //2.创建ViewHolder holder = new ViewHolder(); //3. findViewById holder.ivIcon = (ImageView) convertView.findViewById(R.id.iv_app_icon); holder.tvName = (TextView) convertView.findViewById(R.id.tv_app_name); holder.tvInstall = (TextView) convertView.findViewById(R.id.tv_app_install); holder.tvSpace = (TextView) convertView.findViewById(R.id.tv_app_space); //4.设置标记 convertView.setTag(holder); }else{ //复用 holder = (ViewHolder) convertView.getTag(); } //设置数据 AppBean bean = (AppBean) getItem(position); holder.ivIcon.setImageDrawable(bean.icon); holder.tvName.setText(bean.name); holder.tvSpace.setText(bean.space); holder.tvInstall.setText(bean.isInstallSD ? "SD卡安装" : "内存安装"); return convertView; }

数据模拟测试

为了先看到效果,我们先模拟假数据,实现代码如下。 private void initData() {

... Random rdm = new Random(); // 加载假数据 mDatas = new ArrayList<AppBean>(); for (int i = 0; i < 100; i++) { AppBean bean = new AppBean(); bean.name = "应用-" + i; bean.isInstallSD = rdm.nextBoolean(); bean.size = rdm.nextInt(100000); mDatas.add(bean); } // 设置Adpater mListView.setAdapter(new AppAdapter()); }

效果图如下

image

应用数据的加载

软件管理页面展示的是整个手机里面所有的应用程序软件。这些软件包括在手机你屏幕上展示出的有图标的软件,也包括没有图标的软件。这里我们通过一个业务方法来查找系统中所有的应用软件。 查询方法的创建

查询方法的创建

新建business包,在包内创建ContactProviderr类。在这个类中我们的目的也比较明确,就是创建一个能够查询出系统中所有的应用程序,然后通过list集合返回给调用者,框架如下。

image

public class ContactProvider { public static List<AppBean> getAllApps(Context context) { List<AppBean> list = new ArrayList<AppBean>(); //向集合中添加数据TODO return list; } }

在安卓中可以通过包管理器获得所有程序的清单文件集合,然后通过遍历集合,就可以获得每个清单文件中的所有信息。

// 获得包管理器PackageManager pm = context.getPackageManager(); // 获得所有安装的应用程序的清单文件List<PackageInfo> installedPackages = pm.getInstalledPackages(0);

应用的名称是由清单文件下的application节点的label属性决定的;图标是由application的icon属性决定,清单文件如下。

<application android:allowBackup="true" android:icon="@drawable/ic_launcher" //决定应用程序图标 android:label="@string/app_name" //决定应用程序名称 android:theme="@style/APPTheme" > ... </application>

所以我们遍历清单文件,并给bean中的图标和名称赋值后添加到集合中,代码如下。(getInstalledPackages) public static List getAllContacts(Context context) { List list = new ArrayList(); // 查询联系人数据 ContentResolver cr = context.getContentResolver(); Uri uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI; //姓名、电话、联系人ID(唯一标识) String[] projection = new String[] { ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER, ContactsContract.CommonDataKinds.Phone.CONTACT_ID };// 查询的列

String selection = null;//查询条件 String[] selectionArgs = null;//条件对应参数 String sortOrder = null;//排序规则 //数据集合 Cursor cursor = cr.query(uri, projection, selection, selectionArgs, sortOrder); if(cursor != null){ while(cursor.moveToNext()){ String name = cursor.getString(0); String phone = cursor.getString(1); long contactId = cursor.getLong(2); ContactBean bean = new ContactBean(); bean.name = name; bean.phone = phone; bean.contactId = contactId; list.add(bean ); } }

image 这里我们发现图标的类型为Drawable,所以要替换掉之前AppBean中icon的类型

查询方法的使用

上面还有两个数据没有赋值,我们先看一下真实数据的效果。找到AppManagerActivity类的initData方法,然后把模拟数据的部分替换掉,调用AppProvider的getAllApps方法,实现代码如下。

private void initData() { ... // 模拟数据 // Random rdm = new Random(); // mDatas = new ArrayList<AppBean>(); // for (int i = 0; i < 100; i++) { // AppBean bean = new AppBean(); // bean.name = "应用-" + i; // bean.isInstallSD = rdm.nextBoolean(); // bean.size = rdm.nextInt(100000); // // mDatas.add(bean); // } //替换成真实数据 mListDatas = new ArrayList<AppBean>(); // 设置Adpater-->List<数据> //initTempData(); mListDatas = PackageUtils.getAppData(this); //排序:用户应用在前,系统应用在后 }

在adapter的getView方法中给控件赋值的位置,设置左侧左侧显示的图片,添加代码如下。

public View getView(int position, View convertView, ViewGroup parent) { //1.声明ViewHolder ViewHolder holder = null; if(convertView == null){ //没有复用 //1.加载布局 convertView = View.inflate(AppManagerActivity.this, R.layout.item_app_managerr, null); //2.创建ViewHolder holder = new ViewHolder(); //3. findViewById holder.ivIcon = (ImageView) convertView.findViewById(R.id.iv_app_icon); holder.tvName = (TextView) convertView.findViewById(R.id.tv_app_name); holder.tvInstall = (TextView) convertView.findViewById(R.id.tv_app_install); holder.tvSpace = (TextView) convertView.findViewById(R.id.tv_app_space); //4.设置标记 convertView.setTag(holder); }else{ //复用 holder = (ViewHolder) convertView.getTag(); } //设置数据 AppBean bean = (AppBean) getItem(position); holder.ivIcon.setImageDrawable(bean.icon); holder.tvName.setText(bean.name); holder.tvSpace.setText(bean.space); holder.tvInstall.setText(bean.isInstallSD ? "SD卡安装" : "内存安装"); return convertView; }}

运行程序,可以看到效果图如下。

image

应用大小和存储位置的确定

查询方法还有两个数据没有确定,一个是apk的大小,还有一个是apk的存储位置。 applicationInfo对象有个sourceDir属性,可以获得对应apk的路径,我们通过打印log日志,看一下对应的程序路径。

List<PackageInfo> installedPackages = pm.getInstalledPackages(0);for (PackageInfo info : installedPackages) { ApplicationInfo applicationInfo = info.applicationInfo; ... //打印apk资源目录 Log.d(TAG, "" + applicationInfo.sourceDir); ..}

` 运行程序,eclipse的控制台打印的log如下。

image

从输出结果看apk总共有三个存储位置,系统应用存储在/system/app目录下;内部存储在/data/app目录下;外部存储在/mnt/asec目录下。此处我们把系统应用和内部存储统称为内部,也就是只有存储路径是以“/mnt/asec”开头的才算外部存储。

既然通过applicationInfo.sourceDir可以获得apk文件路径,所以直接调用文件的length方法就可以获得apk的大小。文件路径只要是以“/mnt/asec”开头就算SD卡存储,不是以它开头的都算内部存储,实现代码入如下。

for (PackageInfo info : installedPackages) {ApplicationInfo applicationInfo = info.applicationInfo;String name = applicationInfo.loadLabel(pm).toString();Drawable icon = applicationInfo.loadIcon(pm);//获得apk所在文件File file = new File(applicationInfo.sourceDir);//判断是否为外部存储boolean isInstallSD = applicationInfo.sourceDir .startsWith("/mnt/asec");AppBean bean = new AppBean();bean.name = name;bean.icon = icon;bean.size = file.length(); //apk大小bean.isInstallSD = isInstallSD; //是否SD卡安装list.add(bean);

系统程序的判断

从软件管理的效果图可以看出,listview在展示应用程序的时候是分类展示的,如下图8-1所示

image

展示的软件分为两类,一类是用户程序,另一类是系统程序,并且每一个种类的开头都会显示应用种类的名称。Listview展示的视图依赖的是数据,所以我们要先在数据上把系统程序和用户程序区分开。

程序种类的区分

区分程序种类需要在AppBean中添加一个属性,用来标记此软件是系统软件还是用户软件。因为软件种类只有两种,所以属性类型用布尔类型即可,代码如下。

public class AppBean {...public boolean isSystem;// 用来判断是否是系统程序...

方案一

定义完是否为系统程序的标记后,我们需要在查找系统所有软件的方法时给标记赋值。从之前打印的log日志可知,系统程序的路径都是以“/system/app/”开头,示意图如下。

image

所以我们就可以在获得程序的路径后,判断是否是以“/system/”开头,如果是则表明是系统程序;如果不是就说明是用户程序。需要注意的是系统程序除了在/system/app/文件目录下,有些还会在/system/framework/目录下,实现代码如下。

public static List<AppBean> getAllApps(Context context) {...List<PackageInfo> installedPackages = pm.getInstalledPackages(0);for (PackageInfo info : installedPackages) { ... File file = new File(applicationInfo.sourceDir); //判断是否为SD卡安装 boolean isInstallSD = applicationInfo.sourceDir.startsWith("/mnt/asec"); //判断是否为系统程序 boolean isSystem = applicationInfo.sourceDir.startsWith("/system/"); AppBean bean = new AppBean(); ... //赋值 bean.isSystem = isSystem; list.add(bean);}return list;

}

方案二

applicationInfo有个flags标记,这个标记的作用是表示此应用所具有的某些能力。标记是用32位的二进制表示的,如果某一位上是1,则表示有特定的某种能力。得到此应用的flags标记后和ApplicationInfo.FLAG_SYSTEM进行逻辑“与”运算,如果结果还是ApplicationInfo.FLAG_SYSTEM本身,说明此应用就是系统应用,运算示意图如下。

image `

理解了flag的意义后,代码实现就比较简答了,代码如下。

`int flags = applicationInfo.flags;// flags 具备的能力 boolean isSystem = false; //判断标记是否为ApplicationInfo.FLAG_SYSTEM if ((flags & ApplicationInfo.FLAG_SYSTEM) == ApplicationInfo.FLAG_SYSTEM) { isSystem = true; }

集合数据的排序

在AppManagerActivity的initData方法中,加载数据后要对加载出的数据重新排序,区分出哪些是用户程序,哪些是系统程序。排序后的结果应该是用户程序在前,系统程序在后

实现方案是新建两个集合,分别代表用户程序和系统程序,然后遍历查找到的所有程序,如果是用户程序就添加到用户程序集合,如果是系统程序就添加到系统程序集合。遍历完成后把总数据集合清空,然后依次把用户程序集合和系统程序集合添加到总数据集合中,实现代码如下。

private void initData() {...//获得所有的应用程序mDatas = AppProvider.getAllApps(AppManagerActivity.this);//创建用户程序集合mUserDatas = new ArrayList<AppBean>();//创建系统程序集合mSystemDatas = new ArrayList<AppBean>();// 对mDatas进行排序for (AppBean bean : mDatas) { if (bean.isSystem) { // 系统程序 mSystemDatas.add(bean); } else { // 用户程序 mUserDatas.add(bean); }}// 清空原来集合mDatas.clear();//添加用户程序集合到原集合mDatas.addAll(mUserDatas);//添加系统程序集合原集合mDatas.addAll(mSystemDatas);mListView.setAdapter(new AppAdapter(););}

数据排序后运行程序,效果图8-4如下。可以看到,手势锁以上为用户程序,手势锁以下为系统程序。

image


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表