现在的项目要增加一个用户反馈功能,由于是临时提出的需求也没有UI设计,就想到参照微信的设计来实现。
先看微信的效果:
在web端实现的,开始也想过做在web端这样更灵活,但目前的架构还是传统的纯native应用,这么搞太麻烦,估计要捣鼓一段时间,还是就用android端实现。
功能分析
用户点击添加图片按钮后在手机图片库中选择照片,选定后展示出来,最多选择4张,水平排列,一行不够排两行,就想到GridView结合startActivityForResult+ 线程池来实现。因为是上传图片,最好转变为webp格式,再加上像素压缩处理,可以大大的节省用户流量。
流程:
输入文字->startActivityForResult选择照片->onActivityResult中更新GridView->点击提交->弹出等待提示框->创建文件List,文字保存为txt格式,从GridView中取出图片保存为webp格式->线程池上传文件->上传结束关闭等待提示框。
功能实现
想起来简单做起来的时候才发现很多问题,上传图片时想弹出一个含旋转动画的等待提示框(继承DialogFragment),每个线程执行完后得到执行结果,根据结果设置等待提示框的文字,采用ExecutorService.submit() + future.get()实现时发现一个奇怪的现象:从点击提交到上传结束没有看到等待提示框,后来一步步排查问题发现,ExecutorService.submit()之后就会进入future.get(),get()方法会阻塞UI线程,导致无法弹出提示框;当线程池结束才回到UI线程,此时会先处理之前的任务(弹出等待提示框),但由于线程池结束又调用了DialogFragment的dissmiss()方法,等待提示框很快出现又很快消失了。
好吧,由于没有搞懂线程池浪费了大把时间,代码也白写了,但也让我对线程池加深了认识,这一特性大概几年都不会忘了,于是又重新考虑实现方式。
最终的实现方式是自定义一个线程池,用一个循环执行Runnable。
自定义线程池:
class MyThreadPool extends ThreadPoolExecutor {
private List<File> fileList;
private MyThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, List<File> files) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
fileList = files;
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
}
@Override
protected void terminated() {
super.terminated();
//当调用shutDown()或者shutDownNow()时会触发该方法,关掉等待框
waitingDialog.dismissAllowingStateLoss();
for (File file:fileList){
deleteOldLogcatFile(getApplicationContext(), file.getName());
}
//必须用looper才能在线程中用toast
Looper.prepare();
if (uploadSucc){
Toast.makeText(getApplicationContext(), "上传完成", Toast.LENGTH_LONG).show();
} else{
Toast.makeText(getApplicationContext(), "上传失败!", Toast.LENGTH_LONG).show();
}
Looper.loop();
}
}
自定义的好处是在线程池结束后可以自定义提示信息,在terminated()中删除了旧文件,并根据每个线程结果提示上传成功还是失败。
调用自定义线程
MyThreadPool myThreadPool = new MyThreadPool(2, 4, 1,
TimeUnit.MINUTES, new ArrayBlockingQueue<Runnable>(fileCount), files);
for (int i = 0; i < fileCount; i++) {
final File file = files.get(i);
Runnable runnable = new Runnable(){
@Override
public void run() {
int ret = ftpUpload(ftpUrl, fptPort, ftpUserName, ftpPassword, ftpRemotePath, path, file.getName());
ECMLog.i_ui(CLASS_TAG, "ftpUpload ret: " + ret);
uploadSucc = ret == FTP_UPLOAD_SUCC;
}
};
myThreadPool.execute(runnable);
}
myThreadPool.shutdown();
还有个效果是点击选择好的图片后放大显示图片,图片下方有个删除图标能删除图片,这也是通过startActivityForResult实现,在onActivityResult中删除GridView中对应图片。
图片处理
startActivityForResult要用两次,一次是添加图片,一次是点击已添加的图片然后重新启动一个activity显示图片,在这个activity中能删除已选择的图片。
图片的压缩处理:onActivityResult拿到图片uri,根据uri得到路径,通过路径加上像素压缩算法得到bitmap,再调用bitmap.compress()得到webp格式的图片文件。
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK && requestCode == REQUEST_ADD_PIC) {
Uri uri = data.getData();
String picPath = Utils.getFilePathFromUri(getApplicationContext(), uri);
if (picPath == null){
ContentResolver cr = this.getContentResolver();
try {
mBitmap = BitmapFactory.decodeStream(cr.openInputStream(uri));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}else {
mBitmap = Utils.getSmallBitmap(picPath, 480, 800);
}
//加到第一个
mGridViewUris.add(0, uri);
mGridViewDatas.add(0, mBitmap);
mGridViewAdapter=new GridViewAdapter(getApplication(), mGridViewDatas, MAX_UPLOAD_PIC);
mGridView.setAdapter(mGridViewAdapter);
mGridViewAdapter.notifyDataSetChanged();
}else if (resultCode == RESULT_OK && requestCode == REQUEST_SHOW_PIC){
int position = data.getIntExtra(PIC_POSITION, 0);
mGridViewDatas.remove(position);
mGridViewUris.remove(position);
mGridViewAdapter=new GridViewAdapter(getApplication(), mGridViewDatas, MAX_UPLOAD_PIC);
mGridView.setAdapter(mGridViewAdapter);
mGridViewAdapter.notifyDataSetChanged();
}
//包含添加图片
int totalPictures = mGridViewDatas.size();
currentPicTV.setText(String.valueOf(totalPictures - 1));
}
public static String getFilePathFromUri(final Context context, final Uri uri) {
// final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
}
// MediaProvider
else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[] { split[1] };
return getDataColumn(context, contentUri, selection, selectionArgs);
}
} else if ("content".equalsIgnoreCase(uri.getScheme())) {
// Return the remote address
if (isGooglePhotosUri(uri))
return uri.getLastPathSegment();
return getDataColumn(context, uri, null, null);
} else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
}
return null;
}
//根据路径获得图片并压缩,返回bitmap用于显示
public static Bitmap getSmallBitmap(String filePath, int reqWidth, int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePath, options);
// options.inSampleSize = calculateInSampleSize(options, 480, 800);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(filePath, options);
}
//计算图片的缩放值
private static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int heightRatio = Math.round((float) height/ (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
通过以上方式,一个截图文件原始大小300KB,被压缩到10KB。
下面是完整代码
自定义GridView适配器:
package com.cetcs.ecmapplication.innerclass;
import android.app.Application;
import android.graphics.Bitmap;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import com.cetcs.ecmapplication.R;
import com.cetcs.ecmcommon.ECMLog;
import com.cetcs.ecmcommon.GlobalData;
import java.util.List;
public class GridViewAdapter extends BaseAdapter {
private String CLASS_TAG = getClass().getCanonicalName();
private List<Bitmap> mList;
LayoutInflater mLayoutInflater;
private ImageView mImageView;
private Application mAppllication;
private int mSize;
public GridViewAdapter(Application application, List<Bitmap> list, int size){
mAppllication = application;
mList = list;
mLayoutInflater = LayoutInflater.from(application);
mSize = size;
}
@Override
public int getCount() {
return mList.size();
}
@Override
public Object getItem(int position) {
return mList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ECMLog.i_ui(CLASS_TAG, "position: " + position + " mList size: " + mList.size());
convertView = mLayoutInflater.inflate(R.layout.gridviewlayout, null);
mImageView = (ImageView) convertView.findViewById(R.id.ItemImage);
mImageView.setImageBitmap(mList.get(position));
GlobalData globalData = (GlobalData) mAppllication;
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) mImageView.getLayoutParams();
layoutParams.height = 240 * globalData.mScreenHeight / 1920;
layoutParams.width = 240 * globalData.mScreenWidth / 1080;
mImageView.setLayoutParams(layoutParams);
return convertView;
}
}
gridviewlayout.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:paddingBottom="4dip" android:layout_width="fill_parent">
<ImageView
android:layout_height="wrap_content"
android:id="@+id/ItemImage"
android:layout_width="wrap_content"
android:layout_centerHorizontal="true">
</ImageView>
</RelativeLayout>
activity的layout
activity_feedback.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:RoundCornerTextView="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_feedback"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:background="#f0f0f0"
tools:context="com.cetcs.ecmapplication.FeedbackActivity">
<TextView
android:id="@+id/titleTV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="反馈和意见"
android:layout_marginStart="10dp"
android:layout_marginBottom="10dp"
/>
<RelativeLayout
android:id="@+id/textviewLayout"
android:layout_below="@id/titleTV"
android:layout_width="match_parent"
android:background="@color/white"
android:layout_height="wrap_content">
<EditText
android:id="@+id/feedbackET"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请填写10个以上的问题描述"
android:background="@color/white"
android:paddingStart="10dp"
android:paddingEnd="10dp" />
<TextView
android:id="@+id/allowedTV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_marginEnd="10dp"
android:text="/200"/>
<TextView
android:id="@+id/currentNumberTV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_toStartOf="@id/allowedTV"
android:text="0"/>
</RelativeLayout>
<RelativeLayout
android:id="@+id/pictureLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:layout_below="@id/textviewLayout">
<TextView
android:id="@+id/pictureTV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:textColor="@color/black"
android:text="图片(问题截图,选填)"/>
<TextView
android:id="@+id/allowedPicTV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_marginEnd="10dp"
android:text="/4"/>
<TextView
android:id="@+id/currentPicTV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toStartOf="@id/allowedPicTV"
android:text="0"/>
<GridView
android:id="@+id/gridview"
android:layout_marginTop="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/pictureTV"
android:numColumns="3"
android:verticalSpacing="10dp"
android:horizontalSpacing="10dp"
android:columnWidth="90dp"
android:stretchMode="columnWidth"
android:gravity="center">
</GridView>
</RelativeLayout>
<TextView
android:id="@+id/telTV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/pictureLayout"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:text="联系电话"
android:layout_marginStart="10dp"
/>
<EditText
android:id="@+id/telET"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/telTV"
android:background="@color/white"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:hint="选填"/>
<!--自定义控件,圆角button-->
<acxingyun.cetcs.com.roundconertextview.RoundConerTextView
android:id="@+id/submitBT"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
RoundCornerTextView:Text="@string/tijiao"
RoundCornerTextView:BackgroundColor="@color/roundconerunpressed"
RoundCornerTextView:ConerRadius="5dp"
RoundCornerTextView:TextColor="@android:color/white"
RoundCornerTextView:pressedColor="@color/finish_pressed"
RoundCornerTextView:unPressedColor="@color/finish_unpressed"
/>
</RelativeLayout>
FeedbackActivity
package com.cetcs.ecmapplication;
import android.content.ContentResolver;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.os.Looper;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.View;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.GridView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.cetcs.ecmapplication.dialog.WaitingDialog;
import com.cetcs.ecmapplication.innerclass.GridViewAdapter;
import com.cetcs.ecmcommon.ECMActivity;
import com.cetcs.ecmcommon.ECMLog;
import com.cetcs.ecmcommon.GlobalData;
import com.cetcs.ecmcommon.Utils;
import com.cetcs.jniencardmanager.JniCardLoginedInfo;
import com.cetcs.jniencardmanager.JniCardUnloginInfo;
import com.cetcs.jniencardmanager.JniEnCardManager;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import acxingyun.cetcs.com.roundconertextview.RoundConerTextView;
import static android.util.TypedValue.COMPLEX_UNIT_PX;
import static com.cetcs.ecmcommon.Constants.FTP_UPLOAD_SUCC;
import static com.cetcs.logreport.LogcatUploaderUtils.appendStringToFile;
import static com.cetcs.logreport.LogcatUploaderUtils.deleteOldLogcatFile;
import static com.cetcs.logreport.LogcatUploaderUtils.ftpUpload;
import static com.cetcs.logreport.LogcatUploaderUtils.writeStringToFile;
public class FeedbackActivity extends ECMActivity {
private final String CLASS_TAG = this.getClass().getSimpleName();
private RelativeLayout textviewLayout;
private RelativeLayout pictureLayout;
private EditText telET;
private TextView currentPicTV;
private RoundConerTextView submitBT;
private EditText feedbackET;
private TextWatcher watcher;
private TextView currentNumberTV;
private List<Bitmap> mGridViewDatas;
private List<Uri> mGridViewUris;
private GridView mGridView;
private GridViewAdapter mGridViewAdapter;
private Bitmap mBitmap;
private static int REQUEST_ADD_PIC = 1;
private static int REQUEST_SHOW_PIC = 2;
private static int MAX_UPLOAD_PIC = 4;
private static String SHOW_PIC_URI = "show_pic_full_screen";
private static String PIC_POSITION = "picture_position";
private WaitingDialog waitingDialog;
private static final String ftpUrl = xxxxx.xxxxx.xxxxx;
private static final String fptPort = xxxx;
private static final String ftpUserName = xxxx;
private static final String ftpPassword = xxxx;
private static final String ftpRemotePath = "/aaa/bbb";
private boolean uploadSucc = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);//取消标题栏
setContentView(R.layout.activity_feedback);
initViews();
initLayoutParameters();
}
private void initViews(){
textviewLayout = (RelativeLayout) findViewById(R.id.textviewLayout);
pictureLayout = (RelativeLayout) findViewById(R.id.pictureLayout);
telET = (EditText) findViewById(R.id.telET);
submitBT = (RoundConerTextView) findViewById(R.id.submitBT);
feedbackET = (EditText) findViewById(R.id.feedbackET);
currentNumberTV = (TextView) findViewById(R.id.currentNumberTV);
//监控输入字数
watcher = new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// TODO Auto-generated method stub
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
// TODO Auto-generated method stub
}
@Override
public void afterTextChanged(Editable s) {
// TODO Auto-generated method stub
int len = feedbackET.getText().length();
ECMLog.i_ui(CLASS_TAG, "onTextChanged len:" + len);
if (len >= 200){
currentNumberTV.setText(String.valueOf(len));
currentNumberTV.setTextColor(getResources().getColor(R.color.deep_orange));
}else {
currentNumberTV.setTextColor(getResources().getColor(R.color.black));
currentNumberTV.setText(String.valueOf(len));
}
}
};
feedbackET.addTextChangedListener(watcher);
mGridView=(GridView) findViewById(R.id.gridview);
mGridViewDatas = new ArrayList<>();
mGridViewUris = new ArrayList<>();
Bitmap addBitmap = Utils.getInstance().readBitmap(getApplicationContext(), R.drawable.add_pic);
mGridViewDatas.add(0, addBitmap);
mGridViewUris.add(0, new Uri.Builder().build());
mGridViewAdapter = new GridViewAdapter(getApplication(), mGridViewDatas, MAX_UPLOAD_PIC);
mGridView.setAdapter(mGridViewAdapter);
mGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
int pictureNumber = mGridViewDatas.size();
ECMLog.i_ui(CLASS_TAG,"position: " + position + " pictureNumber: " + pictureNumber);
if (position == pictureNumber - 1){
if (pictureNumber < MAX_UPLOAD_PIC + 1){
chosePictureFromPhone();
}
}else {
Uri uri = mGridViewUris.get(position);
Intent intent = new Intent(FeedbackActivity.this, PicShowerActivity.class);
intent.putExtra(SHOW_PIC_URI, uri.toString());
intent.putExtra(PIC_POSITION, position);
startActivityForResult(intent, REQUEST_SHOW_PIC);
}
}
});
submitBT.setOnTouchCallback(new RoundConerTextView.onTouchCallback() {
@Override
public void onRoundTextViewTouched() {
if (feedbackET.getText().length() == 0){
Toast.makeText(getApplicationContext(), "请输入您的建议", Toast.LENGTH_SHORT).show();
return;
}
if (feedbackET.getText().length() > 200){
Toast.makeText(getApplicationContext(), "请输入200字以内建议", Toast.LENGTH_SHORT).show();
return;
}
waitingDialog = new WaitingDialog();
waitingDialog.setCancelable(false);
waitingDialog.show(getFragmentManager(), getString(R.string.zhengzaishangchaunqingshaohou));
List<File> list = creatFilesThread();
if (list == null){
waitingDialog.dismissAllowingStateLoss();
Toast.makeText(getApplicationContext(), "网络不可用!", Toast.LENGTH_SHORT).show();
return;
}
ExecutorServiceThread(list);
}
});
}
private void initLayoutParameters(){
GlobalData globalDate = (GlobalData) getApplication();
RelativeLayout.LayoutParams layoutparam = (RelativeLayout.LayoutParams) textviewLayout.getLayoutParams();
layoutparam.height = 350 * globalDate.mScreenHeight / 1920;
textviewLayout.setLayoutParams(layoutparam);
//反馈和意见
TextView titleTV = (TextView) findViewById(R.id.titleTV);
titleTV.setTextSize(COMPLEX_UNIT_PX, 40 * globalDate.mScreenHeight / 1920);
//反馈文字
feedbackET = (EditText) findViewById(R.id.feedbackET);
feedbackET.setTextSize(COMPLEX_UNIT_PX, 50 * globalDate.mScreenHeight / 1920);
//200
TextView allowedTV = (TextView) findViewById(R.id.allowedTV);
allowedTV.setTextSize(COMPLEX_UNIT_PX, 40 * globalDate.mScreenHeight / 1920);
//字数
TextView currentNumberTV = (TextView) findViewById(R.id.currentNumberTV);
currentNumberTV.setTextSize(COMPLEX_UNIT_PX, 40 * globalDate.mScreenHeight / 1920);
layoutparam = (RelativeLayout.LayoutParams) pictureLayout.getLayoutParams();
layoutparam.height = 430 * globalDate.mScreenHeight / 1920;
layoutparam.topMargin = 60 * globalDate.mScreenHeight / 1920;
pictureLayout.setLayoutParams(layoutparam);
TextView pictureTV = (TextView) findViewById(R.id.pictureTV);
pictureTV.setTextSize(COMPLEX_UNIT_PX, 46 * globalDate.mScreenHeight / 1920);
TextView allowedPicTV = (TextView) findViewById(R.id.allowedPicTV);
allowedPicTV.setTextSize(COMPLEX_UNIT_PX, 40 * globalDate.mScreenHeight / 1920);
currentPicTV = (TextView) findViewById(R.id.currentPicTV);
currentPicTV.setTextSize(COMPLEX_UNIT_PX, 40 * globalDate.mScreenHeight / 1920);
TextView telTV = (TextView) findViewById(R.id.telTV);
telTV.setTextSize(COMPLEX_UNIT_PX, 40 * globalDate.mScreenHeight / 1920);
telET = (EditText) findViewById(R.id.telET);
telET.setTextSize(COMPLEX_UNIT_PX, 50 * globalDate.mScreenHeight / 1920);
layoutparam = (RelativeLayout.LayoutParams) telET.getLayoutParams();
layoutparam.height = 128 * globalDate.mScreenHeight / 1920;
telET.setLayoutParams(layoutparam);
if (globalDate.getChannelName().equals("westone")){
submitBT.setBackgroundColor(getResources().getColor(R.color.westonewanchengzhengchang));
submitBT.setPressedColor(getResources().getColor(R.color.westonewanchengdianji));
submitBT.setUnPressedColor(getResources().getColor(R.color.westonewanchengzhengchang));
}
submitBT.setWidth(990 * globalDate.mScreenWidth / 1080);
submitBT.setHeight(135 * globalDate.mScreenHeight / 1920);
submitBT.setTextSize(60 * globalDate.mScreenHeight / 1920);
submitBT.invalidate();
}
private void chosePictureFromPhone(){
Intent intent = new Intent();
/* 开启Pictures画面Type设定为image */
intent.setType("image/*");
/* 使用Intent.ACTION_GET_CONTENT这个Action */
intent.setAction(Intent.ACTION_GET_CONTENT);
/* 取得相片后返回本画面 */
startActivityForResult(intent, REQUEST_ADD_PIC);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK && requestCode == REQUEST_ADD_PIC) {
Uri uri = data.getData();
String picPath = Utils.getFilePathFromUri(getApplicationContext(), uri);
if (picPath == null){
ContentResolver cr = this.getContentResolver();
try {
mBitmap = BitmapFactory.decodeStream(cr.openInputStream(uri));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}else {
mBitmap = Utils.getSmallBitmap(picPath, 480, 800);
}
//加到第一个
mGridViewUris.add(0, uri);
mGridViewDatas.add(0, mBitmap);
mGridViewAdapter=new GridViewAdapter(getApplication(), mGridViewDatas, MAX_UPLOAD_PIC);
mGridView.setAdapter(mGridViewAdapter);
mGridViewAdapter.notifyDataSetChanged();
}else if (resultCode == RESULT_OK && requestCode == REQUEST_SHOW_PIC){
int position = data.getIntExtra(PIC_POSITION, 0);
mGridViewDatas.remove(position);
mGridViewUris.remove(position);
mGridViewAdapter=new GridViewAdapter(getApplication(), mGridViewDatas, MAX_UPLOAD_PIC);
mGridView.setAdapter(mGridViewAdapter);
mGridViewAdapter.notifyDataSetChanged();
}
//包含添加图片
int totalPictures = mGridViewDatas.size();
currentPicTV.setText(String.valueOf(totalPictures - 1));
}
private List<File> creatUploadFiles(){
//包含文本、图片
List<File> fileList = new ArrayList<>();
File suggestFile = creatSuggestionFile();
if (suggestFile != null){
String tel = telET.getText().toString();
if (tel.length() > 0){
tel = "\n" + "tel:" + tel;
appendStringToFile(getApplicationContext(), suggestFile.getName(), tel);
}
fileList.add(suggestFile);
}
//不包括添加图片
int picCount = mGridViewDatas.size() - 1;
for (int i = 0;i < picCount;i++){
//phoneNumber_tfNumber_20170101010101_1
String bitmapFileName = getFileName() + "_" + i + ".webp";
Bitmap bitmap = mGridViewDatas.get(i);
File bitmapFile = saveBitmapToFile(bitmap, bitmapFileName);
fileList.add(bitmapFile);
}
return fileList;
}
/**
* phoneNumber_tfNumber_time
* 没有后缀
* @return
*/
private String getFileName(){
String fileName;
String phoneNumber = "";
String tfNumber = "";
String time = "";
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault());
time = sdf.format(new Date());
..............
fileName = phoneNumber +"_" + tfNumber +"_" + time;
return fileName;
}
private File creatSuggestionFile(){
String fileName;
fileName = getFileName() + ".txt";
String path = getApplicationContext().getFilesDir().getAbsolutePath();
File file = new File(path, fileName);
//保存String为文件
writeStringToFile(getApplication(), feedbackET.getText().toString(), file.getName());
return file;
}
private File saveBitmapToFile(Bitmap bitmap, String fileName){
String path = getApplicationContext().getFilesDir().getAbsolutePath();
File file = new File(path, fileName);
if(file.exists()){
file.delete();
}
FileOutputStream out;
try{
out = new FileOutputStream(file);
if(bitmap.compress(Bitmap.CompressFormat.WEBP, 50, out))
{
out.flush();
out.close();
}
}
catch (FileNotFoundException e)
{
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return file;
}
private List<File> creatFilesThread(){
List<File> fileList = null;
ExecutorService cacheThreadExecutor = Executors.newSingleThreadExecutor();
Future<List<File>> future = cacheThreadExecutor.submit(new preUploadTask());
try {
fileList = future.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
return fileList;
}
private void ExecutorServiceThread(List<File> files) {
final String path = getApplicationContext().getFilesDir().getAbsolutePath();
int fileCount = files.size();
MyThreadPool myThreadPool = new MyThreadPool(2, 4, 1,
TimeUnit.MINUTES, new ArrayBlockingQueue<Runnable>(fileCount), files);
for (int i = 0; i < fileCount; i++) {
final File file = files.get(i);
Runnable runnable = new Runnable(){
@Override
public void run() {
int ret = ftpUpload(ftpUrl, fptPort, ftpUserName, ftpPassword, ftpRemotePath, path, file.getName());
ECMLog.i_ui(CLASS_TAG, "ftpUpload ret: " + ret);
uploadSucc = ret == FTP_UPLOAD_SUCC;
}
};
myThreadPool.execute(runnable);
}
myThreadPool.shutdown();
}
class preUploadTask implements Callable<List<File>>{
@Override
public List<File> call() throws Exception {
if (!Utils.ping()){
return null;
}
return creatUploadFiles();
}
}
class MyThreadPool extends ThreadPoolExecutor {
private List<File> fileList;
private MyThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, List<File> files) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
fileList = files;
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
}
@Override
protected void terminated() {
super.terminated();
//当调用shutDown()或者shutDownNow()时会触发该方法
waitingDialog.dismissAllowingStateLoss();
for (File file:fileList){
deleteOldLogcatFile(getApplicationContext(), file.getName());
}
//必须用looper才能在线程中用toast
Looper.prepare();
if (uploadSucc){
Toast.makeText(getApplicationContext(), "上传完成", Toast.LENGTH_LONG).show();
} else{
Toast.makeText(getApplicationContext(), "上传失败!", Toast.LENGTH_LONG).show();
}
Looper.loop();
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
return super.onKeyDown(keyCode, event);
}
@Override
protected void onDestroy() {
if (mBitmap != null &&!mBitmap.isRecycled()){
mBitmap.recycle();
mBitmap = null;
}
mGridView = null;
super.onDestroy();
}
/**
* 通过ftp上传文件
* @param url ftp服务器地址 如: 192.168.1.110
* @param port 端口如 : 21
* @param username 登录名
* @param password 密码
* @param remotePath 上到ftp服务器的磁盘路径
* @param fileNamePath 要上传的文件路径
* @param fileName 要上传的文件名
* @return
*/
public static int ftpUpload(String url, String port, String username,String password, String remotePath, String fileNamePath,String fileName) {
ECMLog.i_ecm(CLASS_TAG, "ftpUpload called,fileName: " + fileName);
FTPClient ftpClient = new FTPClient();
FileInputStream fis = null;
int result = 0;
try {
ftpClient.setConnectTimeout(5 * 1000);
ftpClient.connect(url, parseInt(port));
ECMLog.i_ecm(CLASS_TAG, "connected...");
boolean loginResult = ftpClient.login(username, password);
int returnCode = ftpClient.getReplyCode();
ECMLog.i_ecm(CLASS_TAG, "ftp returnCode: " + returnCode);
if (loginResult && FTPReply.isPositiveCompletion(returnCode)) {// 如果登录成功
ftpClient.makeDirectory(remotePath);
// 设置上传目录
ftpClient.changeWorkingDirectory(remotePath);
ftpClient.setBufferSize(1024);
if (fileName.endsWith(".png") || fileName.endsWith(".webp") || fileName.endsWith(".jpeg")){
//上传上去的图片数据格式()一定要写这玩意,不然在服务器就打不开了
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
}
ftpClient.setControlEncoding("UTF-8");
ftpClient.enterLocalPassiveMode();
fis = new FileInputStream(fileNamePath + "/" + fileName);
ftpClient.storeFile(fileName, fis);
result = FTP_UPLOAD_SUCC; //上传成功
} else {// 如果登录失败
result = FTP_LOGIN_ERR;
}
} catch (IOException e) {
ECMLog.e_ecm(CLASS_TAG, "ftp err...");
e.printStackTrace();
result = FTP_CLIENT_ERR;
// throw new RuntimeException("FTP客户端出错!", e);
} finally {
try {
ftpClient.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
}
展示图片的activity,PicShowerActivity:
package com.cetcs.ecmapplication;
import android.content.ContentResolver;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import com.cetcs.ecmapplication.dialog.ActionConfirmActivity;
import com.cetcs.ecmcommon.Constants;
import com.cetcs.ecmcommon.ECMActivity;
import com.cetcs.ecmcommon.ECMLog;
import com.cetcs.ecmcommon.GlobalData;
import com.cetcs.ecmcommon.Utils;
import java.io.FileNotFoundException;
import static com.cetcs.ecmcommon.Constants.ACTIONCONFIRM_ACTIVITY_DELETE_PIC;
public class PicShowerActivity extends ECMActivity {
private ImageView mShowedIV;
private ImageView mDeleteIV;
private static String SHOW_PIC_URI = "show_pic_full_screen";
private final String CLASS_TAG = this.getClass().getSimpleName();
private static String PIC_POSITION = "picture_position";
private int picPosition;
private Bitmap mBitmap;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ECMLog.i_ui(CLASS_TAG, "onCreate called...");
requestWindowFeature(Window.FEATURE_NO_TITLE);//取消标题栏
setContentView(R.layout.activity_pic_shower);
initViews();
initLayoutParameters();
}
private void initViews(){
mShowedIV = (ImageView) findViewById(R.id.mShowedIV);
mDeleteIV = (ImageView) findViewById(R.id.mDeleteIV);
Intent intent = getIntent();
if (intent != null){
//position
picPosition = intent.getIntExtra(PIC_POSITION, 0);
ECMLog.i_ui(CLASS_TAG, "picPosition: " + picPosition);
//Uri
String uriString = intent.getStringExtra(SHOW_PIC_URI);
Uri uri = Uri.parse(uriString);
mBitmap = null;
String picPath = Utils.getFilePathFromUri(getApplicationContext(), uri);
if (picPath == null){
ContentResolver cr = this.getContentResolver();
try {
mBitmap = BitmapFactory.decodeStream(cr.openInputStream(uri));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}else {
mBitmap = Utils.getSmallBitmap(picPath, 480, 800);
}
mShowedIV.setImageBitmap(mBitmap);
}
mDeleteIV.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent1 = new Intent(PicShowerActivity.this, ActionConfirmActivity.class);
intent1.putExtra(Constants.KEY_INFORMATIONTYPE, ACTIONCONFIRM_ACTIVITY_DELETE_PIC);
intent1.putExtra(Constants.KEY_INFORMATION, getString(R.string.quedingshanchutupian));
startActivityForResult(intent1, ACTIONCONFIRM_ACTIVITY_DELETE_PIC);
}
});
mShowedIV.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setResult(RESULT_CANCELED);
finish();
}
});
}
private void initLayoutParameters(){
GlobalData globalData = (GlobalData) getApplication();
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) mShowedIV.getLayoutParams();
layoutParams.width = 800 * globalData.mScreenWidth / 1080;
layoutParams.height = 1400 * globalData.mScreenHeight / 1920;
mShowedIV.setLayoutParams(layoutParams);
layoutParams = (RelativeLayout.LayoutParams) mDeleteIV.getLayoutParams();
layoutParams.height = 68 * globalData.mScreenHeight / 1920;
layoutParams.width = 68 * globalData.mScreenWidth / 1080;
mDeleteIV.setLayoutParams(layoutParams);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == ACTIONCONFIRM_ACTIVITY_DELETE_PIC && resultCode == RESULT_OK){
Intent intent = new Intent();
intent.putExtra(PIC_POSITION, picPosition);
setResult(RESULT_OK, intent);
finish();
}
}
@Override
protected void onDestroy() {
if (!mBitmap.isRecycled()){
mBitmap.recycle();
mBitmap = null;
}
mShowedIV.setImageBitmap(null);
mDeleteIV.setImageBitmap(null);
super.onDestroy();
}
}
最终效果
用户反馈页面:
查看图片:
写这篇文章主要目的还是记录和总结一些开发经验,争取以后每开发一个功能写一篇博客。