我在简书也发布了一份,喜欢黑色背景的同学可以看一下

  这其实是大二下的一个课设,内容是实现实时的图片、视频以及地点上传,而我的理解就是实现一个类似于微信朋友圈或是微博的主界面的App、 但自己学得太久,用了4个月时间(19.1-19.5),主要的时间用于来搞Android。而现在目前方向变了,想系统学习一下JavaWeb前后端以及框架,写个随笔来记录一下这个Demo的关键点,方便自己日后查看一下。
  现在也学了其他的知识,其实后端的工作其实可以用InteliJ IDEA+Maven+Tomcat+Mysql来搭建,就不需要引入jar包,用框架去整合比较方便。

  这Demo的 Android的xml配置文件 相当于 web的前端。但和前端不同的是,Android需要线程进行和对服务器的通话。而主线程需要更新界面部件,只能我们自己新建一个子线程来处理,并且用messageHandler来负责服务器返回信息的处理。而上传文件/获取信息的接口 其实就是我们web的网址,我们只要将传出去的数据格式化为json数据传输给服务器Tomcat,经过服务器的验证后,用json数据返回,App就能实现在浏览器登录的效果,并且跳转。
而我这个demo没有将服务器放在云服务器中,所以测试的时候也比较麻烦,而且测试建议用真机,在电脑上跑As模拟器的确太卡,以下是项目的地址

Android Studio 前端 github 地址

Eclipse 后端 github 地址

这2个地址中都包含了我自己以及在网上找的小Demo。所以导入的时候,可以全部导入,
也可以只导入部分的,其中在Eclipse也有使用说明,也有对应数据库的sql文件。
导入部分内容:

Android Studio:导入 projectthree 和 okhttputils

Eclipse:导入 MyWebTest

最值得注意的是:

  1. Android Studiookhttputils 项目中的 gradle文件 中 要 任意 **注释 **掉其中的一句
    因为两个项目Gradle文件引用的jar相同 导致jar包冲突
implementation 'com.zhy:okhttputils:2.6.2'implementation 'com.squareup.okio:okio:2.2.2'implementation("com.squareup.okhttp3:okhttp:3.14.1")
  1. 在Eclipse 中 要 修改 Tomcat 中的server.xml 的 配置文件 将上传的文件路径改为 电脑磁盘的特定的路径 因为每次Tomcat每次接收到的文件都是存储在其默认目录中的,如果Tomcat关闭并重新启动,它会将默认的目录下的文件清空,所以需要设置它的路径 设置它的Context
    所以在这个Demo中 我将上传到的文件 存储在 F://Picture//Upload 文件中

      <Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true" xmlNamespaceAware="false" xmlValidation="false">        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" pattern="%h %l %u %t "%r" %s %b" prefix="localhost_access_log" suffix=".txt"/>             <Context docBase="F://Picture//Upload" path="/upload"/>      <Context docBase="layuiDemo" path="/ui" reloadable="true" source="org.eclipse.jst.jee.server:layuiDemo"/></Host>

3.这个Demo 我用的是Mysql8.0,因为当时没有学Maven 需要手动引入一下jar包, 其次可以在util包中的JDBCUtil中修改自己的数据库属性

public class JDBCUtil {private static String url="jdbc:mysql://localhost:3306/demodatabase?serverTimezone=UTC&&characterEncoding=utf-8";//private static String url="jdbc:mysql://172.16.86.194:3306/demodatabase?serverTimezone=UTC";private static String driverClass="com.mysql.cj.jdbc.Driver";private static String username="root";private static String password="Password";private static Connection con;


4.更改AndroidStudio 中 Projectthree 中的 上传url 更改为 本机IP地址
LoginServletActivity UploadActivityUploadVidioActivityRegisterServletActivityAppHomeFragmentAppFindFragment3private final static String Url="http://本机IP地址:8080/MyWebTest/queryServlet";
  1. 好了 最麻烦的一步来了 因为搞云服务器 需要形成一个 PAN(personal area network)
  • 你需要保持电脑有网 能用wifi 并且关闭防火墙
  • 将Android 的 projectthree导入到 手机 中(默认是Android 9.0 pie)
  • 将电脑的热点打开 并用测试手机连接上该电脑wifi 就可以运行了
  • 其实也可以借同学的手机来开一下热点 然后你的电脑和测试手机都连接同一个热点 也可以实现。但前提还是得关防火墙

万事俱备 只欠东风

看一下效果图吧

