Android(安卓)Studio + Eclipse 实现类似微博主页功能APP
我在简书也发布了一份,喜欢黑色背景的同学可以看一下
这其实是大二下的一个课设,内容是实现实时的图片、视频以及地点上传,而我的理解就是实现一个类似于微信朋友圈或是微博的主界面的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
最值得注意的是:
- 在 Android Studio 的 okhttputils 项目中的 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")
- 在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";
- 好了 最麻烦的一步来了 因为没搞云服务器 需要形成一个 PAN(personal area network)
- 你需要保持电脑有网 能用wifi 并且关闭防火墙
- 将Android 的 projectthree导入到 手机 中(默认是Android 9.0 pie)
- 将电脑的热点打开 并用测试手机连接上该电脑wifi 就可以运行了
- 其实也可以借同学的手机来开一下热点 然后你的电脑和测试手机都连接同一个热点 也可以实现。但前提还是得关防火墙
万事俱备 只欠东风
看一下效果图吧
- 登录界面
登录界面的 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(); } } };}
- 第一次登录
第一次登录成功后会提示授权信息 在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收尾
更多相关文章
- android 使用Photoshop获取图片某一点的颜色
- 集成Android免费语音合成功能(在线、离线、离在线融合)
- 设置AlertDialog的列表样式
- Android[学习] UI优化方案
- Android下的Java之interface接口泛型 动态获取泛型的类型
- 【Android】高德定位错误总结
- android中使用okhttp实现文件上传
- android 6.0获取mac 地址都是02:00:00:00:00:00 的问题
- Android通知管理(NotificationManager)的使用,包括震动,led闪屏