## 软件管理 ##android
- 文件大小的计算app
android.text.Formatter 类能够格式化文件大小
// 内部存储, 其实就是data目录的容量ide
File dataDirectory = Environment.getDataDirectory();
// 所有
long totalSpace = dataFile.getTotalSpace();
// 可用
long usableSpace = dataFile.getUsableSpace();
// 已用
long usedSpace = totalSpace - usableSpace;
mPdvRom.setTitle("内存: ");
mPdvRom.setTextLeft(Formatter.formatFileSize(getApplicationContext(), usedSpace) + "已用");
mPdvRom.setTextRight(Formatter.formatFileSize(getApplicationContext(), freeSpace) + "可用");
mPdvRom.setProgress((int) (usedSpace * 100f / totalSpace + 0.5f)); // 四舍五入
// SD卡
File sdDirectory = Environment.getExternalStorageDirectory();
// 总空间
long sdTotalSpace = sdDirectory.getTotalSpace();
// 剩余空间
long sdFreeSpace = sdDirectory.getFreeSpace();
long sdUsedSpace = totalSpace - freeSpace;
mPdvSD.setTitle("SD卡: ");
mPdvSD.setTextLeft(Formatter.formatFileSize(getApplicationContext(), sdUsedSpace) + "已用");
mPdvSD.setTextRight(Formatter.formatFileSize(getApplicationContext(), sdFreeSpace) + "可用");
mPdvSD.setProgress((int) (sdUsedSpace * 100f / sdTotalSpace+ 0.5f));布局
## 软件管理的内容部分 ##优化
- 应用程序列表先简单实现 标准listview带优化的写法动画
private class AppAdapter extends BaseAdapter {
@Override
public int getCount() {
return mDatas.size();
}
@Override
public Object getItem(int position) {
return mDatas.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = View.inflate(getApplicationContext(), R.layout.item_appinfo, null);
holder = new ViewHolder();
holder.ivIcon = (ImageView) convertView.findViewById(R.id.item_appinfo_iv_icon);
holder.tvName = (TextView) convertView.findViewById(R.id.item_appinfo_tv_name);
holder.tvInstallPath = (TextView) convertView.findViewById(R.id
.item_appinfo_tv_install);
holder.tvSize = (TextView) convertView.findViewById(R.id.item_appinfo_tv_size);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
// 这里可使用 getItem 方法获取某个位置对应的对象
AppInfo info = (AppInfo) getItem(position);
holder.tvName.setText(info.mName);
holder.ivIcon.setImageDrawable(info.mIcon);
holder.tvInstallPath.setText(info.mIsInstallSD ? "SD卡安装" : "手机内存");
holder.tvSize.setText(Formatter.formatFileSize(getApplicationContext(), info.mSize));
return convertView;
}
}
static class ViewHolder {
ImageView ivIcon;
TextView tvName;
TextView tvInstallPath;
TextView tvSize;
}this
-- 对应的JavaBean:指针
public class AppInfo {
public String mName;// 应用的名称
public String mPackageName;// 应用的包名
public Drawable mIcon;// 应用图标
public boolean mIsInstallSD;// 是否安装在sd卡
public long mSize;// 应用的大小
public boolean mIsSystem;// 是不是系统程序
}orm
## 获取应用程序的信息##xml
- 建立一个包, engine 或者 business, 写个类, AppInfoProvider
public class AppInfoProvider {
public static ArrayList<AppInfo> getAllAppInfo(Context context) {
PackageManager packageManager = context.getPackageManager();
// 获取全部的安装包信息, PackageInfo 至关于 manifest 节点
List<PackageInfo> packages = packageManager.getInstalledPackages(0);
ArrayList<AppInfo> list = new ArrayList<>();
for (PackageInfo packageInfo : packages) {
AppInfo appInfo = new AppInfo();
// 获取包名
appInfo.mPackageName = packageInfo.packageName;
// 获取应用名称
ApplicationInfo applicationInfo = packageInfo.applicationInfo;
appInfo.mName = applicationInfo.loadLabel(packageManager).toString();
// 获取应用图标
appInfo.mIcon = applicationInfo.loadIcon(packageManager);
// 获取应用安装包大小
String sourceDir = applicationInfo.sourceDir;// data/app/xxx.apk 或者 system/app/xxx.apk
//应用安装包
appInfo.mSize = new File(sourceDir).length();
// info.mSize = Formatter.formatFileSize(context, length);
// 是否为系统应用
if((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == ApplicationInfo
.FLAG_SYSTEM) {
appInfo.mIsSystem = true;
}else {
appInfo.mIsSystem = false;
}
// 是否为外部存储
// 应用能够装在sd卡上, 在清单文件根节点中配置 android:installLocation 属性便可
if((applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == ApplicationInfo
.FLAG_EXTERNAL_STORAGE) {
appInfo.mIsInstallSD = true;
}else {
appInfo.mIsInstallSD = false;
}
list.add(appInfo);
}
return list;
}
}
注意 flags 和 &, | 运算符的含义
##进度条/include标签的使用##
- 新建一个布局文件 loading:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:id="@+id/loading"
android:orientation="vertical" >
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminateDrawable="@drawable/progress_loading"
android:indeterminateDuration="600" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="加载中..."
android:textColor="#a000"
android:textSize="16sp" />
</LinearLayout>
在其余布局文件里使用 include 标签引用便可
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ListView
android:id="@+id/lv_am"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fastScrollEnabled="true" />
<include
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
layout="@layout/loading" />
</RelativeLayout>
- 这和直接写在布局文件里效果是同样的, 在代码中, 能够经过id找到相应的控件.
new Thread() {
@Override
public void run() {
super.run();
// 模拟耗时操做
SystemClock.sleep(1000);
// 填充数据集合s
infos = AppInfoProvider.getAllAppInfo
(getApplicationContext());
runOnUiThread(new Runnable() {
@Override
public void run() {
mLlLoading.setVisibility(View.GONE);
// 给ListView设置Adapter
mLvApp.setAdapter(new AppAdapter());
}
});
}
}.start();
- ListView中的条目分组显示
- 把应用信息分红用户应用和系统应用, 在数据加载完成以后分红两个集合
mInfos = AppInfoProvider.getAllAppInfo(getApplicationContext());
mSysInfos = new ArrayList<AppInfo>();
mUserInfos = new ArrayList<AppInfo>();
// 区分用户和系统程序 分别添加到两个集合里
for (AppInfo info : mInfos) {
if (info.isSys) {
// 系统程序
mSysInfos.add(info);
} else {
// 用户程序
mUserInfos.add(info);
}
}
mInfos.clear();// 清空以前的乱序数据
// 先添加用户程序 而后系统程序
mInfos.addAll(mUserInfos);
mInfos.addAll(mSysInfos);
##ListView分隔条目/复杂ListView## 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点
-因为多了两个分隔条目, getCount() 方法返回的数量要加2
@Override
public int getCount() {
return mInfos.size() + 2; // 增长两个分隔条目
}
- 在 getitem也要对应的改变
@Override
public Object getItem(int position) {
if (position == 0 || position == mUserInfos.size() + 1) {
return null;
}
if (position < mUserInfos.size() + 1) {
return mInfos.get(position - 1);
} else {
return mInfos.get(position - 2);
}
}
- 咱们要实现的效果中ListView有两个分隔条目, 区分用户应用和系统应用, 有多个条目类型的ListView. 要实现这种效果, 须要重写 Adapter 里的两个方法:
// 返回有多少种条目类型
@Override
public int getViewTypeCount() {
return 2;
}
// 每一个位置对应的条目的类型, 返回值表示条目类型
@Override
public int getItemViewType(int position) {
// 注意返回值必须从0开始, 好比有三种类型, 就得返回 0, 1, 2.
if (position == 0 || position == mUserDatas.size() + 1) {
return 0;
} else {
return 1;
}
}
-getView方法中, 也得根据当前位置的条目类型返回相应的View:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
int itemViewType = getItemViewType(position); // 先获取条目类型
switch (itemViewType) { // 根据条目类型返回不一样的View
case 0:
convertView = new TextView(getApplicationContext());
tv = (TextView)convertView;
tv.setTextColor(Color.BLACK);
tv.setTextSize(14);
tv.setPadding(4, 4, 4, 4);
tv.setBackgroundColor(Color.parseColor("#ffcccccc"));
if(position == 0) {
tv.setText("用户程序( " + mUserDatas.size() + " 个)");
}else if(position == mUserDatas.size() + 1) {
tv.setText("系统程序( " + mSystemDatas.size() + " 个)");
}
break;
case 1:
ViewHolder holder;
if (convertView == null || convertView instanceof TextView) {
convertView = View.inflate(getApplicationContext(), R.layout.item_appinfo, null);
holder = new ViewHolder();
holder.ivIcon = (ImageView) convertView.findViewById(R.id.item_appinfo_iv_icon);
holder.tvName = (TextView) convertView.findViewById(R.id.item_appinfo_tv_name);
holder.tvInstallPath = (TextView) convertView.findViewById(R.id
.item_appinfo_tv_install);
holder.tvSize = (TextView) convertView.findViewById(R.id.item_appinfo_tv_size);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
AppInfo info = (AppInfo) getItem(position);
holder.tvName.setText(info.mName);
holder.ivIcon.setImageDrawable(info.mIcon);
holder.tvInstallPath.setText(info.mIsInstallSD ? "SD卡安装" : "手机内存");
holder.tvSize.setText(Formatter.formatFileSize(getApplicationContext(), info.mSize));
break;
}
return convertView;
}
##分隔条目的显示和隐藏##
在布局文件里加一个 TextView, 样式和分隔条目TextView如出一辙, 当滚动ListView的时候, 根据第一个可见条目,
作相应的显示便可.
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ListView
android:id="@+id/lv_am"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fastScrollEnabled="true" />
<include
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
layout="@layout/public_loading" />
<TextView
android:id="@+id/tv_am_apphead"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#aaaaaa"
android:padding="4px"
android:textColor="@android:color/black"
android:textSize="15sp"
android:visibility="invisible" />
</RelativeLayout>
-给ListView设置滚动监听: 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点 重点
// 设置listview滑动监听 在设置的时候 默认的方法都会都一遍
lvAm.setOnScrollListener(new OnScrollListener() {
// 滑动状态的改变
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
// 滑动时不停的执行
// 参1 当前listview 参2 能够见的第一个条目的索引 参3 可见的条目数量 参4 总数量
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (mUserInfos == null || mSysInfos == null) {
// 防止空指针 在设置的时候 默认的方法都会都一遍
return;
}
if (firstVisibleItem >= mUserInfos.size() + 1) {
// 显示系统程序 更换固定条目内容
tvTopSize.setText("系统程序(" + mSysInfos.size() + "个)");
} else {
tvTopSize.setText("用户程序(" + mUserInfos.size() + "个)");
}
}
});
- 默认应该是隐藏的, 当加载数据完成以后再显示:
runOnUiThread(new Runnable() {
@Override
public void run() {
// 显示ListView头部信息
mTvHeader.setVisibility(View.VISIBLE);
// 隐藏进度条
mLlLoading.setVisibility(View.GONE);
// 给ListView设置Adapter
mLvApp.setAdapter(new AppAdapter());
}
});
- ListView的属性:android:fastScrollEnabled="true",
这个属性值设置为true的话, ListView会出现快速滑动条, 默认false
##PopupWindow的基本使用## 重点 重点 重点 重点 重点 重点 重点 重点
- 它有些方法和View有点像, 有些又和Dialog比较像.
它也是经过Window加到屏幕上的, 可是它和Dialog的又不太同样, 它的弹出位置不固定.
- 基本用法, 建立一个示例项目.
public void popup(View v) {
TextView contentView = new TextView(getApplicationContext());
contentView.setTextColor(Color.RED);
contentView.setText("我是一个弹出窗口");
int width = ViewGroup.LayoutParams.WRAP_CONTENT;
int height = ViewGroup.LayoutParams.WRAP_CONTENT;
// 弹出窗口, 第一个参数表示里面要显示的View, 后两个表示宽高.
PopupWindow popupWindow = new PopupWindow(contentView, width, height);
// 若是想让一个弹出窗可以在点击别的区域时或者按返回键时消失, 须要调用下面两个方法.
// 表示能够获取焦点
popupWindow.setFocusable(true);
// 必须设置背景, 若是实在不想要, 能够设置 new ColorDrawable(Color.TRANSPARENT)
popupWindow.setBackgroundDrawable(new ColorDrawable(Color.GREEN));
// 显示在某个View的左下角
// popupWindow.showAsDropDown(mTv);
// 显示在屏幕的某个地方, 第一个参数只须要传当前Activity里任何一个View就行.
// popupWindow.showAtLocation(mTv, Gravity.CENTER, 0, 0);
// 显示在某个View的左下角, 而且指定x, y轴的偏移量
popupWindow.showAsDropDown(mTv, mTv.getWidth(), -mTv.getHeight());
}
##在手机卫士使用PopupWindow##
- 给ListView设置 OnItemClickListener, 点击某个条目后, 在当前条目下面弹出PopupWindow.
这里能够写一个单独的方法, 把当前条目对应的View做为参数传过去. 而且记录当前点击的对象.
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// 只有点在应用条目上, 才应该显示PopupWindow, 点在分隔标题上不显示
// parent 只的就是ListView, getItemAtPosition内部调用的是 Adapter的 getItem
AppInfo appInfo = (AppInfo) parent.getItemAtPosition(position);
if (appInfo != null) {
mCurrentAppInfo = appInfo;
showPopupWindow(view);
}
}
/**
* 显示listview单条点击的弹出框
*
* @param view
*/
private void showPop(View view) {
// 为空的时候再建立对象
if (pop == null) {
// 设置宽高为包裹内容
int width = LayoutParams.WRAP_CONTENT;
int height = LayoutParams.WRAP_CONTENT;
View contentView = View.inflate(getApplicationContext(), R.layout.popup_am,
null);
contentView.findViewById(R.id.tv_popam_uninstall).setOnClickListener(this);
contentView.findViewById(R.id.tv_popam_open).setOnClickListener(this);
contentView.findViewById(R.id.tv_popam_share).setOnClickListener(this);
contentView.findViewById(R.id.tv_popam_info).setOnClickListener(this);
pop = new PopupWindow(contentView, width, height);
// 能够获取焦点
pop.setFocusable(true);
// 设置背景 只有设置了背景,点击 外部区域或者返回键 才会消失
pop.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));// 设置背景透明
// anchor 锚点 抛锚 让PopupWindow显示在指定的anchor下方
// pop.showAsDropDown(view);
// 设置动画弹出方式
pop.setAnimationStyle(R.style.PopAnimation);
}
pop.showAsDropDown(view, 60, -view.getHeight());
// 参1 传入activity里任意一个view就能够
// pop.showAtLocation(mtv, Gravity.LEFT | Gravity.CENTER_VERTICAL, 50,
// 0);
}
- 动画样式 pop.setAnimationStyle(R.style.PopAnimation);
这里能够仿照输入法的动画样式, 本身写一个, 在 styles.xml中:
<style name="PopAnimation">
<item name="android:windowEnterAnimation">@anim/pop_am_enter</item>
<item name="android:windowExitAnimation">@anim/pop_am_exit</item>
</style>
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false" >
<translate
android:duration="@android:integer/config_shortAnimTime"
android:fromXDelta="100%"
android:interpolator="@interpolator/overshoot"
android:toXDelta="0" />
<alpha
android:duration="@android:integer/config_shortAnimTime"
android:fromAlpha="0.5"
android:interpolator="@interpolator/decelerate_cubic"
android:toAlpha="1.0" />
</set>
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false" >
<translate
android:duration="@android:integer/config_shortAnimTime"
android:fromXDelta="0"
android:interpolator="@interpolator/anticipate"
android:toXDelta="100%" />
<alpha
android:duration="@android:integer/config_shortAnimTime"
android:fromAlpha="1.0"
android:interpolator="@interpolator/accelerate_cubic"
android:toAlpha="0.0" />
</set>
- 注意使用了两个interpolator文件:
<overshootInterpolator xmlns:android="http://schemas.android.com/apk/res/android"/>
<anticipateInterpolator xmlns:android="http://schemas.android.com/apk/res/android"/>
# 卸载, 打开, 分享, 信息#
- 点击PopupWindow里面的文字时, 实现相应的功能
/**
* 显示app信息页面
*
* @param packageName
*/
private void showInfo(String packageName) {
// <action android:name="android.settings.APPLICATION_DETAILS_SETTINGS"
// />
// <category android:name="android.intent.category.DEFAULT" />
// <data android:scheme="package" />
Intent intent = new Intent();
intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setData(Uri.parse("package:" + packageName));
startActivity(intent);
}
/**
* 分享app
*/
private void shareApp() {
// <action android:name="android.intent.action.SEND" />
// <category android:name="android.intent.category.DEFAULT" />
// <data android:mimeType="text/plain" />
// sharesdk mob 友盟 极光 百度云推送
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, "分享一个app,,:" + mCurrentInfo.name);
startActivity(intent);
}
/**
* 打开一个app
*
* @param packageName
*/
private void openApp(String packageName) {
PackageManager pm = getPackageManager();
// 返回启动页面的intent
Intent openIntent = pm.getLaunchIntentForPackage(packageName);
// 有些应用没有页面 只有后台程序 须要判断空
if (openIntent != null) {
startActivity(openIntent);
}
}
/**
* 卸载app
*
* @param packageName
*/
private void unInstallApp(String packageName) {
// <action android:name="android.intent.action.VIEW" />
// <action android:name="android.intent.action.DELETE" />
// <category android:name="android.intent.category.DEFAULT" />
// <data android:scheme="package" />
Intent intent = new Intent();
intent.setAction(Intent.ACTION_DELETE);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setData(Uri.parse("package:" + packageName));
startActivityForResult(intent, REQUESTCODE_UNINSTALL);
}
## 卸载后的处理 ##
- 简单的作法, 点击卸载时, startActivityForResult, 从卸载界面回来的时候, 从新获取一遍数据便可.
注意获取数据以前, 先清空以前的数据集合.
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUESTCODE_UNINSTALL) {// 从卸载页面返回
// Activity.RESULT_OK
// resultCode
// System.out.println("resultCode ==" + resultCode);
// 重新获取数据
// 获取数据前先清空
userAppInfos.clear();
sysAppInfos.clear();
appInfos = AppInfoProvider.getAllAppInfo(getApplicationContext());
// 把用户程序和系统程序分开
for (AppInfo info : appInfos) {
if (info.isSystem) {
sysAppInfos.add(info);
} else {
userAppInfos.add(info);
}
}
appAdapter.notifyDataSetChanged();
}
}
- 比较高级的作法, 监听系统卸载应用的广播:
在 onCreate 中注册广播接收者:
// 注册应用卸载的广播
IntentFilter filter = new IntentFilter();
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addDataScheme("package");
registerReceiver(mPackageReceiver, filter);
- 在 onDestroy 中解除注册:
unregisterReceiver(mPackageReceiver);
对应的广播接收者:
private BroadcastReceiver mPackageReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
System.out.println("接收到卸载广播");
String dataString = intent.getDataString();
System.out.println("卸载了:" + dataString);
String packageName = dataString.replace("package:", "");
// 只须要遍历用户集合就能够了, 由于系统的删不掉
// 一边遍历一遍移除须要使用 iterator
ListIterator<AppInfo> iterator = mUserDatas.listIterator();
while (iterator.hasNext()) {
AppInfo next = iterator.next();
if (next.packageName.equals(packageName)) {
// 移除
iterator.remove();
mUserInfos.remove(info);
break;
}
}
// 刷新ListView
mAdapter.notifyDataSetChanged();
}
};
但其实使用广播是不靠谱的, 由于有的系统不发这个广播...
- bug
点击分割条目的bug 单条点击事件
if (position == 0 || position == mUserInfos.size() + 1) {
//若是点击的是分割条目 不处理
return;
}
把固定条目变成可点击 处理透过点击的bug android:clickable="true"