  1. 登录界面
      登录界面的 icon 可以去阿里巴巴矢量标签库中查找。登录交换的的代码与其他界面的类似,用apach的HttpClient来实现比较基础的json传输,例如:登录、注册、以及图片视频url的获取。在上传的界面其中的效果在主页也是等同的用到了MainApplication来存储用户的用户名以及密码信息,并且在MainApplication中对baidu地图以及ImageLoader图片加载的初始化
public class MainApplication extends Application {    private final static String TAG="MainApplication";    private static MainApplication mApp;    public HashMap<String,String> UserinfoMap=new HashMap<String,String>();    // 利用单例模式获取当前应用的唯一实例    public static MainApplication getInstance() {        return mApp;    }    @Override    public void onCreate() {        super.onCreate();        // 在打开应用时对静态的应用实例赋值        mApp = this;        Log.d(TAG, "onCreate");        initImageLoader();        SDKInitializer.initialize(this);        //自4.3.0起,百度地图SDK所有接口均支持百度坐标和国测局坐标,用此方法设置您使用的坐标类型.        //包括BD09LL和GCJ02两种坐标,默认是BD09LL坐标。        SDKInitializer.setCoordType(CoordType.BD09LL);    }    @Override    public void onTerminate() {        Log.d(TAG, "onTerminate");        super.onTerminate();    }    private void initImageLoader() {        ImageLoaderConfiguration configuration = ImageLoaderConfiguration.createDefault(this);        ImageLoader.getInstance().init(configuration);    }}
public class LoginServletActivity extends AppCompatActivity implements View.OnClickListener {    private final static String TAG="LoginServletActivity";    private EditText et_username;    private EditText et_password;    private TextView tv_result;//    private static String url="http://172.16.86.194:8080/MyWebTest/loginServlet";    private static String url="http://你的ip地址:8080/MyWebTest/loginServlet";    private final static int Login=1;    private final static int Fail=2;    String username=null;    String password=null;    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_login_servlet);        et_username=findViewById(R.id.et_username);        et_password=findViewById(R.id.et_password);        tv_result=findViewById(R.id.tv_result);        findViewById(R.id.btn_login).setOnClickListener(this);        findViewById(R.id.btn_register).setOnClickListener(this);    }    @Override    public void onClick(View v) {        if (v.getId()==R.id.btn_login){            username=et_username.getText().toString().trim();            password=et_password.getText().toString().trim();                new Thread(new Runnable() {                    @Override                    public void run() {                        try {                            HttpClient httpClient = new DefaultHttpClient();                            HttpPost httpPost = new HttpPost(url);                            List<NameValuePair> list = new ArrayList<NameValuePair>();                            list.add(new BasicNameValuePair("username", username));                            list.add(new BasicNameValuePair("password", password));                            //实例化                            final UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "utf-8");                            httpPost.setEntity(entity);                            HttpResponse httpResponse = httpClient.execute(httpPost);                            if (httpResponse.getStatusLine().getStatusCode() == 200) {                                HttpEntity entity1 = httpResponse.getEntity();                                String row = EntityUtils.toString(entity1, "utf-8");                                Message message = new Message();                                message.what = Login;                                message.obj = row;                                handler.sendMessage(message);                            } else {                                Message message = new Message();                                message.what = Fail;                                handler.sendMessage(message);                            }                        } catch (Exception e) {                            e.printStackTrace();                        }                    }                }).start();//            }        }else if(v.getId()==R.id.btn_register){            Intent intent=new Intent(this,RegisterServletActivity.class);            startActivity(intent);        }    }    private Handler handler=new Handler(){        @Override        public void handleMessage(Message msg) {            if(msg.what==Login){                String row=(String)msg.obj;                String[] info=row.split("#");                if(info[1].equalsIgnoreCase("SUCC")){                    tv_result.setText("Login SUCC");                    Toast.makeText(LoginServletActivity.this,"Succ",Toast.LENGTH_SHORT).show();                    MainApplication.getInstance().UserinfoMap=new HashMap<String,String>();                    MainApplication.getInstance().UserinfoMap.put("username",username);                    MainApplication.getInstance().UserinfoMap.put("password",password);                    Intent intent=new Intent(LoginServletActivity.this,AppMainActivity2.class);                    Bundle bundle=new Bundle();                    bundle.putString("username",username);                    bundle.putString("password",password);//                    Toast.makeText(LoginServletActivity.this,username+" "+password,Toast.LENGTH_SHORT).show();                    intent.putExtras(bundle);                    startActivity(intent);                }else{                    tv_result.setText("Login Fail 请检查用户名和密码是否正确");                    Toast.makeText(LoginServletActivity.this,"Fail",Toast.LENGTH_SHORT).show();                }            }else if(msg.what==Fail){                tv_result.setText("服务器繁忙");//                Toast.makeText(LoginServletActivity.this,"Succ",Toast.LENGTH_SHORT).show();            }        }    };}
  1. 第一次登录

第一次登录成功后会提示授权信息 在AppMainActivity2用了PermissionRequest进行权限的提示,gradle引用了 implementation ‘com.github.franmontiel:PersistentCookieJar:v1.0.1’ 需要编译后才能生效,并用Fragment来主要来实现界面的切换

@RuntimePermissionspublic class AppMainActivity2 extends AppCompatActivity {    private static final String TAG="AppMainActivity";    private FragmentTabHost tabHost;    public String username=null;    public String password=null;    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_app_main);        Intent intent=getIntent();        Bundle temp=intent.getExtras();        username=temp.getString("username");        password=temp.getString("password");        Bundle bundle=new Bundle();//用于传递信息        bundle.putString("tag",TAG);        bundle.putString("username",username);        bundle.putString("password",password);        tabHost=findViewById(android.R.id.tabhost);        //把内容放在标签栏正上方        tabHost.setup(this,getSupportFragmentManager(),R.id.fl_content);        //放置fragment        tabHost.addTab(getTabView(R.string.home,R.drawable.tab_home_selector), AppHomeFragment.class,bundle);        tabHost.addTab(getTabView(R.string.find,R.drawable.tab_find_selector), AppFindFragment3.class,bundle);        tabHost.addTab(getTabView(R.string.message,R.drawable.tab_message_selector), AppMessageFragment.class,bundle);        tabHost.addTab(getTabView(R.string.me,R.drawable.tab_me_selector), AppMeFragment.class,bundle);        //不设置各标签的之间的分隔线        tabHost.getTabWidget().setShowDividers(LinearLayout.SHOW_DIVIDER_NONE);        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {            //initMap();        } else {            AppMainActivity2PermissionsDispatcher.ApplySuccessWithCheck(this);        }    }    private TabSpec getTabView(int textId,int imageId){        String text=getResources().getString(textId);        Drawable drawable=getResources().getDrawable(imageId);        drawable.setBounds(0,0,drawable.getMinimumWidth(),drawable.getMinimumHeight());        View item_tabbar=getLayoutInflater().inflate(R.layout.item_tabbar,null);        TextView tv_item=item_tabbar.findViewById(R.id.tv_item_tabbar);        tv_item.setCompoundDrawables(null, drawable, null, null);        // 生成并返回该标签按钮对应的标签规格        return tabHost.newTabSpec(text).setIndicator(item_tabbar);    }    @Override    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {        super.onRequestPermissionsResult(requestCode, permissions, grantResults);        // NOTE: delegate the permission handling to generated method        AppMainActivity2PermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);    }    /**     * 申请权限成功时     */    @NeedsPermission(Manifest.permission.ACCESS_COARSE_LOCATION)    void ApplySuccess() {        //initMap();    }    /**     * 申请权限告诉用户原因时     * @param request     */    @OnShowRationale(Manifest.permission.ACCESS_COARSE_LOCATION)    void showRationaleForMap(PermissionRequest request) {        showRationaleDialog("使用此功能需要打开定位的权限", request);    }    /**     * 申请权限被拒绝时     *     */    @OnPermissionDenied(Manifest.permission.ACCESS_COARSE_LOCATION)    void onMapDenied() {        Toast.makeText(this,"你拒绝了权限,该功能不可用",Toast.LENGTH_LONG).show();    }    /**     * 申请权限被拒绝并勾选不再提醒时     */    @OnNeverAskAgain(Manifest.permission.ACCESS_COARSE_LOCATION)    void onMapNeverAskAgain() {        AskForPermission();    }    /**     * 告知用户具体需要权限的原因     * @param messageResId     * @param request     */    private void showRationaleDialog(String messageResId, final PermissionRequest request) {        new AlertDialog.Builder(this)                .setPositiveButton("确定", new DialogInterface.OnClickListener() {                    @Override                    public void onClick(@NonNull DialogInterface dialog, int which) {                        request.proceed();//请求权限                    }                })                .setNegativeButton("取消", new DialogInterface.OnClickListener() {                    @Override                    public void onClick(@NonNull DialogInterface dialog, int which) {                        request.cancel();                    }                })                .setCancelable(false)                .setMessage(messageResId)                .show();    }    /**     * 被拒绝并且不再提醒,提示用户去设置界面重新打开权限     */    private void AskForPermission() {        AlertDialog.Builder builder = new AlertDialog.Builder(this);        builder.setTitle("当前应用缺少定位权限,请去设置界面打开\n打开之后按两次返回键可回到该应用哦");        builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {            @Override            public void onClick(DialogInterface dialog, int which) {                return;            }        });        builder.setPositiveButton("设置", new DialogInterface.OnClickListener() {            @Override            public void onClick(DialogInterface dialog, int which) {                Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);                intent.setData(Uri.parse("package:" + AppMainActivity2.this.getPackageName())); // 根据包名打开对应的设置界面                startActivity(intent);            }        });        builder.create().show();    }}

3.正常的主页界面

通过设置NineGridAdapter适配器通过NineGridTestLayout来初始化图片、Framelayout初始化视频,在item_grid.xml中配置好每个部件的大小和位置,并将数据库得到的信息进行解析并设置对应的listener
如果解析后数据是图片格式就将
NineGridTestLayoutandroid:visibility="visiable"
Framelayout的android:visibility="gone"
如果解析后数据是视频格式就 反之

public class NineGridItem implements Serializable {    private static final long serialVersionUID = 2189052605715370758L;    public boolean isShowAll = false;    public String uid;//上传用户 id    public String time;//上传时间    public List<String> urlList = new ArrayList<>();//url列表    public String text;//描述    public String location;//地点    public String type;//类型 图片和视频    public String url;//    public boolean bPressed;//是否按下    public int id;// item id    private static int seq=0;//是否超过九个}
public class NineGridAdapter extends RecyclerView.Adapter<NineGridAdapter.ViewHolder> implements View.OnClickListener, View.OnLongClickListener{    private int CLICK = 0; // 正常点击    private int DELETE = 1; // 点击了删除按钮    private Context mContext;    private List<NineGridItem> mList;    protected LayoutInflater inflater;    public NineGridAdapter(Context context) {        mContext = context;        inflater = LayoutInflater.from(context);    }    public void setList(List<NineGridItem> list) {        mList = list;    }    @Override    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {        View convertView = inflater.inflate(R.layout.item_grid, parent, false);        ViewHolder viewHolder = new ViewHolder(convertView);        return viewHolder;    }    @Override    public void onBindViewHolder(ViewHolder holder, int position) {        NineGridItem item=mList.get(position);        holder.tv_uid.setText(item.getUid());        holder.tv_text.setText(item.getText());        holder.tv_location.setText(item.getLocation());        holder.tv_time.setText(item.getTime());        if(item.getType().equals("图片")){            holder.layout.setIsShowAll(mList.get(position).isShowAll);            holder.layout.setUrlList(mList.get(position).urlList);        }else{            holder.fl_video.setVisibility(View.VISIBLE);            getImage(holder.iv_videopic,item.getUrlList().get(0));            holder.fl_video.setOnClickListener(new View.OnClickListener() {                @Override                public void onClick(View v) {                    Intent intent=new Intent(mContext, MoviePlayActivity.class);                    Bundle bundle=new Bundle();                    bundle.putString("path",mList.get(position).getUrlList().get(0));                    intent.putExtras(bundle);                    mContext.startActivity(intent);                }            });        }        holder.tv_delete.setVisibility((item.bPressed) ? View.VISIBLE : View.GONE);        holder.tv_delete.setId(item.id * 10 + DELETE);        holder.ll_item.setId(item.id * 10 + CLICK);        holder.tv_delete.setOnClickListener(this);//         列表项的点击事件需要自己实现        holder.ll_item.setOnClickListener(this);//         列表项的长按事件需要自己实现        holder.ll_item.setOnLongClickListener(this);    }    public class ViewHolder extends RecyclerView.ViewHolder {        NineGridTestLayout layout;        public TextView tv_uid;        public TextView tv_delete;        public TextView tv_location;        public TextView tv_text;        public TextView tv_time;        public LinearLayout ll_item;        public FrameLayout fl_video;        public ImageView iv_pause;        public ImageView iv_videopic;        public TextView tv_url;        public ViewHolder(View itemView) {            super(itemView);            layout = (NineGridTestLayout) itemView.findViewById(R.id.layout_nine_grid);            ll_item=itemView.findViewById(R.id.ll_item);            tv_uid=itemView.findViewById(R.id.tv_uid);            tv_delete=itemView.findViewById(R.id.tv_delete);            tv_text=itemView.findViewById(R.id.tv_text);            tv_location=itemView.findViewById(R.id.tv_location);            tv_time=itemView.findViewById(R.id.tv_time);            fl_video=itemView.findViewById(R.id.fl_video);            iv_pause=itemView.findViewById(R.id.iv_pause);            iv_videopic=itemView.findViewById(R.id.iv_videopic);            tv_url=itemView.findViewById(R.id.tv_url);        }    }    private int getListSize(List<NineGridItem> list) {        if (list == null || list.size() == 0) {            return 0;        }        return list.size();    }    @Override    public int getItemCount() {        return getListSize(mList);    }    //根据列表项编号获得当前位置序号    private int getPosition(int item_id){        int pos=0;        for(int i=0;i<mList.size();i++){            if(mList.get(i).id==item_id){                pos=i;                break;            }        }        return pos;    }    public void getImage(ImageView iv_image,String path){        FFmpegMediaMetadataRetriever retriever = new  FFmpegMediaMetadataRetriever();        try {            retriever.setDataSource(path);            Bitmap bitmap = retriever.getFrameAtTime(100000,FFmpegMediaMetadataRetriever.OPTION_CLOSEST_SYNC );  //这个时间就是第一秒的            iv_image.setImageBitmap(bitmap);        } catch (Exception e) {            e.printStackTrace();        }        finally{            retriever.release();        }    }    @Override    public void onClick(View v) {            int position = getPosition((int) v.getId() / 10);            int type = (int) v.getId() % 10;            if (type == CLICK) { // 正常点击,则触发点击监听器的onItemClick方法                if (mOnItemClickListener != null) {                    mOnItemClickListener.onItemClick(v, position);                }            } else if (type == DELETE) { // 点击了删除按钮,则触发删除监听器的onItemDeleteClick方法                if (mOnItemDeleteClickListener != null) {                    mOnItemDeleteClickListener.onItemDeleteClick(v, position);                }            }    }    @Override    public boolean onLongClick(View v) {        int position = getPosition((int) v.getId() / 10);        if (mOnItemLongClickListener != null) {            mOnItemLongClickListener.onItemLongClick(v, position);        }        return true;    }    // 声明列表项的点击监听器对象    private RecyclerExtras.OnItemClickListener mOnItemClickListener;    public void setOnItemClickListener(RecyclerExtras.OnItemClickListener listener) {        this.mOnItemClickListener = listener;    }    // 声明列表项的长按监听器对象    private RecyclerExtras.OnItemLongClickListener mOnItemLongClickListener;    public void setOnItemLongClickListener(RecyclerExtras.OnItemLongClickListener listener) {        this.mOnItemLongClickListener = listener;    }    // 声明列表项的删除监听器对象    private RecyclerExtras.OnItemDeleteClickListener mOnItemDeleteClickListener;    public void setOnItemDeleteClickListener(RecyclerExtras.OnItemDeleteClickListener listener) {        this.mOnItemDeleteClickListener = listener;    }}

3.1 在主页的界面上能 实现图片以及视频的展示
Gilde可以通过url 获取图片并且显示
FFmpegMediaMetadataRetriever可以通过url获取视频资源的第一帧 然后在首页显示的视频播放图标,只是将两张图片拼接出来的,然后如果点击图片或者视频才会继续向服务器请求新的数据。其实在实际的开发中,图片应该使用缩略图和原图来存储,当用户点击缩略图才放大显示原图,这样可以加速界面的刷新,也省了加载的时间

implementation 'com.github.wseemann:FFmpegMediaMetadataRetriever:1.0.14'implementation 'com.github.bumptech.glide:glide:4.9

3.2
布局方面使用SwipeRefreshLayout用于刷新以及RecyclerView用于放置布局文件,可以实现下拉刷新界面并且 长按组件可以右上角出现删除图标,布局的话有参考了一下网上别人的Demo,学习一下别人对于图片的裁剪实现类似微信朋友圈的九宫格布局layout以及适配器adapter;更主要实现就是先将每个 用户发送的信息 设置为一个item的布局,然后通过类似于list添加到RecyclerView,并且为item设置viewHolder,在viewHolder中对于每个item进行对图片和视频不同的初始化。

public class AppHomeFragment extends Fragment implements View.OnClickListener,OnItemClickListener, OnItemLongClickListener, OnItemDeleteClickListener,OnRefreshListener{    private static final String TAG="HomeFragment";    private int conunt=5;    protected View mView;    protected Context mContext;    private ArrayList<Picinfo> PublicArray=new ArrayList<Picinfo>();//数据链表    private ArrayList<Picinfo> AllArray=new ArrayList<Picinfo>();//数据链表    private static int download=1;//    private final static String Url="http://172.16.86.194:8080/MyWebTest/downloadServlet";//    private final static String Url2="http://172.16.86.194:8080/upload";    private final static String Url="http://你的ip地址:8080/MyWebTest/downloadServlet";    private final static String Url2="http://你的ip地址:8080/upload";    private SwipeRefreshLayout srl_dynamic;//转圈圈    private RecyclerView rv_dynamic; //循环视图    private RecyclerView.LayoutManager rv_manager;//布局管理器    private NineGridAdapter adapter;//适配器    private ArrayList<NineGridItem> mList=new ArrayList<NineGridItem>();    @Nullable    @Override    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {        mContext=getActivity();        mView=inflater.inflate(R.layout.fragment_app_home,container,false);        srl_dynamic=mView.findViewById(R.id.srl_dynamic);        srl_dynamic.setOnRefreshListener((SwipeRefreshLayout.OnRefreshListener) this);        srl_dynamic.setColorSchemeResources(                R.color.red, R.color.orange, R.color.green, R.color.blue);        rv_dynamic=mView.findViewById(R.id.rv_dynamic);        GetfromMysql();        return mView;    }    private void initRecyclerDynamic() {        rv_manager = new LinearLayoutManager(mContext);        rv_dynamic.setLayoutManager(rv_manager);        adapter = new NineGridAdapter(mContext);        adapter.setList(mList);        rv_dynamic.setAdapter(adapter);        // 设置线性列表的点击监听器        adapter.setOnItemClickListener(this);        // 设置线性列表的长按监听器        adapter.setOnItemLongClickListener(this);        // 设置线性列表的删除按钮监听器        adapter.setOnItemDeleteClickListener(this);        rv_dynamic.setItemAnimator(new DefaultItemAnimator());        // 给rv_dynamic添加列表项之间的空白装饰        rv_dynamic.addItemDecoration(new SpacesItemDecoration(1));    }    @Override    public void onRefresh() {        // 延迟若干秒后启动刷新任务        mHandler.postDelayed(mRefresh, 2000);    }    private Handler mHandler = new Handler(); // 声明一个处理器对象    // 定义一个刷新任务    private Runnable mRefresh = new Runnable() {        @Override        public void run() {            srl_dynamic.setRefreshing(false);            mList=new ArrayList<NineGridItem>();            GetfromMysql();            Toast.makeText(mContext,"刷新成功",Toast.LENGTH_SHORT).show();        }    };    private void GetfromMysql(){        new Thread(new Runnable() {            @Override            public void run() {                try{                    //客户端                    HttpClient httpClient=new DefaultHttpClient();                    //post方式                    HttpPost httpPost=new HttpPost(Url);                    //传输数据                    List<NameValuePair> list=new ArrayList<NameValuePair>();                    list.add(new BasicNameValuePair("download","download"));                    list.add(new BasicNameValuePair("count",String.valueOf(conunt++)));                    UrlEncodedFormEntity entity=new UrlEncodedFormEntity(list,"utf-8");                    httpPost.setEntity(entity);                    //回应                    HttpResponse httpResponse=httpClient.execute(httpPost);                    if(httpResponse.getStatusLine().getStatusCode()==200){                        HttpEntity entity1=httpResponse.getEntity();                        String jstr= EntityUtils.toString(entity1,"utf-8");                        Message message=new Message();                        message.what=download;                        message.obj=jstr;                        messageHander.sendMessage(message);                    }                }catch (Exception e){                    e.printStackTrace();                }            }        }).start();    }    private Handler messageHander=new Handler(){        @Override        public void handleMessage(Message msg) {            if(msg.what==download){                initPublicArray((String) msg.obj);//译码                initRecyclerDynamic(); // 初始化动态线性布局的循环视图            }        }    };    private void initPublicArray(String jstr) {        //ali size() org length()        JSONArray array= JSON.parseArray(jstr);        for(int i=0;i<array.size();i++){            JSONObject jsonObject=array.getJSONObject(i);            String uid=jsonObject.getString("uid");            String time=jsonObject.getString("time");            String url=jsonObject.getString("url");            String text=jsonObject.getString("text");            String location=jsonObject.getString("location");            String type=jsonObject.getString("type");            Log.e(TAG,uid+"  "+url);            Log.w(TAG,uid+"  "+url);//            url="http://172.16.86.194:8080/upload"+url;            String []urls=url.split("#");            List<String> urlList = new ArrayList<String>();            for(int j=0;j<urls.length;j++){//                urlList.add("http://172.16.86.194:8080/upload"+urls[j]);                urlList.add(Url2+urls[j]);            }            NineGridItem item=new NineGridItem(uid,time,urlList,text,location,type);            mList.add(item);//            Picinfo picinfo=new Picinfo(uid,text,url,location,false);//            PublicArray.add(picinfo);//            AllArray.add(picinfo);        }    }    @Override    public void onItemClick(View view, int position) {        String desc = String.format("您点击了第%d项,标题是%s", position + 1,               mList.get(position).getText());        Toast.makeText(getActivity(), desc, Toast.LENGTH_SHORT).show();    }    @Override    public void onItemLongClick(View view, int position) {        NineGridItem item=mList.get(position);        item.bPressed=!item.bPressed;        mList.set(position,item);        adapter.notifyItemChanged(position);    }    @Override    public void onItemDeleteClick(View view, int position) {        mList.remove(position);        adapter.notifyItemRemoved(position);    }    @Override    public void onClick(View v) {    }}

4.实现定位
  我是用buidu地图来实现定位的功能的,看着baiduAPI来慢慢实现定位,需要你去百度地图开发平台申请一个密钥放在mainfests中,一个密钥只能对于一个App。并且 在当你选择上传 图片/视频 的时候会自动将地点填入,其实还有挺多功能能实现的,有兴趣可以去baidu地图开发平台了解一下

