我在简书也发布了一份,喜欢黑色背景的同学可以看一下
这其实是大二下的一个课设,内容是实现实时的图片、视频以及地点上传,而我的理解就是实现一个类似于微信朋友圈或是微博的主界面的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
最值得注意的是:
implementation 'com.zhy:okhttputils:2.6.2'
implementation 'com.squareup.okio:okio:2.2.2'
implementation("com.squareup.okhttp3:okhttp:3.14.1")
<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;
LoginServletActivity
UploadActivity
UploadVidioActivity
RegisterServletActivity
AppHomeFragment
AppFindFragment3
private final static String Url="http://本机IP地址:8080/MyWebTest/queryServlet";
看一下效果图吧
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来主要来实现界面的切换
@RuntimePermissions
public 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比较底层的方法不太了解,最后虽然实现效果,为了赶课设,但自己对这方面还是有些许欠缺
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
}
<?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>
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'
}
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收尾