對話保持的解決方案
要求:
- app中使用webview訪問具體網站的內容,但是app與服務器的溝通是使用HttpUrlConnection來完成。
- webview訪問時不需要再次登陸,繼承app的登陸狀態。
會話未保持的現象:
- 雖然app已經登錄服務器,但是在webview中還是提示需要登錄。
- app下一次對服務器的請求也會失敗,提示session過期。
解決方案:
- 獲取到HttpUrlConnection中服務器返回的session id。
- 本地保存session id,每次對服務器的請求,手動添加。
- 將此session id設置到持有webview的activity中的CookieManager裏
網絡處理類 NetHelper
/**
* 發送登陸請求,並將SESSIONID保存起來
* @param urlPath 登陸請求的地址
* @return 返回的內容
* */
public static String login(String urlPath) {
......省略號......
try {
URL url = new URL(urlPath);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//設置請求方式
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
// conn.setReadTimeout(5000);
int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
InputStream is = conn.getInputStream();
cookList = conn.getHeaderFields().get("Set-Cookie");
if ((sessionId == null) && (cookList != null)) {
for (String value : cookList) {
if ((value != null) && (value.toUpperCase().indexOf(";") > 0)) {
sessionId = value.split(";")[0];
}
}
}
......省略號......
}
}catch (Exception e){
e.printStackTrace();
}
......省略號......
}/**
* 發送一條請求,將內容以字符串返回
* @param urlPath 請求的地址
* @return 返回的內容
* */
public static String request(String urlPath) {
......省略號......
try {
URL url = new URL(urlPath);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
if(sessionId !=null ){
conn.setRequestProperty("Cookie",sessionId);
}
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
// conn.setReadTimeout(5000);
......省略號......
} catch (Exception e) {
e.printStackTrace();
}
......省略號......
}持有webview的Activity MainActivity
private CookieManager cookieManager;
cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);
clearSession();
private void clearSession() {
if (NetHelper.cookList != null) {
cookieManager.removeSessionCookie();
}
}
//在第一次請求的時候,設置一次session即可
private void setSession(String url) {
if (NetHelper.cookList != null) {
String values = NetHelper.cookList.toString();
cookieManager.setCookie(url, values); //設置cookie
CookieSyncManager.getInstance().sync(); //同步
}
}
自定義控件的實現方案
自定義控件的實現方式(詳細內容可以參考壓縮包中的<自定義控件.pdf>):
- 繼承方式
當簡單控件不滿足需求時,通過繼承重寫簡單控件,實現對控件的定製。
- 組合方式
當單個控件不滿足需求時,可以採用多個控件的組合,實現對控件的定製。
- 控件自繪方式
通過繼承自view,重寫onDraw方法實現
項目中的具體應用:
- 登錄郵箱的自動補全功能實現(純代碼實現佈局)。
- 彈窗滾輪的實現(代碼加布局文件)
- TabButton的實現(兩種實現方式)
A:登錄郵箱的自動補全功能實現:
效果:
實現原理:
- 繼承重寫簡單控件AutoCompleteTextView
- 編寫自定義數據適配器和佈局文件,並實現文字變化監聽器
- 通過組合方式,實現右側的刪除圖標。並根據焦點和文字的變化,動態顯示右側刪除圖標。
- 通過繼承自簡單控件AutoCompleteTextView實現帳號自動補全
關鍵代碼:
public class AutoComplete extends AutoCompleteTextView {
private static final String[] emailSuffix = {
"@qq.com", "@163.com", "@126.com", "@gmail.com", "@sina.com", "@hotmail.com",
"@yahoo.cn", "@sohu.com", "@foxmail.com", "@139.com", "@yeah.net", "@vip.qq.com",
"@vip.sina.com"};
......省略號......
//構造函數原型要正確,留給系統調用
public AutoComplete(Context context) {
super(context);
mContext = context;
}
public AutoComplete(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
}
public void init(ImageView imageView) {
mImageView = imageView;
final MyAdatper adapter = new MyAdatper(mContext);
setAdapter(adapter);
addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
if (isTextWatch) {
String input = s.toString();
......省略號......
adapter.clearList(); //注意要清空數據,根據輸入的變化,自動生成數據
if (input.length() > 0) {
for (int i = 0; i < emailSuffix.length; ++i) {
adapter.addListData(input + emailSuffix[i]);
}
}
adapter.notifyDataSetChanged();
showDropDown();//該行代碼會造成崩潰
}
}
});
//當輸入一個字符的時候就開始檢測
setThreshold(1);
}
private class ViewHolder {
TextView tv_Text;
}
class MyAdatper extends BaseAdapter implements Filterable {
private List<String> mList;
private Context mContext;
private MyFilter mFilter;
......省略號......
public void clearList() {
mList.clear();
}
public void addListData(String strData) {
mList.add(strData);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
ViewHolder viewHolder;
if (convertView == null) {
view = LayoutInflater.from(mContext).inflate(R.layout.activity_autocomplete_item, null);
viewHolder = new ViewHolder();
viewHolder.tv_Text = (TextView) view.findViewById(R.id.tv_autocomplete);
view.setTag(viewHolder);
} else {
view = convertView;
viewHolder = (ViewHolder) view.getTag();
}
viewHolder.tv_Text.setText(mList.get(position));
return view;
}
......省略號......
}
activity_autocomplete_item 下拉列表佈局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:background="@color/White"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_autocomplete"
android:padding="15dp"
android:textSize="20sp"
android:singleLine="true"
android:textColor="@color/Black"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
上面自動補全的效果圖:
通過組合方式實現帳號自動補全複雜控件
關鍵代碼:
public class AdvancedAutoCompleteTextView extends RelativeLayout {
private Context mContext;
private AutoComplete mAutoComplete; //上面的自定義控件
private ImageView mImageView; //右側的圖標控件
......省略號......
@Override
protected void onFinishInflate() {
super.onFinishInflate();
initViews();
}
//代碼方式,初始化佈局
private void initViews() {
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
params.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
params.addRule(RelativeLayout.CENTER_VERTICAL);
mAutoComplete = new AutoComplete(mContext);
mAutoComplete.setLayoutParams(params);
mAutoComplete.setPadding(0, 0, 40, 0);
mAutoComplete.setSingleLine(true);
mAutoComplete.setInputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS);
mAutoComplete.setFitsSystemWindows(true);
mAutoComplete.setEms(10);
mAutoComplete.setHint("URS賬號");
mAutoComplete.setImeOptions(EditorInfo.IME_ACTION_NEXT
| EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_FLAG_NO_FULLSCREEN);
mAutoComplete.setDropDownHorizontalOffset(0);
mAutoComplete.setDropDownVerticalOffset(2);
mAutoComplete.setBackgroundResource(R.drawable.edit_text_background);
RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, LayoutParams.FILL_PARENT);
p.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
p.addRule(RelativeLayout.CENTER_VERTICAL);
p.rightMargin = 10;
mImageView = new ImageView(mContext);
mImageView.setLayoutParams(p);
mImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
mImageView.setImageResource(R.drawable.unselect);
mImageView.setClickable(true);
mImageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setText("");
}
});
this.addView(mAutoComplete);
this.addView(mImageView);
//監聽獲取焦點事件,目的:輸入帳號時,右側圖標的顯示
mAutoComplete.setOnFocusChangeListener(new OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus && !mAutoComplete.getText().toString().isEmpty()) {
mAutoComplete.setShow(false); //如果獲取首次獲取焦點,此時文本不爲空,則顯示,並禁止文本改變監聽裏的設置
mImageView.setImageResource(R.drawable.item_delete);
} else if (hasFocus) {
mAutoComplete.setShow(true);//如果獲取首次獲取焦點,此時文本爲空,則不改變,並開啓文本改變監聽裏的設置
} else {
mAutoComplete.setShow(false);
mImageView.setImageResource(R.drawable.unselect);
}
}
});
//對AutoComplete自定義控件初始化,一定要放到最後.否則,會由於AutoComplete初始化未完成,就彈窗,而崩潰
mAutoComplete.init(mImageView);
}
}
B:彈窗滾輪的實現
效果:
實現原理:
- 繼承重寫簡單控件ScrollView,實現滾動效果,並添加回調接口,用於獲取選擇的內容。
- 爲自定義控件添加內容,其中每一項爲一個TextView,用於內容顯示。
- 通過自繪添加上下兩條直線,實現選中狀態。
- 最後利用popup彈窗,加載整個視圖,顯示彈窗滾動效果。
通過繼承ScrollView實現滾動,並向佈局添加具體項
關鍵代碼:
ublic class WheelView extends ScrollView {
//選擇後的回調接口
public interface OnWheelViewListener {
void onSelected(int selectedIndex, String item);
}
......省略號......
//初始化,並創建佈局
private void init(Context context) {
this.context = context;
this.setVerticalScrollBarEnabled(false);
views = new LinearLayout(context); //爲自定義控件創建線性佈局
views.setOrientation(LinearLayout.VERTICAL);
this.addView(views);
//異步任務,根據滾動的位置自動調整待顯示的數據,該異步任務會在滾動事件觸發式執行
scrollerTask = new Runnable() {
public void run() {
if (itemHeight == 0) {
return;
}
int newY = getScrollY();
if (initialY - newY == 0) { // stopped
final int remainder = initialY % itemHeight;
final int divided = initialY / itemHeight;
if (remainder == 0) {
selectedIndex = divided + offset;
onSeletedCallBack();
} else {
if (remainder > itemHeight / 2) {
WheelView.this.post(new Runnable() {
@Override
public void run() {
WheelView.this.smoothScrollTo(0, initialY - remainder + itemHeight);
selectedIndex = divided + offset + 1;
onSeletedCallBack();
}
});
} else {
WheelView.this.post(new Runnable() {
@Override
public void run() {
WheelView.this.smoothScrollTo(0, initialY - remainder);
selectedIndex = divided + offset;
onSeletedCallBack();
}
});
}
}
} else {
initialY = getScrollY();
WheelView.this.postDelayed(scrollerTask, newCheck);
}
}
};
}
//往佈局添加數據
private void initData() {
displayItemCount = offset * 2 + 1;
//添加新view之前,必須移除舊的,否則不正確
views.removeAllViews();
for (String item : items) {
views.addView(createView(item));
}
refreshItemView(0);
}
private TextView createView(String item) {
TextView tv = new TextView(context);
tv.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
tv.setSingleLine(true);
tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
tv.setText(item);
tv.setGravity(Gravity.CENTER);
int padding = dip2px(15);
tv.setPadding(padding, padding, padding, padding);
if (0 == itemHeight) {
itemHeight = getViewMeasuredHeight(tv);
views.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, itemHeight * displayItemCount));
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) this.getLayoutParams();
this.setLayoutParams(new LinearLayout.LayoutParams(lp.width, itemHeight * displayItemCount));
}
return tv;
}
......省略號......
@Override //上下直線的自繪
public void setBackgroundDrawable(Drawable background) {
if (viewWidth == 0) {
viewWidth = ((Activity) context).getWindowManager().getDefaultDisplay().getWidth();
}
if (null == paint) {
paint = new Paint();
paint.setColor(Color.parseColor("#83cde6"));
paint.setStrokeWidth(dip2px(1f));
}
background = new Drawable() {
@Override
public void draw(Canvas canvas) {
canvas.drawLine(viewWidth * 1 / 6, obtainSelectedAreaBorder()[0], viewWidth * 5 / 6,
obtainSelectedAreaBorder()[0], paint);
canvas.drawLine(viewWidth * 1 / 6, obtainSelectedAreaBorder()[1], viewWidth * 5 / 6,
obtainSelectedAreaBorder()[1], paint);
}
};
super.setBackgroundDrawable(background);
}
}
動態加載佈局,並利用PopupWindow彈窗顯示
private void addView(int num){
......省略號......
wheel_layout_view = LayoutInflater.from(this).inflate(R.layout.wheel_view, null);
......省略號......
}
佈局文件 wheel_view 效果圖
private void popupWindows(List<String> list){
if (wheel_layout_view != null){
mPopupWindow = null;
mPopupWindow = new PopupWindow(wheel_layout_view);
mPopupWindow.setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
mPopupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
//點擊外部,自動消失
mPopupWindow.setFocusable(true);
mPopupWindow.setOutsideTouchable(true);
......省略號......
mPopupWindow.showAtLocation(ll_weidu_condition, Gravity.BOTTOM, 0, 0);
}
}
C:TabButton的實現
- 利用.9.png實現
- 利用佈局實現
效果:
利用.9.png圖標實現(簡單、美觀)
屬性定義attrs.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 自定義的button控件,用於日期的選擇-->
<declare-styleable name="TabButton">
<attr name="normal_bg_res" format="reference" />
<attr name="selected_bg_res" format="reference" />
</declare-styleable>
</resources>
佈局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto" //聲明自定義屬性空間
......省略號......
android:orientation="vertical">
......省略號......
<xxxxxxxxxxx.customui.TabButton
style="@style/commonButton"
android:layout_width="0dp"
android:layout_margin="0dp"
android:layout_weight="1"
android:layout_height="40dp"
android:text="昨天"
android:textSize="22sp"
android:gravity="center"
android:background="@drawable/btn_left"
android:textColor="@color/blue"
custom:normal_bg_res="@drawable/btn_left"
custom:selected_bg_res="@drawable/btn_left_selected"
android:id="@+id/bt_yesterday" />
......省略號......
</LinearLayout>
關鍵代碼:
public class TabButton extends Button {
private int normal_bg_res;
private int selected_bg_res;
public TabButton(Context context) {
super(context);
}
public TabButton(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typeArray = context.obtainStyledAttributes(attrs, R.styleable.TabButton);
normal_bg_res = typeArray.getResourceId(R.styleable.TabButton_normal_bg_res, 0);
selected_bg_res = typeArray.getResourceId(R.styleable.TabButton_selected_bg_res, 0);
typeArray.recycle();
}
public void setSelected(boolean selected) {
if (selected) {
setBackgroundResource(selected_bg_res);
setTextColor(Color.WHITE);
} else {
setBackgroundResource(normal_bg_res);
setTextColor(getResources().getColor(R.color.blue));
}
}
}
利用佈局文件實現(複雜、靈活)
更多樣式,可以參數官方的SDK(android-sdk-windows\platforms\android-1.5\data\res\)
佈局樣式button_style:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape android:shape="rectangle">
<solid android:color="#0d76e1" />
</shape>
</item>
<item android:state_focused="true">
<shape android:shape="rectangle">
<solid android:color="@color/Grey" />
</shape>
</item>
<item>
<shape android:shape="rectangle">
<solid android:color="@color/Grey" />
</shape>
</item>
</selector>
樣式應用:
<Button android:id="@+id/tab_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/button_style">
蒙板效果的實現
- 不保留標題
- 保留標題
不保留標題欄蒙板的實現
效果:
原理:
- 彈窗時,設置背景窗體的透明度
- 取消彈窗時,恢復背景窗體的透明度
關鍵代碼:
private void popupWindows(List<String> list){
//產生背景變暗效果
WindowManager.LayoutParams lp=getWindow().getAttributes();
lp.alpha = 0.4f;
getWindow().setAttributes(lp);
......省略號......
mPopupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
@Override
public void onDismiss() {
WindowManager.LayoutParams lp = getWindow().getAttributes();
lp.alpha = 1f;
getWindow().setAttributes(lp);
}
});
......省略號......
}
保留標題欄蒙板的實現
效果:
原理:
- 根據需求,設置蒙板佈局大小。
- 彈窗時,顯示蒙板佈局
- 取消彈窗時,隱藏蒙板佈局
- 關鍵代碼:
蒙板佈局實現:
<!-- popup蒙板 -->
<LinearLayout
android:id="@+id/ll_popup_hide"
android:layout_width="match_parent"
android:background="@color/hide_bg"
android:orientation="vertical"
android:layout_height="match_parent">
</LinearLayout>
<color name="hide_bg">#88323232</color>
代碼處理
ll_popup_hide.setVisibility(View.VISIBLE); //顯示蒙板
ll_popup_hide.setVisibility(View.INVISIBLE); //隱藏蒙板
Activity的回收與操作超時的處理
Activity的回收
針對多個activity退出的處理
關鍵代碼:
- 新建活動管理類
- 創建BaseActivity
新建活動管理類:
public class ActivityCollector {
private static List<Activity> activityList = new ArrayList<Activity>();
public static void addActivity(Activity activity){
activityList.add(activity);
}
public static void removeActivity(Activity activity){
activityList.remove(activity);
}
public static void finishAllButLast(){
Activity activity = activityList.get(activityList.size()-1);
removeActivity(activity);
for (Activity activityItem: activityList){
if (!activityItem.isFinishing()){
activityItem.finish();
}
}
activityList.clear();
activityList.add(activity);
}
public static void finishAll(){
for (Activity activity: activityList){
if (!activity.isFinishing()){
activity.finish();
}
}
activityList.clear();
}
}
創建基類BaseActivity,並使所有的activity繼承自該基類 。在創建時,添加到活動管理器,銷燬時,從活動管理器中移除。
public class BaseActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityCollector.addActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
ActivityCollector.removeActivity(this);
}
}
如果需要銷燬所有activity,只需調用finishAll()即可
操作超時處理
原理:
- 在activity的stop函數中,根據app進程IMPORTANCE_FOREGROUND判斷app在前臺或後臺
- 在activity的onResume函數中,做超時檢查。
關鍵代碼:
abstract public class TimeOutCheckActivity extends BaseActivity {
private boolean isLeave = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
pref = getSharedPreferences(Constant.CONFIG_NAME, Context.MODE_PRIVATE);
}
/**
* 回調函數,方便測試
* @return
*/
abstract protected String getTag();
......省略號......
/***
* 當用戶使程序恢復爲前臺顯示時執行onResume()方法,在其中判斷是否超時.
*/
@Override
protected void onResume() {
// Log.i("Back",getTag() + ",onResume,是否在前臺:" + isOnForeground());
super.onResume();
if (isLeave) {
isLeave = false;
timeOutCheck();
}
}
@Override
protected void onStop() {
super.onStop();
if (!isOnForeground()){
if (!isLeave && isOpenALP()) {
isLeave = true;
saveStartTime();
}
}
}
public void timeOutCheck() {
long endtime = System.currentTimeMillis();
if (endtime - getStartTime() >= Constant.TIMEOUT_ALP * 1000) {
Util.toast(this, "超時了,請重新驗證");
String alp = pref.getString(Constant.ALP, null);
if (alp == null || alp == "") {
} else {
Intent intent = new Intent(this, UnlockGesturePasswordActivity.class);
intent.putExtra("pattern", alp);
intent.putExtra("login",false); //手勢驗證,不進行登錄驗證
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
// 打開新的Activity
startActivityForResult(intent, Constant.REQ_COMPARE_PATTERN_TIMEOUT_CHECK);
}
}
}
public void saveStartTime() {
pref.edit().putLong(Constant.START_TIME, System.currentTimeMillis()).commit();
}
public long getStartTime() {
long startTime = 0;
try {
startTime = pref.getLong(Constant.START_TIME, 0);
}catch (Exception e){
startTime = 0;
}
return startTime;
}
/**
* 程序是否在前端運行,通過枚舉運行的app實現。防止重複超時檢測多次,保證只有一個activity進入超時檢測
*當用戶按home鍵時,程序進入後端運行,此時會返回false,其他情況引起activity的stop函數的調用,會返回true
* @return
*/
public boolean isOnForeground() {
ActivityManager activityManager = (ActivityManager) getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
String packageName = getApplicationContext().getPackageName();
List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
if (appProcesses == null)
return false;
for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
if (appProcess.processName.equals(packageName)
&& appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
return true;
}
}
return false;
}
}
補充說明:
可以根據importance的不同來判斷前臺或後臺,RunningAppProcessInfo 裏面的常量IMTANCE就是上面所說的前臺後臺,其實IMOPORTANCE是表示這個app進程的重要性,因爲系統回收時候,會根據IMOPORTANCE來回收進程的。具體可以去看文檔。
by .k
關注"編程v",每一天漲一點
STAY HUNGRY & STAY FOOLISH