        <!-- 百度地图密钥 -->        <meta-data            android:name="com.baidu.lbsapi.API_KEY"            android:value="PtUdzaPVt3yQGDgEiGZA9pzgO8Fp6sE4" />        <service            android:name="com.baidu.location.f"            android:enabled="true"            android:process=":remote" />
public class AppMessageFragment extends Fragment implements View.OnClickListener {    private static final String TAG="MessageFragment";    protected View mView;//声明一个视图对象    protected Context mContext;//声明一个上下文对象    private String[] Permissionrequest={Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE};    private MapView mapView;//地图    private BaiduMap baiduMap;    private TextView tv_loccity;    //防止每次定位都重新设置中心点和marker    private boolean isFirstLocation = true;    //初始化LocationClient定位类    private LocationClient mLocationClient = null;    //BDAbstractLocationListener为7.2版本新增的Abstract类型的监听接口,原有BDLocationListener接口    private BDLocationListener myListener = new MyLocationListener();    //经纬度    private double lat;    private double lon;    @Nullable    @Override    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {        mContext=getActivity();        mView=inflater.inflate(R.layout.fragment_app_message,container,false);        mView.findViewById(R.id.btn_picupload).setOnClickListener(this);        mView.findViewById(R.id.btn_vedioupload).setOnClickListener(this);        mapView=mView.findViewById(R.id.mv_mapview);        baiduMap=mapView.getMap();        tv_loccity=mView.findViewById(R.id.tv_loccity);        initMap();        return mView;    }    @Override    public void onClick(View v) {        if(v.getId()==R.id.btn_picupload){             Intent intent=new Intent(mContext, UploadActivity.class);             Bundle bundle=new Bundle();             bundle.putString("city",tv_loccity.getText().toString().trim());             intent.putExtras(bundle);             startActivity(intent);        }        if(v.getId()==R.id.btn_vedioupload){            Intent intent=new Intent(mContext, UploadVideoActivity.class);            Bundle bundle=new Bundle();            bundle.putString("city",tv_loccity.getText().toString().trim());            intent.putExtras(bundle);            startActivity(intent);        }    }    private void initMap() {        //普通地图        baiduMap.setMapType(BaiduMap.MAP_TYPE_NORMAL);        //卫星地图        //baiduMap.setMapType(BaiduMap.MAP_TYPE_SATELLITE);        //空白地图, 基础地图瓦片将不会被渲染。在地图类型中设置为NONE,将不会使用流量下载基础地图瓦片图层。使用场景:与瓦片图层一起使用,节省流量,提升自定义瓦片图下载速度。        //baiduMap.setMapType(BaiduMap.MAP_TYPE_NONE);        //开启交通图        baiduMap.setTrafficEnabled(true);        //关闭缩放按钮        mapView.showZoomControls(false);        // 开启定位图层        baiduMap.setMyLocationEnabled(true);        //声明LocationClient类        mLocationClient = new LocationClient(mContext);        //注册监听函数        mLocationClient.registerLocationListener(myListener);        initLocation();        //开始定位        mLocationClient.start();    }    private void initLocation() {        LocationClientOption option = new LocationClientOption();        //可选,默认高精度,设置定位模式,高精度,低功耗,仅设备        option.setLocationMode(LocationClientOption.LocationMode.Hight_Accuracy);        //可选,默认gcj02,设置返回的定位结果坐标系        option.setCoorType("bd09ll");        //可选,默认0,即仅定位一次,设置发起定位请求的间隔需要大于等于1000ms才是有效的        int span = 5000;        option.setScanSpan(span);        //可选,设置是否需要地址信息,默认不需要        option.setIsNeedAddress(true);        //可选,默认false,设置是否使用gps        option.setOpenGps(true);        //可选,默认false,设置是否当GPS有效时按照1S/1次频率输出GPS结果        option.setLocationNotify(true);        //可选,默认false,设置是否需要位置语义化结果,可以在BDLocation.getLocationDescribe里得到,结果类似于“在北京天安门附近”        option.setIsNeedLocationDescribe(true);        //可选,默认false,设置是否需要POI结果,可以在BDLocation.getPoiList里得到        option.setIsNeedLocationPoiList(true);        //可选,默认true,定位SDK内部是一个SERVICE,并放到了独立进程,设置是否在stop的时候杀死这个进程,默认不杀死        option.setIgnoreKillProcess(false);        //可选,默认false,设置是否收集CRASH信息,默认收集        option.SetIgnoreCacheException(false);        //可选,默认false,设置是否需要过滤GPS仿真结果,默认需要        option.setEnableSimulateGps(false);        mLocationClient.setLocOption(option);    }    /**     * 实现定位监听 位置一旦有所改变就会调用这个方法     * 可以在这个方法里面获取到定位之后获取到的一系列数据     */    public class MyLocationListener implements BDLocationListener {        @Override        public void onReceiveLocation(BDLocation location) {            //获取定位结果            location.getTime();    //获取定位时间            location.getLocationID();    //获取定位唯一ID,v7.2版本新增,用于排查定位问题            location.getLocType();    //获取定位类型            location.getLatitude();    //获取纬度信息            location.getLongitude();    //获取经度信息            location.getRadius();    //获取定位精准度            location.getAddrStr();    //获取地址信息            location.getCountry();    //获取国家信息            location.getCountryCode();    //获取国家码            location.getCity();    //获取城市信息            location.getCityCode();    //获取城市码            location.getDistrict();    //获取区县信息            location.getStreet();    //获取街道信息            location.getStreetNumber();    //获取街道码            location.getLocationDescribe();    //获取当前位置描述信息            location.getPoiList();    //获取当前位置周边POI信息            location.getBuildingID();    //室内精准定位下,获取楼宇ID            location.getBuildingName();    //室内精准定位下,获取楼宇名称            location.getFloor();    //室内精准定位下,获取当前位置所处的楼层信息            //经纬度            lat = location.getLatitude();            lon = location.getLongitude();            //这个判断是为了防止每次定位都重新设置中心点和marker            if (isFirstLocation) {                isFirstLocation = true;                //设置并显示中心点                setPosition2Center(baiduMap, location, true);            }            tv_loccity.setText(location.getCity()+"  "+location.getDistrict()+"  "+location.getStreet());        }    }    /**     * 设置中心点和添加marker     *     * @param map     * @param bdLocation     * @param isShowLoc     */    public void setPosition2Center(BaiduMap map, BDLocation bdLocation, Boolean isShowLoc) {        MyLocationData locData = new MyLocationData.Builder()                .accuracy(bdLocation.getRadius())                .direction(bdLocation.getRadius()).latitude(bdLocation.getLatitude())                .longitude(bdLocation.getLongitude()).build();        map.setMyLocationData(locData);        if (isShowLoc) {            LatLng ll = new LatLng(bdLocation.getLatitude(), bdLocation.getLongitude());            MapStatus.Builder builder = new MapStatus.Builder();            builder.target(ll).zoom(18.0f);            map.animateMapStatus(MapStatusUpdateFactory.newMapStatus(builder.build()));        }    }    @Override    public void onResume() {        mapView.onResume();        super.onResume();    }    @Override    public void onPause() {        mapView.onPause();        super.onPause();    }    @Override    public void onDestroy() {        super.onDestroy();        //在activity执行onDestroy时执行mMapView.onDestroy(),实现地图生命周期管理        // 退出时销毁定位        mLocationClient.unRegisterLocationListener(myListener);        mLocationClient.stop();        // 关闭定位图层        baiduMap.setMyLocationEnabled(false);        mapView.onDestroy();        mapView = null;    }}

5.文件添加上传图片 因和主页界面类似就不放代码
  上传图片/视频,都是依赖于okhttputil来实现的,这是一个基于okhttp来开发的工具,其实也可以直接用okhttp。我主要用它来实现文件的上传,可以点击图片实现进入相册模式,实现的过程是将图片的url传入另一个activity中处理,并且设置这个activity的背景是纯黑,通过改变图片的width以及height 实现放大效果

