仿微信用户反馈功能实现

现在的项目要增加一个用户反馈功能,由于是临时提出的需求也没有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();
    }
}

最终效果

用户反馈页面:
这里写图片描述
查看图片:
这里写图片描述

写这篇文章主要目的还是记录和总结一些开发经验,争取以后每开发一个功能写一篇博客。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值