## 軟件管理 ##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"