 public void multiFileUpload() {        //String mBaseUrl="http://172.16.86.49:8001/upload";        //mBaseUrl="http://172.16.86.194:8080/MyWebTest/uploadServlet";        String Allurl="";        for(int i=0;i<fileName.size();i++)        {            Allurl+="/"+username+"/"+fileName.get(i)+"#";        }        Map<String, String> params = new HashMap<>();        params.put("uid",username);        params.put("time", DateUtil.getNowDateTime());        params.put("location",tv_cityloc.getText().toString().trim());        params.put("type",type);        params.put("text",et_text.getText().toString().trim());//        params.put("Allurl",Allurl);        String url = mBaseUrl;        //Log.e(TAG,Calendar.getInstance()+name);        for(int i=0;i<arrayList.size();i++)        {            String s=arrayList.get(i);            String name=s.substring(s.lastIndexOf("/")+1);            File file=new File(s);            if (!file.exists()||!file.exists())            {                return;            }            if(i==(arrayList.size()-1)){                params.put("Allurl",Allurl);            }            OkHttpUtils.post()//                    .addFile("mFile", fileName.get(i), file)////                .addFile("mFile", "2.txt", file2)//                    .url(url)                    .params(params)//                    .build()//                    .execute(new UploadVideoActivity.MyStringCallback());        }    }

6.视频上传
  点击视频可以进入电影模式来播放,手机会自动来实现屏幕的翻转。这个比较难实现,我也是在网上找了很多demo来学习,但最后还是没能找到想要的,还是按着自己买的书一步一步打下来,但还是对于Android比较底层的方法不太了解,最后虽然实现效果,为了赶课设,但自己对这方面还是有些许欠缺


7.**Android 主要的配置文件**
  • 主 build.gradle
    com.android.tools.build:gradle:3.2.1 手动可以设置到自己需要的版本号 我当时是设置为4.10.1的
buildscript {    repositories {        google()        jcenter()            }    dependencies {        classpath 'com.android.tools.build:gradle:3.2.1'                // NOTE: Do not place your application dependencies here; they belong        // in the individual module build.gradle files    }}allprojects {    repositories {        google()        jcenter()         }}task clean(type: Delete) {    delete rootProject.buildDir}
  • AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="com.example.projectthree">    <!-- 互联网 1-->    <uses-permission android:name="android.permission.INTERNET" />    <!-- 查看网络状态 11-->    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />    <!-- 开关网络状态 1-->    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />    <!-- 定位 11-->    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />    <!--用于申请调用A-GPS模块 0-->    <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"></uses-permission>    <!--用于申请获取蓝牙信息进行室内定位-->    <uses-permission android:name="android.permission.BLUETOOTH"></uses-permission>    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"></uses-permission>    <uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" />    <uses-permission android:name="android.permission.WAKE_LOCK"/>    <uses-permission android:name="android.permission.GET_TASKS" />    <uses-permission android:name="android.permission.WRITE_SETTINGS" />    <!-- 查看手机状态 1-->    <uses-permission android:name="android.permission.READ_PHONE_STATE" />    <!-- 下载时不提示通知栏 -->    <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />    <!-- 拍照 -->    <uses-permission android:name="android.permission.CAMERA" />    <uses-feature android:name="android.hardware.camera.autofocus" />    <!-- 录像/录音 -->    <uses-permission android:name="android.permission.RECORD_VIDEO"/>    <uses-permission android:name="android.permission.RECORD_AUDIO" />    <!-- 震动 -->    <uses-permission android:name="android.permission.VIBRATE" />    <!-- SD卡 1-->    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />    <!-- 安装应用请求,Android8.0需要 -->    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />    <application        android:allowBackup="true"        android:icon="@mipmap/ic_launcher"        android:label="@string/app_name"        android:roundIcon="@mipmap/ic_launcher_round"        android:supportsRtl="true"        android:theme="@style/AppTheme"        android:networkSecurityConfig="@xml/network_security_config"        android:name=".MainApplication">        <uses-library android:name="org.apache.http.legacy"            android:required="false"/>        <activity android:name=".MainActivity">            <intent-filter>                <action android:name="android.intent.action.MAIN" />                <category android:name="android.intent.category.LAUNCHER" />            </intent-filter>        </activity>        <!-- 百度地图密钥 -->        <meta-data            android:name="com.baidu.lbsapi.API_KEY"            android:value="PtUdzaPVt3yQGDgEiGZA9pzgO8Fp6sE4" />        <service            android:name="com.baidu.location.f"            android:enabled="true"            android:process=":remote" />        <activity android:name=".AppMainActivity" android:theme="@style/AppCompatTheme"/>        <activity android:name=".LoginServletActivity" android:theme="@style/AppCompatTheme"/>        <activity android:name=".RegisterServletActivity" android:theme="@style/AppCompatTheme"/>        <activity android:name=".UploadActivity" android:theme="@style/AppCompatTheme"/>        <activity            android:name=".MoviePlayActivity"            android:configChanges="orientation|keyboardHidden|screenSize|screenLayout"            android:screenOrientation="sensor"            android:theme="@style/FullScreenTheme"            android:supportsPictureInPicture="true" />        <activity android:name=".MyGalleryActivity" android:theme="@style/AppCompatTheme"/>        <activity android:name=".RecyclerViewExampleActivity" android:theme="@style/AppCompatTheme"/>        <activity android:name=".SecondActivity" android:theme="@style/AppCompatTheme"/>        <activity android:name=".AppMainActivity2" android:theme="@style/AppCompatTheme"/>        <activity android:name=".UploadVideoActivity" android:theme="@style/AppCompatTheme"/>    </application></manifest>
  • projectthree/build.gradle
apply plugin: 'com.android.application'android {    compileSdkVersion 28    defaultConfig {        applicationId "com.example.z2"        minSdkVersion 16        targetSdkVersion 28        versionCode 1        versionName "1.0"        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"    }    buildTypes {        release {            minifyEnabled false            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'        }    }    compileOptions {        sourceCompatibility JavaVersion.VERSION_1_8        targetCompatibility JavaVersion.VERSION_1_8    }    lintOptions{        abortOnError false    }    useLibrary 'org.apache.http.legacy'}allprojects {    repositories {        maven { url "https://jitpack.io" }    }}dependencies {    implementation fileTree(dir: 'libs', include: ['*.jar'])    implementation 'com.android.support:appcompat-v7:28.0.0'    implementation 'com.android.support.constraint:constraint-layout:1.1.3'    testImplementation 'junit:junit:4.12'    androidTestImplementation 'com.android.support.test:runner:1.0.2'    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'    implementation files('libs/httpclient-4.2.5.jar')    implementation project(':okhttputils')//    implementation files('libs/okhttputils-2_6_2.jar')//    implementation files('libs/okhttp-3.4.1.jar')//    implementation files('libs/okio-1.9.0.jar')    implementation 'com.google.code.gson:gson:2.3.1'    implementation 'com.alibaba:fastjson:1.1.54.android'    implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.14.1'    implementation 'com.github.franmontiel:PersistentCookieJar:v1.0.1'//    implementation 'com.github.bumptech.glide:glide:4.0.0'//    implementation "com.android.support:recyclerview-v7:28.0.0"    implementation "com.android.support:design:28.0.0"    implementation "com.android.support:recyclerview-v7:28.0.0"    implementation "com.android.support:cardview-v7:28.0.0"    implementation "com.android.support:palette-v7:28.0.0"    implementation 'com.github.wseemann:FFmpegMediaMetadataRetriever:1.0.14'    implementation 'com.github.bumptech.glide:glide:4.9.0'    implementation 'com.android.support:animated-vector-drawable:28.0.0'    implementation 'com.android.support:support-v4:28.0.0'    implementation 'com.github.chrisbanes:PhotoView:1.3.0'    // https://mvnrepository.com/artifact/com.nostra13.universalimageloader/universal-image-loader    implementation group: 'com.nostra13.universalimageloader', name: 'universal-image-loader', version: '1.9.3'    implementation files('libs/BaiduLBS_Android.jar')    implementation files('libs/component_common_sdk_1.0.0.jar')    implementation files('libs/IndoorscapeAlbumPlugin.jar')    implementation 'com.github.hotchemi:permissionsdispatcher:2.2.0'    annotationProcessor 'com.github.hotchemi:permissionsdispatcher-processor:2.2.0'}
  • okhttputils/build.gradle (需要注释1行代码 若能成功允许则不需要)
apply plugin: 'com.android.library'//apply plugin: 'com.novoda.bintray-release'//添加android {    compileSdkVersion 28    defaultConfig {        minSdkVersion 15        targetSdkVersion 28        versionCode 1        versionName "1.0"    }    buildTypes {        release {            minifyEnabled false            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'        }    }    lintOptions{        abortOnError false        warning 'InvalidPackage'    }}task clearJar(type: Delete) {    delete 'build/outputs/okhttputils.jar'}task makeJar(type: Copy) {    from('build/intermediates/bundles/release/')    into('build/outputs/')    include('classes.jar')    rename ('classes.jar', 'okhttputils-2_6_2.jar')}makeJar.dependsOn(clearJar, build)dependencies {    implementation fileTree(dir: 'libs', include: ['*.jar'])//    compile 'com.squareup.okhttp3:okhttp:3.3.1'    //尝试注释这一行    implementation 'com.zhy:okhttputils:2.6.2'    //尝试注释这一行    implementation 'com.squareup.okio:okio:2.2.2'    //尝试注释这一行    implementation("com.squareup.okhttp3:okhttp:3.14.1")    testImplementation("com.squareup.okhttp3:mockwebserver:3.14.1")}

8.数据库
  当时打这个demo的时候还没认真系统学习数据库,只会简单的增删查改,现在看以前的表结构,还是有时间的时候再去解耦合吧

CREATE TABLE `userinfo` (  `ids` int(255) NOT NULL AUTO_INCREMENT,  `uid` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,  `upw` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,  PRIMARY KEY (`ids`)) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;CREATE TABLE `uploadinfo` (  `ids` int(255) NOT NULL AUTO_INCREMENT,  `uid` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,  `time` datetime NOT NULL,  `url` varchar(255) NOT NULL,  `text` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,  `location` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,  `type` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,  PRIMARY KEY (`ids`)) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;

小结

  这个Demo吃了我很多时间,从学习数据库到javaweb再到Android,每一步都很赶也很急,感觉自己对于Android不会的地方还有许多,要不停看书、测试、去Github找demo,去stackoverflow找解决方法,去Maven repository找gradle等等,感觉自己失去同时期的许多机会,也许有连续几晚因为bug而失眠,但你说有没有学到东西,答案是肯定的能学到的,好的就是培养了自己独立思考的能力,以及以后面对问题的从容和淡定。
  现在写随笔记录一下,是因为自己想深入学习一下JavaWeb前后端,不想再打比较肤浅的sql语句以及比较生硬的交互界面,所以感觉会很长时间不会再碰Android。Android里面的东西的确太多,对于图片的修剪,视频的传输格式,自定义相册以及视频播放器等等,不深入理解是很难打得出来的。而我也是参考了挺多demo,心里也不想充当工具人的角色。Android每年一个版本,Android 10.0 Q已经发布了,确实也需要去了解一下和9.0 Pie有什么区别和功能呢,想学精通有点难度,例如Bluetooth、NFC、Flutter等等这些都需要投入一定的精力去学习。而查询的Fragment也没有实现动态查询也是比较可惜。
  最后测试机MI5C收尾

更多相关文章

  1. android 使用Photoshop获取图片某一点的颜色
  2. 集成Android免费语音合成功能(在线、离线、离在线融合)
  3. 设置AlertDialog的列表样式
  4. Android[学习] UI优化方案
  5. Android下的Java之interface接口泛型 动态获取泛型的类型
  6. 【Android】高德定位错误总结
  7. android中使用okhttp实现文件上传
  8. android 6.0获取mac 地址都是02:00:00:00:00:00 的问题
  9. Android通知管理(NotificationManager)的使用,包括震动,led闪屏

随机推荐

  1. 列出SQL Server中具有默认值的所有字段的
  2. 阿拉伯数字转大写中文_财务常用sql存储过
  3. SQL Server存储过程的基础说明
  4. 在 SQLSERVER 中快速有条件删除海量数据
  5. 在SQL Server启动时自动执行存储过程。第
  6. sqlserver只有MDF文件恢复数据库的方法
  7. sqlserver 游标的简单示例
  8. SQL SERVER性能优化综述(很好的总结,不要
  9. SQL Server常用管理命令小结
  10. mssql无数据库日志文件恢复数据库的方法